@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/exam-create.js +1 -179
- package/dist/api/exam-create.types.js +1 -1
- package/dist/api/exam-questions.d.ts +1 -1
- package/dist/api/exam-questions.js +1 -98
- package/dist/api/exam-questions.types.d.ts +45 -1
- package/dist/api/exam-questions.types.js +1 -1
- package/dist/api/exam-taking.js +1 -73
- package/dist/api/exam-taking.types.js +1 -1
- package/dist/api/index.js +1 -6
- package/dist/api/result-review.js +1 -50
- package/dist/api/result-review.types.js +1 -1
- package/dist/components/CueCardEditor.js +1 -58
- package/dist/components/common/ResolvedImage.js +1 -15
- package/dist/components/exams/CompletionDialog.d.ts +8 -0
- package/dist/components/exams/CompletionDialog.js +1 -0
- package/dist/components/exams/CreateExamInfoForm.d.ts +4 -1
- package/dist/components/exams/CreateExamInfoForm.js +1 -35
- package/dist/components/exams/CreateExamPageContainer.d.ts +3 -0
- package/dist/components/exams/CreateExamPageContainer.js +1 -103
- package/dist/components/exams/ExamCreator.d.ts +9 -3
- package/dist/components/exams/ExamCreator.js +1 -89
- package/dist/components/exams/ExamPartTabs.d.ts +1 -1
- package/dist/components/exams/ExamPartTabs.js +1 -40
- package/dist/components/exams/ExamPreviewDialog.d.ts +20 -0
- package/dist/components/exams/ExamPreviewDialog.js +1 -0
- package/dist/components/exams/ExamQuestionGrid.d.ts +1 -1
- package/dist/components/exams/ExamQuestionGrid.js +1 -18
- package/dist/components/exams/ExamQuestionsPageContainer.d.ts +3 -0
- package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
- package/dist/components/exams/ExamViewDialog.d.ts +24 -0
- package/dist/components/exams/ExamViewDialog.js +1 -0
- package/dist/components/exams/PartAudioUploader.js +1 -9
- package/dist/components/exams/TemplateSelector.js +1 -66
- package/dist/components/exams/UnsavedChangesDialog.d.ts +8 -0
- package/dist/components/exams/UnsavedChangesDialog.js +1 -0
- package/dist/components/exams/api.d.ts +1 -1
- package/dist/components/exams/api.js +1 -3
- package/dist/components/exams/create-exam.texts.d.ts +58 -0
- package/dist/components/exams/create-exam.texts.js +1 -0
- package/dist/components/exams/create-exam.validation.js +1 -18
- package/dist/components/exams/exam-question-status.d.ts +9 -0
- package/dist/components/exams/exam-question-status.js +1 -0
- package/dist/components/exams/exam-template.utils.js +1 -18
- package/dist/components/exams/index.d.ts +11 -1
- package/dist/components/exams/index.js +1 -10
- package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
- package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
- package/dist/components/exams/take/exam-taking.utils.js +1 -88
- package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
- package/dist/components/exams/take/index.js +1 -2
- package/dist/components/exams/take/types.js +1 -1
- package/dist/components/index.js +1 -42
- package/dist/components/questions/_shared/config/question-types.config.js +1 -333
- package/dist/components/questions/_shared/constants/color-palette.js +1 -21
- package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
- package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
- package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
- package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
- package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
- package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
- package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
- package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
- package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
- package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
- package/dist/components/questions/_shared/index.js +1 -7
- package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
- package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
- package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
- package/dist/components/questions/_shared/types/color-region.config.js +1 -1
- package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
- package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
- package/dist/components/questions/_shared/types/index.js +1 -12
- package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
- package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
- package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
- package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
- package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
- package/dist/components/questions/_shared/types/question-group.type.js +1 -11
- package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
- package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
- package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
- package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
- package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
- package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
- package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
- package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
- package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
- package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
- package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
- package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
- package/dist/components/questions/_shared/utils/id-generator.js +1 -3
- package/dist/components/questions/_shared/utils/question-validation.js +1 -21
- package/dist/components/questions/components/ColorNode.js +1 -85
- package/dist/components/questions/components/ColorRegionClient.js +1 -545
- package/dist/components/questions/components/ColorRegionCreator.js +1 -420
- package/dist/components/questions/components/ColorRegionNode.js +1 -59
- package/dist/components/questions/components/CreatorGuide.js +1 -103
- package/dist/components/questions/components/NameNode.js +1 -26
- package/dist/components/questions/components/QuestionBankOption.js +1 -46
- package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
- package/dist/components/questions/components/QuestionCommonFields.js +1 -33
- package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
- package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
- package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
- package/dist/components/questions/components/RegionNode.js +1 -94
- package/dist/components/questions/components/index.js +1 -14
- package/dist/components/questions/creator/QuestionCreator.js +1 -90
- package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
- package/dist/components/questions/creator/dedicated-component-router.js +1 -79
- package/dist/components/questions/creator/index.js +1 -3
- package/dist/components/questions/creator/question-type-registry.js +1 -80
- package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
- package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
- package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
- package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
- package/dist/components/questions/index.js +1 -10
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
- package/dist/components/questions/types/answer-the-question/register.js +1 -67
- package/dist/components/questions/types/answer-the-question/transform.js +1 -36
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
- package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
- package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
- package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
- package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
- package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
- package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
- package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
- package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
- package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
- package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
- package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
- package/dist/components/questions/types/fill-in-blank/index.js +1 -2
- package/dist/components/questions/types/fill-in-blank/register.js +1 -138
- package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
- package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
- package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
- package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
- package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
- package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
- package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
- package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
- package/dist/components/questions/types/image-object-matching/index.js +1 -2
- package/dist/components/questions/types/image-object-matching/register.js +1 -3
- package/dist/components/questions/types/image-object-matching/transform.js +1 -2
- package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
- package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
- package/dist/components/questions/types/label-the-picture/index.js +1 -2
- package/dist/components/questions/types/label-the-picture/register.js +1 -54
- package/dist/components/questions/types/label-the-picture/transform.js +1 -28
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
- package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
- package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
- package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
- package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
- package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
- package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
- package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
- package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
- package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
- package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
- package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
- package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
- package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
- package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
- package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
- package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
- package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
- package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
- package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
- package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
- package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
- package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
- package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
- package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
- package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
- package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
- package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
- package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
- package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
- package/dist/components/questions/types/read-displayed-content/register.js +1 -64
- package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
- package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
- package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
- package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
- package/dist/components/questions/types/speaking-conversation/register.js +1 -110
- package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
- package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
- package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
- package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
- package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
- package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
- package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
- package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
- package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
- package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
- package/dist/components/questions/types/true-false/index.js +1 -2
- package/dist/components/questions/types/true-false/register.js +1 -77
- package/dist/components/questions/types/true-false/transform.js +1 -32
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
- package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
- package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
- package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
- package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
- package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
- package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
- package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
- package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
- package/dist/components/questions/types/word-ordering/index.js +1 -2
- package/dist/components/questions/types/word-ordering/register.js +1 -46
- package/dist/components/questions/types/word-ordering/transform.js +1 -24
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
- package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
- package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
- package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
- package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
- package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
- package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
- package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
- package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
- package/dist/components/questions/types/write-sentences/index.js +1 -2
- package/dist/components/questions/types/write-sentences/register.js +1 -58
- package/dist/components/questions/types/write-sentences/transform.js +1 -28
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
- package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
- package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
- package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
- package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
- package/dist/components/questions/viewer/QuestionViewer.js +1 -446
- package/dist/components/questions/viewer/index.js +1 -2
- package/dist/components/results/ResultReviewPageContainer.js +1 -98
- package/dist/components/results/ReviewQuestionRenderer.js +1 -72
- package/dist/components/results/index.js +1 -2
- package/dist/components/results/result-review.utils.js +1 -6
- package/dist/components/results/review-data.js +1 -150
- package/dist/components/results/review-renderer.utils.js +1 -97
- package/dist/components/results/review-types.js +1 -1
- package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
- package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
- package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
- package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
- package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
- package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
- package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
- package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
- package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
- package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
- package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
- package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
- package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
- package/dist/components/themes/cambridge-yle/index.js +1 -13
- package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
- package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
- package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
- package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
- package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
- package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
- package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
- package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
- package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
- package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
- package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
- package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
- package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
- package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
- package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
- package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
- package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
- package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
- package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
- package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
- package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
- package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
- package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
- package/dist/components/themes/english-certification/index.js +1 -21
- package/dist/components/themes/index.js +1 -6
- package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
- package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
- package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
- package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
- package/dist/components/themes/types.js +1 -1
- package/dist/components/ui/alert-dialog.js +1 -26
- package/dist/components/ui/alert.js +1 -22
- package/dist/components/ui/avatar.js +1 -12
- package/dist/components/ui/badge.js +1 -38
- package/dist/components/ui/button.js +1 -35
- package/dist/components/ui/calendar.js +1 -76
- package/dist/components/ui/card.js +1 -16
- package/dist/components/ui/carousel.js +1 -91
- package/dist/components/ui/checkbox.js +1 -9
- package/dist/components/ui/collapsible.js +1 -13
- package/dist/components/ui/command.js +1 -34
- package/dist/components/ui/confirm-dialog.js +1 -49
- package/dist/components/ui/dialog.js +1 -23
- package/dist/components/ui/drawer.js +1 -35
- package/dist/components/ui/dropdown-menu.js +1 -33
- package/dist/components/ui/file-upload.js +1 -155
- package/dist/components/ui/form.js +1 -62
- package/dist/components/ui/hover-card.js +1 -10
- package/dist/components/ui/image-preview.js +1 -29
- package/dist/components/ui/input.js +1 -8
- package/dist/components/ui/label.js +1 -9
- package/dist/components/ui/language-switcher.js +1 -15
- package/dist/components/ui/multi-select.js +1 -90
- package/dist/components/ui/points-input.js +1 -33
- package/dist/components/ui/popover.js +1 -26
- package/dist/components/ui/progress.js +1 -16
- package/dist/components/ui/radio-group.js +1 -12
- package/dist/components/ui/scroll-area.js +1 -11
- package/dist/components/ui/select.js +1 -38
- package/dist/components/ui/separator.js +1 -8
- package/dist/components/ui/sheet.js +1 -40
- package/dist/components/ui/single-select.js +1 -76
- package/dist/components/ui/skeleton.js +1 -6
- package/dist/components/ui/switch.js +1 -8
- package/dist/components/ui/table.js +1 -20
- package/dist/components/ui/tabs.js +1 -17
- package/dist/components/ui/textarea.js +1 -8
- package/dist/components/ui/tooltip.js +1 -11
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -19
- package/dist/mcp/config.js +1 -32
- package/dist/mcp/http-client.js +1 -48
- package/dist/mcp/index.js +1 -29
- package/dist/mcp/tools/_helpers.js +1 -25
- package/dist/mcp/tools/exams.js +1 -439
- package/dist/mcp/tools/index.js +1 -6
- package/dist/mcp/tools/results.js +1 -19
- package/dist/shared/constants/ApiConstant.js +1 -276
- package/dist/shared/constants/QueryKeyConstant.js +1 -112
- package/dist/shared/constants/exam-level.enum.js +1 -41
- package/dist/shared/constants/landings/placement-test.constants.js +1 -431
- package/dist/shared/constants/question-skills.js +1 -267
- package/dist/shared/constants/routes.js +1 -81
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.js +1 -7
- package/dist/shared/lib/hooks/index.js +1 -10
- package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
- package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
- package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
- package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
- package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
- package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
- package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
- package/dist/shared/lib/hooks/useFileUpload.js +1 -24
- package/dist/shared/lib/hooks/useImageUrl.js +1 -5
- package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
- package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
- package/dist/shared/lib/hooks/useToast.js +1 -62
- package/dist/shared/lib/i18n/index.js +1 -54
- package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
- package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
- package/dist/shared/lib/i18n/messages/en/common.js +1 -140
- package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
- package/dist/shared/lib/i18n/messages/en/student.js +1 -133
- package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
- package/dist/shared/lib/i18n/messages/en.js +1 -15
- package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
- package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
- package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
- package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
- package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
- package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
- package/dist/shared/lib/i18n/messages/vi.js +1 -15
- package/dist/shared/lib/i18n/types.js +1 -1
- package/dist/shared/lib/stores/examQuestionStore.js +1 -212
- package/dist/shared/lib/stores/localeStore.js +1 -10
- package/dist/shared/lib/utils/public-page.js +1 -44
- package/dist/shared/lib/utils/question-reverse-transform.d.ts +6 -0
- package/dist/shared/lib/utils/question-reverse-transform.js +1 -0
- package/dist/shared/lib/utils/question-transform-types.js +1 -1
- package/dist/shared/lib/utils/question-transform-validation.d.ts +6 -0
- package/dist/shared/lib/utils/question-transform-validation.js +1 -0
- package/dist/shared/lib/utils/question-transform.d.ts +6 -2
- package/dist/shared/lib/utils/question-transform.js +1 -174
- package/dist/shared/lib/utils.js +1 -168
- package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
- package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
- package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
- package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
- package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
- package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
- package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
- package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
- package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
- package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
- package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
- package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
- package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
- package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
- package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
- package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
- package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
- package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
- package/dist/shared/types/branch.types.js +1 -6
- package/dist/shared/types/common.types.js +1 -50
- package/dist/shared/types/entities/exam-schedule.types.js +1 -5
- package/dist/shared/types/entities/exam.types.js +1 -1
- package/dist/shared/types/entities/question.types.js +1 -4
- package/dist/shared/types/entities/result.types.js +1 -4
- package/dist/shared/types/entities/user.types.js +1 -4
- package/dist/shared/types/exam-taking.types.js +1 -2
- package/dist/shared/types/history.types.js +1 -34
- package/dist/shared/types/index.js +1 -1
- package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
- package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
- package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
- package/dist/shared/types/questions/index.js +1 -40
- package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
- package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
- package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
- package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
- package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
- package/dist/shared/types/questions/read-displayed-content.js +1 -1
- package/dist/shared/types/questions/speaking-conversation.js +1 -7
- package/dist/shared/types/questions/speaking-describe-image.js +1 -1
- package/dist/shared/types/questions/spontaneous-qa.js +1 -1
- package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
- package/dist/shared/types/role.types.js +1 -1
- package/dist/shared/types/structure-management.js +1 -28
- package/dist/shared/types/student.types.js +1 -54
- package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
- package/package.json +12 -3
|
@@ -1,320 +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 { FileUpload } from '../../../../components/ui/file-upload';
|
|
10
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
11
|
-
import { Pencil, Eye, Plus, Trash2, Lightbulb, Mail, ImageIcon, X } from 'lucide-react';
|
|
12
|
-
import { WriteAShortLetterClient } from './WriteAShortLetterClient';
|
|
13
|
-
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
14
|
-
import { usePresignedFileUrl } from '../../../../shared/lib/hooks/usePresignedFileUrl';
|
|
15
|
-
import { z } from 'zod';
|
|
16
|
-
// Component to render a single guide image with presigned URL resolution
|
|
17
|
-
function GuideImageItem({ imageKey, index, localPreviewUrl, onRemove, }) {
|
|
18
|
-
const { previewUrl: presignedUrl } = usePresignedFileUrl(imageKey);
|
|
19
|
-
const displayUrl = localPreviewUrl || presignedUrl || '';
|
|
20
|
-
return (_jsxs("div", { className: "group relative overflow-hidden rounded-lg border border-violet-200 bg-white shadow-sm transition-all hover:shadow-md", children: [_jsx("div", { className: "aspect-square w-full overflow-hidden bg-gray-50", children: displayUrl ? (_jsx("img", { src: displayUrl, alt: `Ảnh hướng dẫn ${index + 1}`, className: "h-full w-full object-cover transition-transform group-hover:scale-105", onError: (e) => {
|
|
21
|
-
e.target.src = '/images/placeholder-image.svg';
|
|
22
|
-
} })) : (_jsx("div", { className: "flex h-full w-full items-center justify-center", children: _jsx(ImageIcon, { className: "h-8 w-8 text-gray-300" }) })) }), _jsx("button", { type: "button", onClick: () => onRemove(index), className: "absolute right-1.5 top-1.5 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white opacity-0 shadow-md transition-opacity group-hover:opacity-100 hover:bg-red-600", title: "X\u00F3a \u1EA3nh", children: _jsx(X, { className: "h-3.5 w-3.5" }) }), _jsx("div", { className: "absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/50 to-transparent p-1.5 opacity-0 transition-opacity group-hover:opacity-100", children: _jsxs("span", { className: "text-xs font-medium text-white", children: ["\u1EA2nh ", index + 1] }) })] }));
|
|
23
|
-
}
|
|
24
|
-
// Form schema for validation only
|
|
25
|
-
const writeAShortLetterFormSchema = z.object({
|
|
26
|
-
informations: z.array(z.string()),
|
|
27
|
-
minWords: z.coerce.number().min(1, 'Số từ tối thiểu phải >= 1'),
|
|
28
|
-
maxWords: z.coerce.number().optional(),
|
|
29
|
-
instruction: z.string().optional(),
|
|
30
|
-
points: z.coerce.number().min(1, 'Điểm phải >= 1'),
|
|
31
|
-
});
|
|
32
|
-
function WriteAShortLetterCreatorContent({ initialData, onChange, externalErrors, validationRef, }) {
|
|
33
|
-
// State - use simple useState like ReadAndColorObjectsCreator
|
|
34
|
-
const [informations, setInformations] = useState(initialData?.informations || ['']);
|
|
35
|
-
const [minWords, setMinWords] = useState(initialData?.minWords ?? 20);
|
|
36
|
-
const [maxWords, setMaxWords] = useState(initialData?.maxWords);
|
|
37
|
-
const [instruction, setInstruction] = useState(initialData?.instructions?.mainInstruction || '');
|
|
38
|
-
const [guideImages, setGuideImages] = useState(initialData?.guideImages || []);
|
|
39
|
-
const [points, setPoints] = useState(initialData?.points || 10);
|
|
40
|
-
// Store presigned URLs keyed by imageKey (NOT index) to avoid index races
|
|
41
|
-
// when the user uploads several images quickly.
|
|
42
|
-
const [guideImagePreviewUrls, setGuideImagePreviewUrls] = useState({});
|
|
43
|
-
// Correlates an upload's `onChange(key)` with its later `onPresignedUrlChange(url)`
|
|
44
|
-
const pendingUploadKeyRef = useRef(null);
|
|
45
|
-
// UI State
|
|
46
|
-
const [isClientMode, setIsClientMode] = useState(false);
|
|
47
|
-
// Use react-hook-form for validation only (like ReadAndColorObjectsCreator)
|
|
48
|
-
const form = useReactHookForm({
|
|
49
|
-
defaultValues: {
|
|
50
|
-
informations: informations,
|
|
51
|
-
minWords: minWords,
|
|
52
|
-
maxWords: maxWords,
|
|
53
|
-
instruction: instruction,
|
|
54
|
-
points: points,
|
|
55
|
-
},
|
|
56
|
-
zodSchema: writeAShortLetterFormSchema,
|
|
57
|
-
mode: 'onBlur',
|
|
58
|
-
});
|
|
59
|
-
// Track previous initialData to detect changes
|
|
60
|
-
const previousInitialDataRef = useRef(initialData);
|
|
61
|
-
const isInitializingRef = useRef(true);
|
|
62
|
-
const hasInitializedRef = useRef(false);
|
|
63
|
-
// Stringified shape of the last payload we emitted (in initialData shape).
|
|
64
|
-
// Used to recognize when incoming initialData is just the parent echoing our own
|
|
65
|
-
// emission back — in that case we must NOT re-run the sync block, otherwise
|
|
66
|
-
// fast typing gets silently dropped (the sync's 150ms guard blocks subsequent
|
|
67
|
-
// onChange emissions).
|
|
68
|
-
const lastEmittedInitialDataRef = useRef('');
|
|
69
|
-
// Track a few recently emitted payloads because parent echoes can arrive slightly
|
|
70
|
-
// out of order while typing into number inputs (e.g. local `300`, then parent
|
|
71
|
-
// echoes older `30`). Those stale echoes must be ignored instead of overwriting
|
|
72
|
-
// the latest local state.
|
|
73
|
-
const recentEmittedInitialDataRef = useRef([]);
|
|
74
|
-
// Expose validation function via ref
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
if (validationRef) {
|
|
77
|
-
validationRef.current = {
|
|
78
|
-
triggerValidation: async () => {
|
|
79
|
-
return validateForm();
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
}, [validationRef, informations, minWords, maxWords, instruction, guideImages, points]);
|
|
84
|
-
// Sync state from initialData when it changes
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
const incomingStr = JSON.stringify(initialData);
|
|
87
|
-
// If parent is echoing back one of our very recent emissions, do NOT
|
|
88
|
-
// re-enter the init block — otherwise we'd block subsequent user keystrokes
|
|
89
|
-
// via isInitializingRef for 150ms and silently drop input
|
|
90
|
-
// (e.g. "300" → older echoed "30").
|
|
91
|
-
if (hasInitializedRef.current &&
|
|
92
|
-
(incomingStr === lastEmittedInitialDataRef.current ||
|
|
93
|
-
recentEmittedInitialDataRef.current.includes(incomingStr))) {
|
|
94
|
-
previousInitialDataRef.current = initialData;
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== incomingStr;
|
|
98
|
-
if (initialDataChanged || !hasInitializedRef.current) {
|
|
99
|
-
isInitializingRef.current = true;
|
|
100
|
-
previousInitialDataRef.current = initialData;
|
|
101
|
-
hasInitializedRef.current = true;
|
|
102
|
-
// Always overwrite every field so previous question's state doesn't leak
|
|
103
|
-
// (e.g. previous had maxWords=300, new question has no maxWords → must reset to undefined)
|
|
104
|
-
setInformations(initialData?.informations?.length ? initialData.informations : ['']);
|
|
105
|
-
setMinWords(initialData?.minWords ?? 20);
|
|
106
|
-
setMaxWords(initialData?.maxWords);
|
|
107
|
-
setInstruction(initialData?.instructions?.mainInstruction ?? '');
|
|
108
|
-
setGuideImages(initialData?.guideImages ?? []);
|
|
109
|
-
setPoints(initialData?.points ?? 10);
|
|
110
|
-
// Clear stale preview URLs from the previous question's uploads
|
|
111
|
-
setGuideImagePreviewUrls({});
|
|
112
|
-
// Reset flag after state updates
|
|
113
|
-
setTimeout(() => {
|
|
114
|
-
isInitializingRef.current = false;
|
|
115
|
-
}, 150);
|
|
116
|
-
}
|
|
117
|
-
}, [initialData]);
|
|
118
|
-
// Store onChange callback in ref to avoid dependency issues
|
|
119
|
-
const onChangeRef = useRef(onChange);
|
|
120
|
-
useEffect(() => {
|
|
121
|
-
onChangeRef.current = onChange;
|
|
122
|
-
}, [onChange]);
|
|
123
|
-
// Track previous values to prevent unnecessary onChange calls
|
|
124
|
-
const previousPayloadRef = useRef('');
|
|
125
|
-
// Trigger onChange when state changes (like ReadAndColorObjectsCreator)
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
// Skip onChange during initialization to prevent infinite loop
|
|
128
|
-
if (isInitializingRef.current || !hasInitializedRef.current) {
|
|
129
|
-
// Still update previous payload ref to track current state
|
|
130
|
-
const currentPayload = JSON.stringify({
|
|
131
|
-
informations,
|
|
132
|
-
minWords,
|
|
133
|
-
maxWords,
|
|
134
|
-
instruction,
|
|
135
|
-
guideImages,
|
|
136
|
-
points,
|
|
137
|
-
});
|
|
138
|
-
previousPayloadRef.current = currentPayload;
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
// Don't call onChange if we have no meaningful data yet
|
|
142
|
-
if (!instruction.trim() && informations.every((s) => !s.trim())) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
// Create payload
|
|
146
|
-
const payload = {
|
|
147
|
-
informations,
|
|
148
|
-
minWords,
|
|
149
|
-
maxWords,
|
|
150
|
-
instructions: {
|
|
151
|
-
mainInstruction: instruction || '',
|
|
152
|
-
},
|
|
153
|
-
guideImages: guideImages.length > 0 ? guideImages : undefined,
|
|
154
|
-
points,
|
|
155
|
-
};
|
|
156
|
-
// Serialize payload for comparison
|
|
157
|
-
const payloadString = JSON.stringify(payload);
|
|
158
|
-
// Only call onChange if payload actually changed
|
|
159
|
-
if (previousPayloadRef.current !== payloadString) {
|
|
160
|
-
previousPayloadRef.current = payloadString;
|
|
161
|
-
// Record the shape parent is expected to echo back (via register.tsx
|
|
162
|
-
// transformInitialData), so the sync effect can recognize the echo and
|
|
163
|
-
// not re-enter its init guard. Field order & shape MUST match
|
|
164
|
-
// `transformInitialData` in register.tsx exactly, including
|
|
165
|
-
// `guideImages` being `[]` (never `undefined`).
|
|
166
|
-
lastEmittedInitialDataRef.current = JSON.stringify({
|
|
167
|
-
informations,
|
|
168
|
-
minWords,
|
|
169
|
-
maxWords,
|
|
170
|
-
instructions: { mainInstruction: instruction || '' },
|
|
171
|
-
guideImages,
|
|
172
|
-
points,
|
|
173
|
-
});
|
|
174
|
-
recentEmittedInitialDataRef.current = [
|
|
175
|
-
...recentEmittedInitialDataRef.current,
|
|
176
|
-
lastEmittedInitialDataRef.current,
|
|
177
|
-
].slice(-10);
|
|
178
|
-
onChangeRef.current?.(payload);
|
|
179
|
-
}
|
|
180
|
-
}, [informations, minWords, maxWords, instruction, guideImages, points]);
|
|
181
|
-
// Get validation errors
|
|
182
|
-
const getErrors = () => {
|
|
183
|
-
const errors = {};
|
|
184
|
-
const informationsError = form.formState.errors.informations;
|
|
185
|
-
if (informationsError && typeof informationsError.message === 'string') {
|
|
186
|
-
errors.informations = informationsError.message;
|
|
187
|
-
}
|
|
188
|
-
const minWordsError = form.formState.errors.minWords;
|
|
189
|
-
if (minWordsError && typeof minWordsError.message === 'string') {
|
|
190
|
-
errors.minWords = minWordsError.message;
|
|
191
|
-
}
|
|
192
|
-
const maxWordsError = form.formState.errors.maxWords;
|
|
193
|
-
if (maxWordsError && typeof maxWordsError.message === 'string') {
|
|
194
|
-
errors.maxWords = maxWordsError.message;
|
|
195
|
-
}
|
|
196
|
-
const instructionError = form.formState.errors.instruction;
|
|
197
|
-
if (instructionError && typeof instructionError.message === 'string') {
|
|
198
|
-
errors.instruction = instructionError.message;
|
|
199
|
-
}
|
|
200
|
-
const pointsError = form.formState.errors.points;
|
|
201
|
-
if (pointsError && typeof pointsError.message === 'string') {
|
|
202
|
-
errors.points = pointsError.message;
|
|
203
|
-
}
|
|
204
|
-
return errors;
|
|
205
|
-
};
|
|
206
|
-
// Validate form data
|
|
207
|
-
const validateForm = () => {
|
|
208
|
-
// Update form values before validation
|
|
209
|
-
form.setValue('informations', informations);
|
|
210
|
-
form.setValue('minWords', minWords);
|
|
211
|
-
form.setValue('maxWords', maxWords);
|
|
212
|
-
form.setValue('instruction', instruction);
|
|
213
|
-
form.setValue('points', points);
|
|
214
|
-
// Trigger validation
|
|
215
|
-
const result = writeAShortLetterFormSchema.safeParse({
|
|
216
|
-
informations,
|
|
217
|
-
minWords,
|
|
218
|
-
maxWords,
|
|
219
|
-
instruction,
|
|
220
|
-
points,
|
|
221
|
-
});
|
|
222
|
-
if (!result.success) {
|
|
223
|
-
// Set errors from Zod validation
|
|
224
|
-
result.error.issues.forEach((issue) => {
|
|
225
|
-
const path = issue.path.join('.');
|
|
226
|
-
form.setError(path, { message: issue.message });
|
|
227
|
-
});
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
return true;
|
|
231
|
-
};
|
|
232
|
-
// Add new information
|
|
233
|
-
const addInformation = () => {
|
|
234
|
-
setInformations([...informations, '']);
|
|
235
|
-
form.clearErrors('informations');
|
|
236
|
-
};
|
|
237
|
-
// Remove information
|
|
238
|
-
const removeInformation = (index) => {
|
|
239
|
-
if (informations.length > 1) {
|
|
240
|
-
setInformations(informations.filter((_, i) => i !== index));
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
// Update information at index
|
|
244
|
-
const updateInformation = (index, value) => {
|
|
245
|
-
const newInformations = [...informations];
|
|
246
|
-
newInformations[index] = value;
|
|
247
|
-
setInformations(newInformations);
|
|
248
|
-
form.clearErrors('informations');
|
|
249
|
-
};
|
|
250
|
-
// Handle guide image upload
|
|
251
|
-
const handleGuideImageUpload = (key) => {
|
|
252
|
-
if (key) {
|
|
253
|
-
pendingUploadKeyRef.current = key;
|
|
254
|
-
setGuideImages((prev) => [...prev, key]);
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
// Handle guide image presigned URL from upload
|
|
258
|
-
// Correlate via pendingUploadKeyRef so rapid uploads don't collide on stale index
|
|
259
|
-
const handleGuideImagePresignedUrl = (presignedUrl) => {
|
|
260
|
-
const key = pendingUploadKeyRef.current;
|
|
261
|
-
if (presignedUrl && key) {
|
|
262
|
-
setGuideImagePreviewUrls((prev) => ({ ...prev, [key]: presignedUrl }));
|
|
263
|
-
pendingUploadKeyRef.current = null;
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
// Remove guide image
|
|
267
|
-
const removeGuideImage = (index) => {
|
|
268
|
-
const removedKey = guideImages[index];
|
|
269
|
-
setGuideImages((prev) => prev.filter((_, i) => i !== index));
|
|
270
|
-
if (removedKey) {
|
|
271
|
-
setGuideImagePreviewUrls((prev) => {
|
|
272
|
-
if (!(removedKey in prev))
|
|
273
|
-
return prev;
|
|
274
|
-
const { [removedKey]: _removed, ...rest } = prev;
|
|
275
|
-
return rest;
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
const errors = getErrors();
|
|
280
|
-
// If in client mode, render the Client component
|
|
281
|
-
if (isClientMode) {
|
|
282
|
-
const questionData = {
|
|
283
|
-
informations,
|
|
284
|
-
minWords,
|
|
285
|
-
maxWords,
|
|
286
|
-
instructions: {
|
|
287
|
-
mainInstruction: instruction || '',
|
|
288
|
-
},
|
|
289
|
-
guideImages: guideImages.length > 0 ? guideImages : undefined,
|
|
290
|
-
};
|
|
291
|
-
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: "Write a Short Letter (Preview)" }), _jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Ch\u1EBF \u0111\u1ED9 xem tr\u01B0\u1EDBc - Hi\u1EC3n th\u1ECB \u0111\u00E1p \u00E1n \u0111\u00FAng" })] }), _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(WriteAShortLetterClient, { questionData: questionData, onSubmit: () => { }, isReviewMode: true })] }));
|
|
292
|
-
}
|
|
293
|
-
return (_jsxs("div", { className: "space-y-6", children: [externalErrors && externalErrors.length > 0 && (_jsx("div", { className: "rounded-lg border border-red-200 bg-red-50 p-4", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex-shrink-0", children: _jsx("svg", { className: "h-5 w-5 text-red-600", fill: "currentColor", viewBox: "0 0 20 20", children: _jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z", clipRule: "evenodd" }) }) }), _jsxs("div", { className: "flex-1", children: [_jsx("h3", { className: "text-sm font-medium text-red-800", children: "Vui l\u00F2ng s\u1EEDa c\u00E1c l\u1ED7i sau:" }), _jsx("ul", { className: "mt-2 list-disc list-inside space-y-1", children: externalErrors.map((error, index) => (_jsx("li", { className: "text-sm text-red-700", children: error }, index))) })] })] }) })), _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-pink-500 via-rose-500 to-orange-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-pink-500 to-rose-600 shadow-md", children: _jsx(Mail, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: "Write a Short Letter" }), _jsx("p", { className: "text-sm text-gray-500", children: "T\u1EA1o b\u00E0i t\u1EADp vi\u1EBFt th\u01B0 v\u1EDBi c\u00E1c th\u00F4ng tin c\u1EA7n bao g\u1ED3m" })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: !minWords || minWords < 1, className: "gap-2 border-rose-200 text-rose-600 hover:bg-rose-50 hover:text-rose-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Xem tr\u01B0\u1EDBc"] })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsx("div", { className: "rounded-xl border border-pink-200 bg-gradient-to-r from-pink-50 to-rose-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-pink-100", children: _jsx(Lightbulb, { className: "h-4 w-4 text-pink-600" }) }), _jsxs("div", { className: "text-sm text-pink-900", children: [_jsx("p", { className: "font-semibold text-pink-800", children: "H\u01B0\u1EDBng d\u1EABn t\u1EA1o c\u00E2u h\u1ECFi" }), _jsxs("ul", { className: "mt-2 space-y-1.5 text-pink-700", children: [_jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-pink-400" }), "Nh\u1EADp h\u01B0\u1EDBng d\u1EABn vi\u1EBFt th\u01B0 cho h\u1ECDc sinh"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-pink-400" }), "Li\u1EC7t k\u00EA c\u00E1c th\u00F4ng tin c\u1EA7n bao g\u1ED3m trong b\u00E0i vi\u1EBFt"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-pink-400" }), "Thi\u1EBFt l\u1EADp gi\u1EDBi h\u1EA1n s\u1ED1 t\u1EEB t\u1ED1i thi\u1EC3u/t\u1ED1i \u0111a"] })] })] })] }) }), _jsxs("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [_jsx("div", { children: _jsx(PointsInput, { defaultValue: points, onChange: (e) => setPoints(Number(e.target.value) || 1), error: errors.points }) }), _jsxs("div", { className: "rounded-lg border border-gray-200 bg-gray-50/50 p-4", children: [_jsx(Label, { className: "mb-3 block text-sm font-medium text-gray-700", children: "Gi\u1EDBi h\u1EA1n s\u1ED1 t\u1EEB" }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex-1", children: [_jsxs(Label, { htmlFor: "minWords", className: "text-xs text-gray-500", children: ["T\u1ED1i thi\u1EC3u ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { id: "minWords", type: "number", min: "1", step: "1", value: minWords ?? '', onChange: (e) => {
|
|
294
|
-
const value = e.target.value;
|
|
295
|
-
if (value === '') {
|
|
296
|
-
setMinWords(1);
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
const numValue = parseInt(value, 10);
|
|
300
|
-
setMinWords(numValue < 1 ? 1 : numValue);
|
|
301
|
-
}
|
|
302
|
-
form.clearErrors('minWords');
|
|
303
|
-
}, placeholder: "20", className: `mt-1 ${errors.minWords ? 'border-red-500 focus:ring-red-500' : ''}` }), errors.minWords && (_jsx("p", { className: "mt-1 text-xs text-red-600", children: errors.minWords }))] }), _jsx("span", { className: "mt-6 text-gray-400", children: "\u2014" }), _jsxs("div", { className: "flex-1", children: [_jsx(Label, { htmlFor: "maxWords", className: "text-xs text-gray-500", children: "T\u1ED1i \u0111a" }), _jsx(Input, { id: "maxWords", type: "number", min: "1", step: "1", value: maxWords ?? '', onChange: (e) => {
|
|
304
|
-
const value = e.target.value;
|
|
305
|
-
if (value === '') {
|
|
306
|
-
setMaxWords(undefined);
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
const numValue = parseInt(value, 10);
|
|
310
|
-
setMaxWords(numValue < 1 ? 1 : numValue);
|
|
311
|
-
}
|
|
312
|
-
form.clearErrors('maxWords');
|
|
313
|
-
}, placeholder: "\u221E", className: `mt-1 ${errors.maxWords ? 'border-red-500 focus:ring-red-500' : ''}` }), errors.maxWords && (_jsx("p", { className: "mt-1 text-xs text-red-600", children: errors.maxWords }))] })] })] })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "instruction", className: "text-sm font-medium", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(Textarea, { id: "instruction", value: instruction, onChange: (e) => {
|
|
314
|
-
setInstruction(e.target.value);
|
|
315
|
-
form.clearErrors('instruction');
|
|
316
|
-
}, placeholder: "V\u00ED d\u1EE5: You are having a birthday party. Write a short letter to your friend, Minh, to invite him. Tell him about the party.", className: `mt-1.5 resize-none ${errors.instruction ? 'border-red-500' : ''}`, rows: 3 }), errors.instruction && (_jsx("p", { className: "mt-1 text-sm text-red-600", children: errors.instruction }))] }), _jsxs("div", { className: "space-y-3 rounded-lg border border-blue-100 bg-blue-50/30 p-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-full bg-blue-100", children: _jsx("span", { className: "text-xs font-semibold text-blue-600", children: "i" }) }), _jsx(Label, { className: "text-sm font-semibold text-gray-800", children: "Th\u00F4ng tin c\u1EA7n bao g\u1ED3m" })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addInformation, className: "h-8 gap-1.5 border-blue-200 bg-white text-blue-600 hover:bg-blue-50 hover:text-blue-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm"] })] }), errors.informations && (_jsx("div", { className: "rounded-md border border-red-200 bg-red-50 p-3", children: _jsx("p", { className: "text-sm text-red-600", children: errors.informations }) })), _jsx("div", { className: "space-y-2", children: informations.map((info, index) => (_jsx("div", { className: "space-y-1", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600", children: index + 1 }), _jsx(Input, { value: info, onChange: (e) => updateInformation(index, e.target.value), placeholder: `Thông tin ${index + 1}...`, className: "flex-1 border-gray-200" }), informations.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeInformation(index), className: "h-8 w-8 flex-shrink-0 p-0 text-gray-400 hover:bg-red-50 hover:text-red-500", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }) }, index))) }), _jsx("p", { className: "text-xs text-gray-500 italic", children: "\uD83D\uDCA1 Li\u1EC7t k\u00EA c\u00E1c th\u00F4ng tin h\u1ECDc sinh c\u1EA7n \u0111\u1EC1 c\u1EADp trong b\u00E0i vi\u1EBFt" })] }), _jsxs("div", { className: "space-y-3 rounded-lg border border-violet-100 bg-violet-50/30 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-full bg-violet-100", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5 text-violet-600" }) }), _jsx(Label, { className: "text-sm font-semibold text-gray-800", children: "\u1EA2nh h\u01B0\u1EDBng d\u1EABn" }), _jsx("span", { className: "text-xs text-gray-400" })] }), guideImages.length > 0 && (_jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4", children: guideImages.map((imageKey, index) => (_jsx(GuideImageItem, { imageKey: imageKey, index: index, localPreviewUrl: guideImagePreviewUrls[imageKey], onRemove: removeGuideImage }, `${imageKey}-${index}`))) })), _jsx(FileUpload, { label: "Th\u00EAm \u1EA3nh h\u01B0\u1EDBng d\u1EABn", accept: "image/*", value: "", onChange: handleGuideImageUpload, onPresignedUrlChange: handleGuideImagePresignedUrl, autoUpload: true, prefix: "write-short-letter/guide", placeholder: "Ch\u1ECDn ho\u1EB7c k\u00E9o th\u1EA3 \u1EA3nh v\u00E0o \u0111\u00E2y", icon: _jsx(ImageIcon, { className: "h-4 w-4" }) }), _jsx("p", { className: "text-xs text-gray-500 italic", children: "\uD83D\uDCF7 Upload nhi\u1EC1u \u1EA3nh \u0111\u1EC3 h\u01B0\u1EDBng d\u1EABn h\u1ECDc sinh vi\u1EBFt b\u00E0i (v\u00ED d\u1EE5: m\u1EABu th\u01B0, h\u00ECnh \u1EA3nh minh h\u1ECDa)" })] })] })] })] }));
|
|
317
|
-
}
|
|
318
|
-
export function WriteAShortLetterCreator(props) {
|
|
319
|
-
return _jsx(WriteAShortLetterCreatorContent, { ...props });
|
|
320
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useRef as s,useEffect as i}from"react";import{Button as n}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{FileUpload as d}from"../../../../components/ui/file-upload";import{Card as m,CardContent as h,CardHeader as u,CardTitle as g}from"../../../../components/ui/card";import{Pencil as p,Eye as f,Plus as x,Trash2 as v,Lightbulb as b,Mail as N,ImageIcon as y,X as w}from"lucide-react";import{WriteAShortLetterClient as W}from"./WriteAShortLetterClient";import{useReactHookForm as k}from"../../../../shared/lib/hooks/useReactHookForm";import{usePresignedFileUrl as C}from"../../../../shared/lib/hooks/usePresignedFileUrl";import{z as S}from"zod";function j({imageKey:t,index:s,localPreviewUrl:i,onRemove:n}){const{previewUrl:a}=C(t),o=i||a||"";return r("div",{className:"group relative overflow-hidden rounded-lg border border-violet-200 bg-white shadow-sm transition-all hover:shadow-md",children:[e("div",{className:"aspect-square w-full overflow-hidden bg-gray-50",children:o?e("img",{src:o,alt:`Ảnh hướng dẫn ${s+1}`,className:"h-full w-full object-cover transition-transform group-hover:scale-105",onError:e=>{e.target.src="/images/placeholder-image.svg"}}):e("div",{className:"flex h-full w-full items-center justify-center",children:e(y,{className:"h-8 w-8 text-gray-300"})})}),e("button",{type:"button",onClick:()=>n(s),className:"absolute right-1.5 top-1.5 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white opacity-0 shadow-md transition-opacity group-hover:opacity-100 hover:bg-red-600",title:"Xóa ảnh",children:e(w,{className:"h-3.5 w-3.5"})}),e("div",{className:"absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/50 to-transparent p-1.5 opacity-0 transition-opacity group-hover:opacity-100",children:r("span",{className:"text-xs font-medium text-white",children:["Ảnh ",s+1]})})]})}const I=S.object({informations:S.array(S.string()),minWords:S.coerce.number().min(1,"Số từ tối thiểu phải >= 1"),maxWords:S.coerce.number().optional(),instruction:S.string().optional(),points:S.coerce.number().min(1,"Điểm phải >= 1")});function E({initialData:w,onChange:C,externalErrors:S,validationRef:E}){const[L,T]=t(w?.informations||[""]),[V,z]=t(w?.minWords??20),[R,U]=t(w?.maxWords),[P,F]=t(w?.instructions?.mainInstruction||""),[J,M]=t(w?.guideImages||[]),[O,H]=t(w?.points||10),[$,q]=t({}),A=s(null),[B,D]=t(!1),K=k({defaultValues:{informations:L,minWords:V,maxWords:R,instruction:P,points:O},zodSchema:I,mode:"onBlur"}),X=s(w),G=s(!0),Q=s(!1),Y=s(""),Z=s([]);i(()=>{E&&(E.current={triggerValidation:async()=>re()})},[E,L,V,R,P,J,O]),i(()=>{const e=JSON.stringify(w);if(Q.current&&(e===Y.current||Z.current.includes(e)))return void(X.current=w);!(JSON.stringify(X.current)!==e)&&Q.current||(G.current=!0,X.current=w,Q.current=!0,T(w?.informations?.length?w.informations:[""]),z(w?.minWords??20),U(w?.maxWords),F(w?.instructions?.mainInstruction??""),M(w?.guideImages??[]),H(w?.points??10),q({}),setTimeout(()=>{G.current=!1},150))},[w]);const _=s(C);i(()=>{_.current=C},[C]);const ee=s("");i(()=>{if(G.current||!Q.current){const e=JSON.stringify({informations:L,minWords:V,maxWords:R,instruction:P,guideImages:J,points:O});return void(ee.current=e)}if(!P.trim()&&L.every(e=>!e.trim()))return;const e={informations:L,minWords:V,maxWords:R,instructions:{mainInstruction:P||""},guideImages:J.length>0?J:void 0,points:O},r=JSON.stringify(e);ee.current!==r&&(ee.current=r,Y.current=JSON.stringify({informations:L,minWords:V,maxWords:R,instructions:{mainInstruction:P||""},guideImages:J,points:O}),Z.current=[...Z.current,Y.current].slice(-10),_.current?.(e))},[L,V,R,P,J,O]);const re=()=>{K.setValue("informations",L),K.setValue("minWords",V),K.setValue("maxWords",R),K.setValue("instruction",P),K.setValue("points",O);const e=I.safeParse({informations:L,minWords:V,maxWords:R,instruction:P,points:O});return!!e.success||(e.error.issues.forEach(e=>{const r=e.path.join(".");K.setError(r,{message:e.message})}),!1)},te=e=>{const r=J[e];M(r=>r.filter((r,t)=>t!==e)),r&&q(e=>{if(!(r in e))return e;const{[r]:t,...s}=e;return s})},se=(()=>{const e={},r=K.formState.errors.informations;r&&"string"==typeof r.message&&(e.informations=r.message);const t=K.formState.errors.minWords;t&&"string"==typeof t.message&&(e.minWords=t.message);const s=K.formState.errors.maxWords;s&&"string"==typeof s.message&&(e.maxWords=s.message);const i=K.formState.errors.instruction;i&&"string"==typeof i.message&&(e.instruction=i.message);const n=K.formState.errors.points;return n&&"string"==typeof n.message&&(e.points=n.message),e})();if(B){const t={informations:L,minWords:V,maxWords:R,instructions:{mainInstruction:P||""},guideImages:J.length>0?J:void 0};return r("div",{className:"space-y-6",children:[e(m,{children:e(u,{children:r("div",{className:"flex items-center justify-between",children:[r("div",{children:[e(g,{children:"Write a Short Letter (Preview)"}),e("p",{className:"mt-1 text-sm text-gray-500",children:"Chế độ xem trước - Hiển thị đáp án đúng"})]}),r(n,{variant:"outline",size:"sm",onClick:()=>D(!1),children:[e(p,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})})}),e(W,{questionData:t,onSubmit:()=>{},isReviewMode:!0})]})}return r("div",{className:"space-y-6",children:[S&&S.length>0&&e("div",{className:"rounded-lg border border-red-200 bg-red-50 p-4",children:r("div",{className:"flex items-start gap-3",children:[e("div",{className:"flex-shrink-0",children:e("svg",{className:"h-5 w-5 text-red-600",fill:"currentColor",viewBox:"0 0 20 20",children:e("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z",clipRule:"evenodd"})})}),r("div",{className:"flex-1",children:[e("h3",{className:"text-sm font-medium text-red-800",children:"Vui lòng sửa các lỗi sau:"}),e("ul",{className:"mt-2 list-disc list-inside space-y-1",children:S.map((r,t)=>e("li",{className:"text-sm text-red-700",children:r},t))})]})]})}),r(m,{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-pink-500 via-rose-500 to-orange-500"}),e(u,{className:"pb-4",children:r("div",{className:"flex items-center justify-between",children:[r("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-pink-500 to-rose-600 shadow-md",children:e(N,{className:"h-5 w-5 text-white"})}),r("div",{children:[e(g,{className:"text-xl font-bold text-gray-900",children:"Write a Short Letter"}),e("p",{className:"text-sm text-gray-500",children:"Tạo bài tập viết thư với các thông tin cần bao gồm"})]})]}),r(n,{variant:"outline",size:"sm",onClick:()=>D(!0),disabled:!V||V<1,className:"gap-2 border-rose-200 text-rose-600 hover:bg-rose-50 hover:text-rose-700",children:[e(f,{className:"h-4 w-4"}),"Xem trước"]})]})}),r(h,{className:"space-y-6",children:[e("div",{className:"rounded-xl border border-pink-200 bg-gradient-to-r from-pink-50 to-rose-50 p-5 shadow-sm",children:r("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-pink-100",children:e(b,{className:"h-4 w-4 text-pink-600"})}),r("div",{className:"text-sm text-pink-900",children:[e("p",{className:"font-semibold text-pink-800",children:"Hướng dẫn tạo câu hỏi"}),r("ul",{className:"mt-2 space-y-1.5 text-pink-700",children:[r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-pink-400"}),"Nhập hướng dẫn viết thư cho học sinh"]}),r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-pink-400"}),"Liệt kê các thông tin cần bao gồm trong bài viết"]}),r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-pink-400"}),"Thiết lập giới hạn số từ tối thiểu/tối đa"]})]})]})]})}),r("div",{className:"grid grid-cols-1 gap-6 md:grid-cols-2",children:[e("div",{children:e(c,{defaultValue:O,onChange:e=>H(Number(e.target.value)||1),error:se.points})}),r("div",{className:"rounded-lg border border-gray-200 bg-gray-50/50 p-4",children:[e(a,{className:"mb-3 block text-sm font-medium text-gray-700",children:"Giới hạn số từ"}),r("div",{className:"flex items-center gap-4",children:[r("div",{className:"flex-1",children:[r(a,{htmlFor:"minWords",className:"text-xs text-gray-500",children:["Tối thiểu ",e("span",{className:"text-red-500",children:"*"})]}),e(o,{id:"minWords",type:"number",min:"1",step:"1",value:V??"",onChange:e=>{const r=e.target.value;if(""===r)z(1);else{const e=parseInt(r,10);z(e<1?1:e)}K.clearErrors("minWords")},placeholder:"20",className:"mt-1 "+(se.minWords?"border-red-500 focus:ring-red-500":"")}),se.minWords&&e("p",{className:"mt-1 text-xs text-red-600",children:se.minWords})]}),e("span",{className:"mt-6 text-gray-400",children:"—"}),r("div",{className:"flex-1",children:[e(a,{htmlFor:"maxWords",className:"text-xs text-gray-500",children:"Tối đa"}),e(o,{id:"maxWords",type:"number",min:"1",step:"1",value:R??"",onChange:e=>{const r=e.target.value;if(""===r)U(void 0);else{const e=parseInt(r,10);U(e<1?1:e)}K.clearErrors("maxWords")},placeholder:"∞",className:"mt-1 "+(se.maxWords?"border-red-500 focus:ring-red-500":"")}),se.maxWords&&e("p",{className:"mt-1 text-xs text-red-600",children:se.maxWords})]})]})]})]}),r("div",{children:[e(a,{htmlFor:"instruction",className:"text-sm font-medium",children:"Hướng dẫn"}),e(l,{id:"instruction",value:P,onChange:e=>{F(e.target.value),K.clearErrors("instruction")},placeholder:"Ví dụ: You are having a birthday party. Write a short letter to your friend, Minh, to invite him. Tell him about the party.",className:"mt-1.5 resize-none "+(se.instruction?"border-red-500":""),rows:3}),se.instruction&&e("p",{className:"mt-1 text-sm text-red-600",children:se.instruction})]}),r("div",{className:"space-y-3 rounded-lg border border-blue-100 bg-blue-50/30 p-4",children:[r("div",{className:"flex items-center justify-between",children:[r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-full bg-blue-100",children:e("span",{className:"text-xs font-semibold text-blue-600",children:"i"})}),e(a,{className:"text-sm font-semibold text-gray-800",children:"Thông tin cần bao gồm"})]}),r(n,{type:"button",variant:"outline",size:"sm",onClick:()=>{T([...L,""]),K.clearErrors("informations")},className:"h-8 gap-1.5 border-blue-200 bg-white text-blue-600 hover:bg-blue-50 hover:text-blue-700",children:[e(x,{className:"h-3.5 w-3.5"}),"Thêm"]})]}),se.informations&&e("div",{className:"rounded-md border border-red-200 bg-red-50 p-3",children:e("p",{className:"text-sm text-red-600",children:se.informations})}),e("div",{className:"space-y-2",children:L.map((t,s)=>e("div",{className:"space-y-1",children:r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600",children:s+1}),e(o,{value:t,onChange:e=>((e,r)=>{const t=[...L];t[e]=r,T(t),K.clearErrors("informations")})(s,e.target.value),placeholder:`Thông tin ${s+1}...`,className:"flex-1 border-gray-200"}),L.length>1&&e(n,{type:"button",variant:"ghost",size:"sm",onClick:()=>(e=>{L.length>1&&T(L.filter((r,t)=>t!==e))})(s),className:"h-8 w-8 flex-shrink-0 p-0 text-gray-400 hover:bg-red-50 hover:text-red-500",children:e(v,{className:"h-4 w-4"})})]})},s))}),e("p",{className:"text-xs text-gray-500 italic",children:"💡 Liệt kê các thông tin học sinh cần đề cập trong bài viết"})]}),r("div",{className:"space-y-3 rounded-lg border border-violet-100 bg-violet-50/30 p-4",children:[r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-full bg-violet-100",children:e(y,{className:"h-3.5 w-3.5 text-violet-600"})}),e(a,{className:"text-sm font-semibold text-gray-800",children:"Ảnh hướng dẫn"}),e("span",{className:"text-xs text-gray-400"})]}),J.length>0&&e("div",{className:"grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4",children:J.map((r,t)=>e(j,{imageKey:r,index:t,localPreviewUrl:$[r],onRemove:te},`${r}-${t}`))}),e(d,{label:"Thêm ảnh hướng dẫn",accept:"image/*",value:"",onChange:e=>{e&&(A.current=e,M(r=>[...r,e]))},onPresignedUrlChange:e=>{const r=A.current;e&&r&&(q(t=>({...t,[r]:e})),A.current=null)},autoUpload:!0,prefix:"write-short-letter/guide",placeholder:"Chọn hoặc kéo thả ảnh vào đây",icon:e(y,{className:"h-4 w-4"})}),e("p",{className:"text-xs text-gray-500 italic",children:"📷 Upload nhiều ảnh để hướng dẫn học sinh viết bài (ví dụ: mẫu thư, hình ảnh minh họa)"})]})]})]})]})}export function WriteAShortLetterCreator(r){return e(E,{...r})}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from './WriteAShortLetterClient';
|
|
1
|
+
export*from"./WriteAShortLetterCreator";export*from"./WriteAShortLetterClient";
|
|
@@ -1,68 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const writeAShortLetterRegistration = {
|
|
3
|
-
component: WriteAShortLetterCreator,
|
|
4
|
-
transformInitialData: (initialData) => {
|
|
5
|
-
const content = initialData?.content;
|
|
6
|
-
const answer = initialData?.answer;
|
|
7
|
-
const apiPoints = initialData?.total_points
|
|
8
|
-
? parseFloat(initialData.total_points)
|
|
9
|
-
: (initialData?.points || 10); // WRITE_A_SHORT_LETTER defaults to 10 points
|
|
10
|
-
let creatorData = { points: apiPoints };
|
|
11
|
-
// Priority 1: Data from store (answer has informations directly)
|
|
12
|
-
if (answer?.informations && Array.isArray(answer.informations)) {
|
|
13
|
-
creatorData = {
|
|
14
|
-
informations: answer.informations,
|
|
15
|
-
minWords: answer.minWords,
|
|
16
|
-
maxWords: answer.maxWords,
|
|
17
|
-
instructions: answer.instructions || { mainInstruction: '' },
|
|
18
|
-
guideImages: answer.guideImages || [],
|
|
19
|
-
points: apiPoints,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
// Priority 2: Data from API (content has informations)
|
|
23
|
-
else if (content?.informations && Array.isArray(content.informations)) {
|
|
24
|
-
creatorData = {
|
|
25
|
-
informations: content.informations,
|
|
26
|
-
minWords: content.minWords,
|
|
27
|
-
maxWords: content.maxWords,
|
|
28
|
-
instructions: content.instructions || { mainInstruction: '' },
|
|
29
|
-
guideImages: content.guideImages || [],
|
|
30
|
-
points: apiPoints,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
// Priority 3: Legacy format with contexts
|
|
34
|
-
else if (content?.contexts || answer?.contexts) {
|
|
35
|
-
const contexts = content?.contexts || answer?.contexts || [];
|
|
36
|
-
const informations = contexts
|
|
37
|
-
.filter((ctx) => ctx.type === 'text' && ctx.text)
|
|
38
|
-
.map((ctx) => ctx.text);
|
|
39
|
-
creatorData = {
|
|
40
|
-
informations: informations.length > 0 ? informations : [],
|
|
41
|
-
minWords: content?.minWords ?? answer?.minWords,
|
|
42
|
-
maxWords: content?.maxWords ?? answer?.maxWords,
|
|
43
|
-
instructions: content?.instructions || answer?.instructions || { mainInstruction: '' },
|
|
44
|
-
guideImages: content?.guideImages || answer?.guideImages || [],
|
|
45
|
-
points: apiPoints,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
return creatorData;
|
|
49
|
-
},
|
|
50
|
-
wrapOnSave: (data, ctx) => ({
|
|
51
|
-
type: ctx.questionType,
|
|
52
|
-
content: ctx.state.content,
|
|
53
|
-
points: data?.points ?? ctx.state.points ?? 10,
|
|
54
|
-
level: ctx.state.level,
|
|
55
|
-
difficulty: ctx.state.difficulty,
|
|
56
|
-
skill: ctx.state.skill,
|
|
57
|
-
answer: data,
|
|
58
|
-
}),
|
|
59
|
-
wrapOnChange: (data, ctx) => ({
|
|
60
|
-
type: ctx.questionType,
|
|
61
|
-
content: ctx.state.content,
|
|
62
|
-
points: data?.points ?? ctx.state.points ?? 10,
|
|
63
|
-
level: ctx.state.level,
|
|
64
|
-
difficulty: ctx.state.difficulty,
|
|
65
|
-
skill: ctx.state.skill,
|
|
66
|
-
answer: data,
|
|
67
|
-
}),
|
|
68
|
-
};
|
|
1
|
+
import{WriteAShortLetterCreator as t}from"./WriteAShortLetterCreator";export const writeAShortLetterRegistration={component:t,transformInitialData:t=>{const n=t?.content,s=t?.answer,i=t?.total_points?parseFloat(t.total_points):t?.points||10;let o={points:i};if(s?.informations&&Array.isArray(s.informations))o={informations:s.informations,minWords:s.minWords,maxWords:s.maxWords,instructions:s.instructions||{mainInstruction:""},guideImages:s.guideImages||[],points:i};else if(n?.informations&&Array.isArray(n.informations))o={informations:n.informations,minWords:n.minWords,maxWords:n.maxWords,instructions:n.instructions||{mainInstruction:""},guideImages:n.guideImages||[],points:i};else if(n?.contexts||s?.contexts){const t=(n?.contexts||s?.contexts||[]).filter(t=>"text"===t.type&&t.text).map(t=>t.text);o={informations:t.length>0?t:[],minWords:n?.minWords??s?.minWords,maxWords:n?.maxWords??s?.maxWords,instructions:n?.instructions||s?.instructions||{mainInstruction:""},guideImages:n?.guideImages||s?.guideImages||[],points:i}}return o},wrapOnSave:(t,n)=>({type:n.questionType,content:n.state.content,points:t?.points??n.state.points??10,level:n.state.level,difficulty:n.state.difficulty,skill:n.state.skill,answer:t}),wrapOnChange:(t,n)=>({type:n.questionType,content:n.state.content,points:t?.points??n.state.points??10,level:n.state.level,difficulty:n.state.difficulty,skill:n.state.skill,answer:t})};
|
|
@@ -1,25 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// Uses simple string[] for informations (different from WRITE_SHORT_PARAGRAPH which uses object[] contexts)
|
|
3
|
-
export const transformWriteAShortLetter = (question) => {
|
|
4
|
-
const answerData = question.answer;
|
|
5
|
-
const informations = Array.isArray(answerData?.informations)
|
|
6
|
-
? answerData.informations.filter((info) => typeof info === 'string' && info.trim() !== '')
|
|
7
|
-
: [];
|
|
8
|
-
const mainInstruction = answerData?.instructions?.mainInstruction || '';
|
|
9
|
-
const guideImages = Array.isArray(answerData?.guideImages)
|
|
10
|
-
? answerData.guideImages.filter((url) => typeof url === 'string' && url.trim() !== '')
|
|
11
|
-
: [];
|
|
12
|
-
const apiContent = {
|
|
13
|
-
informations,
|
|
14
|
-
minWords: answerData?.minWords || 20,
|
|
15
|
-
maxWords: answerData?.maxWords ?? null,
|
|
16
|
-
instructions: { mainInstruction },
|
|
17
|
-
};
|
|
18
|
-
if (guideImages.length > 0) {
|
|
19
|
-
apiContent.guideImages = guideImages;
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
apiContent,
|
|
23
|
-
apiCorrectAnswer: {},
|
|
24
|
-
};
|
|
25
|
-
};
|
|
1
|
+
export const transformWriteAShortLetter=r=>{const t=r.answer,i=Array.isArray(t?.informations)?t.informations.filter(r=>"string"==typeof r&&""!==r.trim()):[],n=t?.instructions?.mainInstruction||"",s=Array.isArray(t?.guideImages)?t.guideImages.filter(r=>"string"==typeof r&&""!==r.trim()):[],o={informations:i,minWords:t?.minWords||20,maxWords:t?.maxWords??null,instructions:{mainInstruction:n}};return s.length>0&&(o.guideImages=s),{apiContent:o,apiCorrectAnswer:{}}};
|
package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js
CHANGED
|
@@ -1,61 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useEffect } from 'react';
|
|
4
|
-
import { Input } from '../../../../components/ui/input';
|
|
5
|
-
import { cn } from '../../../../shared/lib/utils';
|
|
6
|
-
import { Check, X, FileText, PenLine, AlertTriangle, Lightbulb } from 'lucide-react';
|
|
7
|
-
export function WriteCorrectVerbFormClient({ questionData, onSubmit, isReviewMode = false, userAnswer = '', }) {
|
|
8
|
-
// ============ STATE ============
|
|
9
|
-
const [answer, setAnswer] = useState(userAnswer || '');
|
|
10
|
-
// Sync userAnswer when it changes
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
if (userAnswer !== undefined) {
|
|
13
|
-
setAnswer(userAnswer);
|
|
14
|
-
}
|
|
15
|
-
}, [userAnswer]);
|
|
16
|
-
// ============ HANDLERS ============
|
|
17
|
-
const handleAnswerChange = (value) => {
|
|
18
|
-
if (isReviewMode)
|
|
19
|
-
return;
|
|
20
|
-
setAnswer(value);
|
|
21
|
-
onSubmit?.(value);
|
|
22
|
-
};
|
|
23
|
-
// Check if answer is correct
|
|
24
|
-
const isAnswerCorrect = (userInput) => {
|
|
25
|
-
const correctAnswer = questionData.correctAnswer.trim();
|
|
26
|
-
const inputAnswer = userInput.trim();
|
|
27
|
-
if (questionData.caseSensitive) {
|
|
28
|
-
return inputAnswer === correctAnswer;
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
return inputAnswer.toLowerCase() === correctAnswer.toLowerCase();
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
const isCorrect = isReviewMode && isAnswerCorrect(answer);
|
|
35
|
-
const isWrong = isReviewMode && answer && !isCorrect;
|
|
36
|
-
const hasAnswered = answer.trim().length > 0;
|
|
37
|
-
// Render sentence with input field at {0} position
|
|
38
|
-
const renderSentenceWithInput = () => {
|
|
39
|
-
const parts = questionData.sentence.split(/(\{0\})/g);
|
|
40
|
-
return (_jsx("div", { className: "flex flex-wrap items-center gap-1.5 text-lg leading-relaxed", children: parts.map((part, index) => {
|
|
41
|
-
if (part === '{0}') {
|
|
42
|
-
return (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsxs("div", { className: "relative inline-flex items-center", children: [_jsx(Input, { value: questionData.correctAnswer, readOnly: true, disabled: true, className: cn('w-36 text-center font-semibold border-2 rounded-lg transition-all', 'border-green-400 bg-green-50 text-green-800 cursor-not-allowed') }), isReviewMode && (_jsx("div", { className: cn('absolute -right-7 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded-full', isCorrect ? 'bg-green-500' : isWrong ? 'bg-red-500' : 'bg-gray-300'), children: isCorrect ? (_jsx(Check, { className: "h-3 w-3 text-white" })) : isWrong ? (_jsx(X, { className: "h-3 w-3 text-white" })) : null }))] }), _jsxs("span", { className: "rounded-lg bg-amber-100 px-2 py-0.5 text-sm font-medium text-amber-700", children: ["(", questionData.verbHint, ")"] })] }, index));
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
return _jsx("span", { className: "text-gray-800", children: part }, index);
|
|
46
|
-
}
|
|
47
|
-
}) }));
|
|
48
|
-
};
|
|
49
|
-
// ============ RENDER ============
|
|
50
|
-
return (_jsxs("div", { className: "space-y-5", children: [questionData.instruction && (_jsx("div", { className: "overflow-hidden rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 shadow-sm", children: _jsxs("div", { className: "flex items-center gap-3 px-5 py-4", children: [_jsx("div", { className: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-blue-500 to-indigo-600 text-white shadow-sm", children: _jsx(FileText, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-blue-900", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx("p", { className: "text-sm text-blue-700", children: questionData.instruction })] })] }) })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: cn('flex items-center justify-between border-b px-5 py-3', isReviewMode && isCorrect && 'border-green-200 bg-gradient-to-r from-green-50 to-emerald-50', isReviewMode && isWrong && 'border-red-200 bg-gradient-to-r from-red-50 to-rose-50', (!isReviewMode || (!isCorrect && !isWrong)) && 'border-gray-100 bg-gradient-to-r from-gray-50 to-white'), children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: cn('flex h-8 w-8 items-center justify-center rounded-lg text-white shadow-sm', isReviewMode && isCorrect && 'bg-gradient-to-br from-green-500 to-emerald-600', isReviewMode && isWrong && 'bg-gradient-to-br from-red-500 to-rose-600', (!isReviewMode || (!isCorrect && !isWrong)) && 'bg-gradient-to-br from-violet-500 to-purple-600'), children: _jsx(PenLine, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Chia \u0111\u1ED9ng t\u1EEB \u0111\u00FAng" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isCorrect ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'), children: [isCorrect ? _jsx(Check, { className: "h-3.5 w-3.5" }) : _jsx(X, { className: "h-3.5 w-3.5" }), isCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "p-6 space-y-5", children: [_jsx("div", { className: "rounded-xl bg-gradient-to-br from-slate-50 to-gray-50 border border-gray-100 p-5", children: renderSentenceWithInput() }), isReviewMode && (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: cn('rounded-xl border-2 p-4', isCorrect
|
|
51
|
-
? 'border-green-200 bg-green-50'
|
|
52
|
-
: isWrong
|
|
53
|
-
? 'border-red-200 bg-red-50'
|
|
54
|
-
: 'border-gray-200 bg-gray-50'), children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-lg', isCorrect ? 'bg-green-200' : isWrong ? 'bg-red-200' : 'bg-gray-200'), children: isCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5 text-green-700" })) : isWrong ? (_jsx(X, { className: "h-3.5 w-3.5 text-red-700" })) : (_jsx("span", { className: "text-xs text-gray-500", children: "?" })) }), _jsx("span", { className: "text-xs font-medium text-gray-600", children: "C\u00E2u tr\u1EA3 l\u1EDDi c\u1EE7a b\u1EA1n" })] }), _jsx("p", { className: cn('text-lg font-bold', isCorrect ? 'text-green-700' : isWrong ? 'text-red-700' : 'text-gray-400'), children: answer || '(Chưa trả lời)' })] }), _jsxs("div", { className: "rounded-xl border-2 border-green-200 bg-gradient-to-br from-green-50 to-emerald-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-lg bg-green-200", children: _jsx(Check, { className: "h-3.5 w-3.5 text-green-700" }) }), _jsx("span", { className: "text-xs font-medium text-gray-600", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsx("p", { className: "text-lg font-bold text-green-700", children: questionData.correctAnswer })] })] }), _jsx("div", { className: cn('rounded-xl p-4', isCorrect
|
|
55
|
-
? 'bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200'
|
|
56
|
-
: 'bg-gradient-to-r from-red-50 to-rose-50 border border-red-200'), children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: cn('flex h-10 w-10 items-center justify-center rounded-xl text-white shadow-sm', isCorrect
|
|
57
|
-
? 'bg-gradient-to-br from-green-500 to-emerald-600'
|
|
58
|
-
: 'bg-gradient-to-br from-red-500 to-rose-600'), children: isCorrect ? _jsx(Check, { className: "h-5 w-5" }) : _jsx(X, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("p", { className: cn('text-sm font-semibold', isCorrect ? 'text-green-800' : 'text-red-800'), children: isCorrect ? 'Chính xác!' : 'Chưa đúng' }), _jsx("p", { className: cn('text-xs', isCorrect ? 'text-green-600' : 'text-red-600'), children: isCorrect
|
|
59
|
-
? 'Bạn đã chia động từ đúng.'
|
|
60
|
-
: `Đáp án đúng là "${questionData.correctAnswer}".` })] })] }) }), questionData.explanation && (_jsx("div", { className: "rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-semibold text-blue-700 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-gray-700 leading-relaxed", children: questionData.explanation })] })] }) }))] })), !isReviewMode && !hasAnswered && (_jsx("div", { className: "rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-amber-400 to-orange-500 text-white shadow-sm", children: _jsx("span", { className: "text-sm font-bold", children: "!" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-amber-800", children: "Nh\u1EADp d\u1EA1ng \u0111\u00FAng c\u1EE7a \u0111\u1ED9ng t\u1EEB" }), _jsx("p", { className: "text-xs text-amber-600", children: "S\u1EED d\u1EE5ng g\u1EE3i \u00FD trong ngo\u1EB7c \u0111\u1EC3 chia \u0111\u1ED9ng t\u1EEB ph\u00F9 h\u1EE3p." })] })] }) }))] })] }), !isReviewMode && questionData.caseSensitive && (_jsx("div", { className: "overflow-hidden rounded-xl border border-yellow-200 bg-gradient-to-r from-yellow-50 to-amber-50 shadow-sm", children: _jsxs("div", { className: "flex items-center gap-3 px-5 py-4", children: [_jsx("div", { className: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-yellow-400 to-amber-500 text-white shadow-sm", children: _jsx(AlertTriangle, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-yellow-900", children: "L\u01B0u \u00FD quan tr\u1ECDng" }), _jsx("p", { className: "text-sm text-yellow-700", children: "\u0110\u00E1p \u00E1n c\u00F3 ph\u00E2n bi\u1EC7t ch\u1EEF hoa/th\u01B0\u1EDDng" })] })] }) }))] }));
|
|
61
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useEffect as s}from"react";import{Input as n}from"../../../../components/ui/input";import{cn as a}from"../../../../shared/lib/utils";import{Check as d,X as l,FileText as i,PenLine as o,AlertTriangle as c,Lightbulb as m}from"lucide-react";export function WriteCorrectVerbFormClient({questionData:g,onSubmit:h,isReviewMode:x=!1,userAnswer:b=""}){const[f,p]=t(b||"");s(()=>{void 0!==b&&p(b)},[b]);const u=x&&(e=>{const r=g.correctAnswer.trim(),t=e.trim();return g.caseSensitive?t===r:t.toLowerCase()===r.toLowerCase()})(f),N=x&&f&&!u,w=f.trim().length>0;return r("div",{className:"space-y-5",children:[g.instruction&&e("div",{className:"overflow-hidden rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 shadow-sm",children:r("div",{className:"flex items-center gap-3 px-5 py-4",children:[e("div",{className:"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-blue-500 to-indigo-600 text-white shadow-sm",children:e(i,{className:"h-5 w-5"})}),r("div",{children:[e("h3",{className:"text-sm font-semibold text-blue-900",children:"Hướng dẫn"}),e("p",{className:"text-sm text-blue-700",children:g.instruction})]})]})}),r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:[r("div",{className:a("flex items-center justify-between border-b px-5 py-3",x&&u&&"border-green-200 bg-gradient-to-r from-green-50 to-emerald-50",x&&N&&"border-red-200 bg-gradient-to-r from-red-50 to-rose-50",(!x||!u&&!N)&&"border-gray-100 bg-gradient-to-r from-gray-50 to-white"),children:[r("div",{className:"flex items-center gap-3",children:[e("div",{className:a("flex h-8 w-8 items-center justify-center rounded-lg text-white shadow-sm",x&&u&&"bg-gradient-to-br from-green-500 to-emerald-600",x&&N&&"bg-gradient-to-br from-red-500 to-rose-600",(!x||!u&&!N)&&"bg-gradient-to-br from-violet-500 to-purple-600"),children:e(o,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Chia động từ đúng"})]}),x&&w&&r("div",{className:a("flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold",u?"bg-green-100 text-green-700":"bg-red-100 text-red-700"),children:[e(u?d:l,{className:"h-3.5 w-3.5"}),u?"Đúng":"Sai"]})]}),r("div",{className:"p-6 space-y-5",children:[e("div",{className:"rounded-xl bg-gradient-to-br from-slate-50 to-gray-50 border border-gray-100 p-5",children:(()=>{const t=g.sentence.split(/(\{0\})/g);return e("div",{className:"flex flex-wrap items-center gap-1.5 text-lg leading-relaxed",children:t.map((t,s)=>"{0}"===t?r("span",{className:"inline-flex items-center gap-2",children:[r("div",{className:"relative inline-flex items-center",children:[e(n,{value:g.correctAnswer,readOnly:!0,disabled:!0,className:a("w-36 text-center font-semibold border-2 rounded-lg transition-all","border-green-400 bg-green-50 text-green-800 cursor-not-allowed")}),x&&e("div",{className:a("absolute -right-7 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded-full",u?"bg-green-500":N?"bg-red-500":"bg-gray-300"),children:u?e(d,{className:"h-3 w-3 text-white"}):N?e(l,{className:"h-3 w-3 text-white"}):null})]}),r("span",{className:"rounded-lg bg-amber-100 px-2 py-0.5 text-sm font-medium text-amber-700",children:["(",g.verbHint,")"]})]},s):e("span",{className:"text-gray-800",children:t},s))})})()}),x&&r("div",{className:"space-y-3",children:[r("div",{className:"grid grid-cols-2 gap-4",children:[r("div",{className:a("rounded-xl border-2 p-4",u?"border-green-200 bg-green-50":N?"border-red-200 bg-red-50":"border-gray-200 bg-gray-50"),children:[r("div",{className:"flex items-center gap-2 mb-2",children:[e("div",{className:a("flex h-6 w-6 items-center justify-center rounded-lg",u?"bg-green-200":N?"bg-red-200":"bg-gray-200"),children:u?e(d,{className:"h-3.5 w-3.5 text-green-700"}):N?e(l,{className:"h-3.5 w-3.5 text-red-700"}):e("span",{className:"text-xs text-gray-500",children:"?"})}),e("span",{className:"text-xs font-medium text-gray-600",children:"Câu trả lời của bạn"})]}),e("p",{className:a("text-lg font-bold",u?"text-green-700":N?"text-red-700":"text-gray-400"),children:f||"(Chưa trả lời)"})]}),r("div",{className:"rounded-xl border-2 border-green-200 bg-gradient-to-br from-green-50 to-emerald-50 p-4",children:[r("div",{className:"flex items-center gap-2 mb-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-lg bg-green-200",children:e(d,{className:"h-3.5 w-3.5 text-green-700"})}),e("span",{className:"text-xs font-medium text-gray-600",children:"Đáp án đúng"})]}),e("p",{className:"text-lg font-bold text-green-700",children:g.correctAnswer})]})]}),e("div",{className:a("rounded-xl p-4",u?"bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200":"bg-gradient-to-r from-red-50 to-rose-50 border border-red-200"),children:r("div",{className:"flex items-center gap-3",children:[e("div",{className:a("flex h-10 w-10 items-center justify-center rounded-xl text-white shadow-sm",u?"bg-gradient-to-br from-green-500 to-emerald-600":"bg-gradient-to-br from-red-500 to-rose-600"),children:e(u?d:l,{className:"h-5 w-5"})}),r("div",{children:[e("p",{className:a("text-sm font-semibold",u?"text-green-800":"text-red-800"),children:u?"Chính xác!":"Chưa đúng"}),e("p",{className:a("text-xs",u?"text-green-600":"text-red-600"),children:u?"Bạn đã chia động từ đúng.":`Đáp án đúng là "${g.correctAnswer}".`})]})]})}),g.explanation&&e("div",{className:"rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200",children:r("div",{className:"flex items-start gap-3",children:[e("div",{className:"flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 text-white",children:e(m,{className:"h-3.5 w-3.5"})}),r("div",{className:"flex-1",children:[e("span",{className:"text-xs font-semibold text-blue-700 uppercase tracking-wider",children:"Giải thích"}),e("p",{className:"mt-1 text-sm text-gray-700 leading-relaxed",children:g.explanation})]})]})})]}),!x&&!w&&e("div",{className:"rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4",children:r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-amber-400 to-orange-500 text-white shadow-sm",children:e("span",{className:"text-sm font-bold",children:"!"})}),r("div",{children:[e("p",{className:"text-sm font-medium text-amber-800",children:"Nhập dạng đúng của động từ"}),e("p",{className:"text-xs text-amber-600",children:"Sử dụng gợi ý trong ngoặc để chia động từ phù hợp."})]})]})})]})]}),!x&&g.caseSensitive&&e("div",{className:"overflow-hidden rounded-xl border border-yellow-200 bg-gradient-to-r from-yellow-50 to-amber-50 shadow-sm",children:r("div",{className:"flex items-center gap-3 px-5 py-4",children:[e("div",{className:"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-yellow-400 to-amber-500 text-white shadow-sm",children:e(c,{className:"h-5 w-5"})}),r("div",{children:[e("h3",{className:"text-sm font-semibold text-yellow-900",children:"Lưu ý quan trọng"}),e("p",{className:"text-sm text-yellow-700",children:"Đáp án có phân biệt chữ hoa/thường"})]})]})})]})}
|