@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/exam-create.js +1 -179
- package/dist/api/exam-create.types.js +1 -1
- package/dist/api/exam-questions.js +1 -98
- package/dist/api/exam-questions.types.js +1 -1
- package/dist/api/exam-taking.js +1 -73
- package/dist/api/exam-taking.types.js +1 -1
- package/dist/api/index.js +1 -6
- package/dist/api/result-review.js +1 -50
- package/dist/api/result-review.types.js +1 -1
- package/dist/components/CueCardEditor.js +1 -58
- package/dist/components/common/ResolvedImage.js +1 -15
- package/dist/components/exams/CreateExamInfoForm.js +1 -35
- package/dist/components/exams/CreateExamPageContainer.js +1 -103
- package/dist/components/exams/ExamCreator.js +1 -89
- package/dist/components/exams/ExamPartTabs.js +1 -40
- package/dist/components/exams/ExamQuestionGrid.js +1 -18
- package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
- package/dist/components/exams/PartAudioUploader.js +1 -9
- package/dist/components/exams/TemplateSelector.js +1 -66
- package/dist/components/exams/api.js +1 -3
- package/dist/components/exams/create-exam.validation.js +1 -18
- package/dist/components/exams/exam-template.utils.js +1 -18
- package/dist/components/exams/index.js +1 -10
- package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
- package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
- package/dist/components/exams/take/exam-taking.utils.js +1 -88
- package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
- package/dist/components/exams/take/index.js +1 -2
- package/dist/components/exams/take/types.js +1 -1
- package/dist/components/index.js +1 -42
- package/dist/components/questions/_shared/config/question-types.config.js +1 -333
- package/dist/components/questions/_shared/constants/color-palette.js +1 -21
- package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
- package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
- package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
- package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
- package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
- package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
- package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
- package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
- package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
- package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
- package/dist/components/questions/_shared/index.js +1 -7
- package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
- package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
- package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
- package/dist/components/questions/_shared/types/color-region.config.js +1 -1
- package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
- package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
- package/dist/components/questions/_shared/types/index.js +1 -12
- package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
- package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
- package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
- package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
- package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
- package/dist/components/questions/_shared/types/question-group.type.js +1 -11
- package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
- package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
- package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
- package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
- package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
- package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
- package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
- package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
- package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
- package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
- package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
- package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
- package/dist/components/questions/_shared/utils/id-generator.js +1 -3
- package/dist/components/questions/_shared/utils/question-validation.js +1 -21
- package/dist/components/questions/components/ColorNode.js +1 -85
- package/dist/components/questions/components/ColorRegionClient.js +1 -545
- package/dist/components/questions/components/ColorRegionCreator.js +1 -420
- package/dist/components/questions/components/ColorRegionNode.js +1 -59
- package/dist/components/questions/components/CreatorGuide.js +1 -103
- package/dist/components/questions/components/NameNode.js +1 -26
- package/dist/components/questions/components/QuestionBankOption.js +1 -46
- package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
- package/dist/components/questions/components/QuestionCommonFields.js +1 -33
- package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
- package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
- package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
- package/dist/components/questions/components/RegionNode.js +1 -94
- package/dist/components/questions/components/index.js +1 -14
- package/dist/components/questions/creator/QuestionCreator.js +1 -90
- package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
- package/dist/components/questions/creator/dedicated-component-router.js +1 -79
- package/dist/components/questions/creator/index.js +1 -3
- package/dist/components/questions/creator/question-type-registry.js +1 -80
- package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
- package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
- package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
- package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
- package/dist/components/questions/index.js +1 -10
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
- package/dist/components/questions/types/answer-the-question/register.js +1 -67
- package/dist/components/questions/types/answer-the-question/transform.js +1 -36
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
- package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
- package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
- package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
- package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
- package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
- package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
- package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
- package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
- package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
- package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
- package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
- package/dist/components/questions/types/fill-in-blank/index.js +1 -2
- package/dist/components/questions/types/fill-in-blank/register.js +1 -138
- package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
- package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
- package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
- package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
- package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
- package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
- package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
- package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
- package/dist/components/questions/types/image-object-matching/index.js +1 -2
- package/dist/components/questions/types/image-object-matching/register.js +1 -3
- package/dist/components/questions/types/image-object-matching/transform.js +1 -2
- package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
- package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
- package/dist/components/questions/types/label-the-picture/index.js +1 -2
- package/dist/components/questions/types/label-the-picture/register.js +1 -54
- package/dist/components/questions/types/label-the-picture/transform.js +1 -28
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
- package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
- package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
- package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
- package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
- package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
- package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
- package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
- package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
- package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
- package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
- package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
- package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
- package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
- package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
- package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
- package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
- package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
- package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
- package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
- package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
- package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
- package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
- package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
- package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
- package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
- package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
- package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
- package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
- package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
- package/dist/components/questions/types/read-displayed-content/register.js +1 -64
- package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
- package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
- package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
- package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
- package/dist/components/questions/types/speaking-conversation/register.js +1 -110
- package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
- package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
- package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
- package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
- package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
- package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
- package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
- package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
- package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
- package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
- package/dist/components/questions/types/true-false/index.js +1 -2
- package/dist/components/questions/types/true-false/register.js +1 -77
- package/dist/components/questions/types/true-false/transform.js +1 -32
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
- package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
- package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
- package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
- package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
- package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
- package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
- package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
- package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
- package/dist/components/questions/types/word-ordering/index.js +1 -2
- package/dist/components/questions/types/word-ordering/register.js +1 -46
- package/dist/components/questions/types/word-ordering/transform.js +1 -24
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
- package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
- package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
- package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
- package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
- package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
- package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
- package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
- package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
- package/dist/components/questions/types/write-sentences/index.js +1 -2
- package/dist/components/questions/types/write-sentences/register.js +1 -58
- package/dist/components/questions/types/write-sentences/transform.js +1 -28
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
- package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
- package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
- package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
- package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
- package/dist/components/questions/viewer/QuestionViewer.js +1 -446
- package/dist/components/questions/viewer/index.js +1 -2
- package/dist/components/results/ResultReviewPageContainer.js +1 -98
- package/dist/components/results/ReviewQuestionRenderer.js +1 -72
- package/dist/components/results/index.js +1 -2
- package/dist/components/results/result-review.utils.js +1 -6
- package/dist/components/results/review-data.js +1 -150
- package/dist/components/results/review-renderer.utils.js +1 -97
- package/dist/components/results/review-types.js +1 -1
- package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
- package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
- package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
- package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
- package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
- package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
- package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
- package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
- package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
- package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
- package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
- package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
- package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
- package/dist/components/themes/cambridge-yle/index.js +1 -13
- package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
- package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
- package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
- package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
- package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
- package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
- package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
- package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
- package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
- package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
- package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
- package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
- package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
- package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
- package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
- package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
- package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
- package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
- package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
- package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
- package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
- package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
- package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
- package/dist/components/themes/english-certification/index.js +1 -21
- package/dist/components/themes/index.js +1 -6
- package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
- package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
- package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
- package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
- package/dist/components/themes/types.js +1 -1
- package/dist/components/ui/alert-dialog.js +1 -26
- package/dist/components/ui/alert.js +1 -22
- package/dist/components/ui/avatar.js +1 -12
- package/dist/components/ui/badge.js +1 -38
- package/dist/components/ui/button.js +1 -35
- package/dist/components/ui/calendar.js +1 -76
- package/dist/components/ui/card.js +1 -16
- package/dist/components/ui/carousel.js +1 -91
- package/dist/components/ui/checkbox.js +1 -9
- package/dist/components/ui/collapsible.js +1 -13
- package/dist/components/ui/command.js +1 -34
- package/dist/components/ui/confirm-dialog.js +1 -49
- package/dist/components/ui/dialog.js +1 -23
- package/dist/components/ui/drawer.js +1 -35
- package/dist/components/ui/dropdown-menu.js +1 -33
- package/dist/components/ui/file-upload.js +1 -155
- package/dist/components/ui/form.js +1 -62
- package/dist/components/ui/hover-card.js +1 -10
- package/dist/components/ui/image-preview.js +1 -29
- package/dist/components/ui/input.js +1 -8
- package/dist/components/ui/label.js +1 -9
- package/dist/components/ui/language-switcher.js +1 -15
- package/dist/components/ui/multi-select.js +1 -90
- package/dist/components/ui/points-input.js +1 -33
- package/dist/components/ui/popover.js +1 -26
- package/dist/components/ui/progress.js +1 -16
- package/dist/components/ui/radio-group.js +1 -12
- package/dist/components/ui/scroll-area.js +1 -11
- package/dist/components/ui/select.js +1 -38
- package/dist/components/ui/separator.js +1 -8
- package/dist/components/ui/sheet.js +1 -40
- package/dist/components/ui/single-select.js +1 -76
- package/dist/components/ui/skeleton.js +1 -6
- package/dist/components/ui/switch.js +1 -8
- package/dist/components/ui/table.js +1 -20
- package/dist/components/ui/tabs.js +1 -17
- package/dist/components/ui/textarea.js +1 -8
- package/dist/components/ui/tooltip.js +1 -11
- package/dist/index.js +1 -19
- package/dist/mcp/config.js +1 -32
- package/dist/mcp/http-client.js +1 -48
- package/dist/mcp/index.js +1 -29
- package/dist/mcp/tools/_helpers.js +1 -25
- package/dist/mcp/tools/exams.js +1 -439
- package/dist/mcp/tools/index.js +1 -6
- package/dist/mcp/tools/results.js +1 -19
- package/dist/shared/constants/ApiConstant.js +1 -276
- package/dist/shared/constants/QueryKeyConstant.js +1 -112
- package/dist/shared/constants/exam-level.enum.js +1 -41
- package/dist/shared/constants/landings/placement-test.constants.js +1 -431
- package/dist/shared/constants/question-skills.js +1 -267
- package/dist/shared/constants/routes.js +1 -81
- package/dist/shared/index.js +1 -7
- package/dist/shared/lib/hooks/index.js +1 -10
- package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
- package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
- package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
- package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
- package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
- package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
- package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
- package/dist/shared/lib/hooks/useFileUpload.js +1 -24
- package/dist/shared/lib/hooks/useImageUrl.js +1 -5
- package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
- package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
- package/dist/shared/lib/hooks/useToast.js +1 -62
- package/dist/shared/lib/i18n/index.js +1 -54
- package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
- package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
- package/dist/shared/lib/i18n/messages/en/common.js +1 -140
- package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
- package/dist/shared/lib/i18n/messages/en/student.js +1 -133
- package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
- package/dist/shared/lib/i18n/messages/en.js +1 -15
- package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
- package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
- package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
- package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
- package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
- package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
- package/dist/shared/lib/i18n/messages/vi.js +1 -15
- package/dist/shared/lib/i18n/types.js +1 -1
- package/dist/shared/lib/stores/examQuestionStore.js +1 -212
- package/dist/shared/lib/stores/localeStore.js +1 -10
- package/dist/shared/lib/utils/public-page.js +1 -44
- package/dist/shared/lib/utils/question-transform-types.js +1 -1
- package/dist/shared/lib/utils/question-transform.js +1 -174
- package/dist/shared/lib/utils.js +1 -168
- package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
- package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
- package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
- package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
- package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
- package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
- package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
- package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
- package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
- package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
- package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
- package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
- package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
- package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
- package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
- package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
- package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
- package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
- package/dist/shared/types/branch.types.js +1 -6
- package/dist/shared/types/common.types.js +1 -50
- package/dist/shared/types/entities/exam-schedule.types.js +1 -5
- package/dist/shared/types/entities/exam.types.js +1 -1
- package/dist/shared/types/entities/question.types.js +1 -4
- package/dist/shared/types/entities/result.types.js +1 -4
- package/dist/shared/types/entities/user.types.js +1 -4
- package/dist/shared/types/exam-taking.types.js +1 -2
- package/dist/shared/types/history.types.js +1 -34
- package/dist/shared/types/index.js +1 -1
- package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
- package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
- package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
- package/dist/shared/types/questions/index.js +1 -40
- package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
- package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
- package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
- package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
- package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
- package/dist/shared/types/questions/read-displayed-content.js +1 -1
- package/dist/shared/types/questions/speaking-conversation.js +1 -7
- package/dist/shared/types/questions/speaking-describe-image.js +1 -1
- package/dist/shared/types/questions/spontaneous-qa.js +1 -1
- package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
- package/dist/shared/types/role.types.js +1 -1
- package/dist/shared/types/structure-management.js +1 -28
- package/dist/shared/types/student.types.js +1 -54
- package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
- package/package.json +4 -2
|
@@ -1,192 +1 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { ImagePreview } from '../../../../components/ui/image-preview';
|
|
4
|
-
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
5
|
-
import { Button } from '../../../../components/ui/button';
|
|
6
|
-
import { Label } from '../../../../components/ui/label';
|
|
7
|
-
import { Input } from '../../../../components/ui/input';
|
|
8
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
9
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
10
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
11
|
-
import { Checkbox } from '../../../../components/ui/checkbox';
|
|
12
|
-
import { usePresignedFileUrl, useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
13
|
-
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
14
|
-
import { Pencil, Eye, Plus, Trash2, ArrowUpDown, GripVertical, Shuffle, ImageIcon, BookOpen, Lightbulb } from 'lucide-react';
|
|
15
|
-
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
16
|
-
import { WordOrderingClient } from './WordOrderingClient';
|
|
17
|
-
import { wordOrderingFormSchema, } from '../../../../shared/lib/validation/word-ordering.validation';
|
|
18
|
-
function WordOrderingCreatorContent({ initialData, onSave, onCancel, onChange, externalErrors, onUnsavedChangesChange, validationRef, headerExtra, }) {
|
|
19
|
-
const apiData = initialData;
|
|
20
|
-
const form = useReactHookForm({
|
|
21
|
-
defaultValues: {
|
|
22
|
-
words: apiData?.words?.length > 0 ? apiData.words : ['', ''],
|
|
23
|
-
answer: apiData?.answer?.length > 0 ? apiData.answer : ['', ''],
|
|
24
|
-
imageUrl: apiData?.imageUrl || '',
|
|
25
|
-
isExample: apiData?.isExample || false,
|
|
26
|
-
explanation: apiData?.explanation || '',
|
|
27
|
-
points: apiData?.points ?? 1,
|
|
28
|
-
},
|
|
29
|
-
zodSchema: wordOrderingFormSchema,
|
|
30
|
-
mode: 'onBlur',
|
|
31
|
-
});
|
|
32
|
-
// UI State
|
|
33
|
-
const [isClientMode, setIsClientMode] = useState(false);
|
|
34
|
-
const [imagePreviewUrl, setImagePreviewUrl] = useState('');
|
|
35
|
-
// Refs
|
|
36
|
-
const previousInitialDataRef = useRef(initialData);
|
|
37
|
-
const isInitializingRef = useRef(false);
|
|
38
|
-
const onChangeRef = useRef(onChange);
|
|
39
|
-
const previousPayloadRef = useRef('');
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
onChangeRef.current = onChange;
|
|
42
|
-
}, [onChange]);
|
|
43
|
-
const debouncedOnChange = useDebouncedCallback((data) => {
|
|
44
|
-
onChangeRef.current?.(data);
|
|
45
|
-
}, 300);
|
|
46
|
-
// Watch form values
|
|
47
|
-
const watchedWords = form.watch('words');
|
|
48
|
-
const watchedAnswer = form.watch('answer');
|
|
49
|
-
const watchedImageUrl = form.watch('imageUrl');
|
|
50
|
-
const watchedIsExample = form.watch('isExample');
|
|
51
|
-
const watchedExplanation = form.watch('explanation');
|
|
52
|
-
const watchedPoints = form.watch('points');
|
|
53
|
-
// Pre-signed URL for image
|
|
54
|
-
const { previewUrl: generatedPreviewUrl } = usePresignedFileUrl(watchedImageUrl || '');
|
|
55
|
-
const displayPreviewUrl = imagePreviewUrl || generatedPreviewUrl || '';
|
|
56
|
-
// Expose validation via ref
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
if (validationRef) {
|
|
59
|
-
validationRef.current = {
|
|
60
|
-
triggerValidation: async () => {
|
|
61
|
-
const result = await form.trigger();
|
|
62
|
-
return result;
|
|
63
|
-
},
|
|
64
|
-
getFormData: () => ({
|
|
65
|
-
words: watchedWords,
|
|
66
|
-
answer: watchedAnswer,
|
|
67
|
-
imageUrl: watchedImageUrl,
|
|
68
|
-
isExample: watchedIsExample,
|
|
69
|
-
explanation: watchedExplanation,
|
|
70
|
-
points: watchedPoints,
|
|
71
|
-
}),
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
}, [validationRef, form, watchedWords, watchedAnswer, watchedImageUrl, watchedIsExample, watchedExplanation, watchedPoints]);
|
|
75
|
-
// Sync from initialData changes
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
const apiData = initialData;
|
|
78
|
-
const normalizedInitialData = {
|
|
79
|
-
words: apiData?.words?.length > 0 ? apiData.words : ['', ''],
|
|
80
|
-
answer: apiData?.answer?.length > 0 ? apiData.answer : ['', ''],
|
|
81
|
-
imageUrl: apiData?.imageUrl || '',
|
|
82
|
-
isExample: apiData?.isExample || false,
|
|
83
|
-
explanation: apiData?.explanation || '',
|
|
84
|
-
points: apiData?.points ?? 1,
|
|
85
|
-
};
|
|
86
|
-
const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
|
|
87
|
-
if (!initialDataChanged)
|
|
88
|
-
return;
|
|
89
|
-
previousInitialDataRef.current = initialData;
|
|
90
|
-
const currentFormData = {
|
|
91
|
-
words: watchedWords,
|
|
92
|
-
answer: watchedAnswer,
|
|
93
|
-
imageUrl: watchedImageUrl,
|
|
94
|
-
isExample: watchedIsExample,
|
|
95
|
-
explanation: watchedExplanation,
|
|
96
|
-
points: watchedPoints,
|
|
97
|
-
};
|
|
98
|
-
const isEchoFromOnChange = JSON.stringify(currentFormData.words) === JSON.stringify(normalizedInitialData.words) &&
|
|
99
|
-
JSON.stringify(currentFormData.answer) === JSON.stringify(normalizedInitialData.answer) &&
|
|
100
|
-
currentFormData.imageUrl === normalizedInitialData.imageUrl &&
|
|
101
|
-
currentFormData.points === normalizedInitialData.points;
|
|
102
|
-
const wasJustSaved = initialData?.isCompleted === true;
|
|
103
|
-
if (isEchoFromOnChange && wasJustSaved)
|
|
104
|
-
return;
|
|
105
|
-
const shouldReset = !isEchoFromOnChange &&
|
|
106
|
-
(JSON.stringify(normalizedInitialData.words) !== JSON.stringify(currentFormData.words) ||
|
|
107
|
-
JSON.stringify(normalizedInitialData.answer) !== JSON.stringify(currentFormData.answer) ||
|
|
108
|
-
normalizedInitialData.imageUrl !== currentFormData.imageUrl);
|
|
109
|
-
if (shouldReset && normalizedInitialData.words.length > 0) {
|
|
110
|
-
isInitializingRef.current = true;
|
|
111
|
-
form.reset(normalizedInitialData);
|
|
112
|
-
setTimeout(() => {
|
|
113
|
-
isInitializingRef.current = false;
|
|
114
|
-
}, 150);
|
|
115
|
-
}
|
|
116
|
-
}, [initialData, form, watchedWords, watchedAnswer, watchedImageUrl, watchedIsExample, watchedExplanation, watchedPoints]);
|
|
117
|
-
// onChange effect
|
|
118
|
-
const watchedWordsStr = JSON.stringify(watchedWords);
|
|
119
|
-
const watchedAnswerStr = JSON.stringify(watchedAnswer);
|
|
120
|
-
useEffect(() => {
|
|
121
|
-
if (isInitializingRef.current) {
|
|
122
|
-
const currentPayload = JSON.stringify({
|
|
123
|
-
words: watchedWords, answer: watchedAnswer, imageUrl: watchedImageUrl,
|
|
124
|
-
isExample: watchedIsExample, explanation: watchedExplanation, points: watchedPoints,
|
|
125
|
-
});
|
|
126
|
-
previousPayloadRef.current = currentPayload;
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
const payload = {
|
|
130
|
-
words: watchedWords,
|
|
131
|
-
answer: watchedAnswer,
|
|
132
|
-
imageUrl: watchedImageUrl,
|
|
133
|
-
isExample: watchedIsExample,
|
|
134
|
-
explanation: watchedExplanation,
|
|
135
|
-
points: watchedPoints,
|
|
136
|
-
};
|
|
137
|
-
const payloadString = JSON.stringify(payload);
|
|
138
|
-
if (previousPayloadRef.current !== payloadString) {
|
|
139
|
-
previousPayloadRef.current = payloadString;
|
|
140
|
-
debouncedOnChange(payload);
|
|
141
|
-
}
|
|
142
|
-
}, [watchedWordsStr, watchedAnswerStr, watchedImageUrl, watchedIsExample, watchedExplanation, watchedPoints]);
|
|
143
|
-
// Handlers
|
|
144
|
-
const addWord = useCallback(() => {
|
|
145
|
-
const newWords = [...form.getValues('words'), ''];
|
|
146
|
-
const newAnswer = [...form.getValues('answer'), ''];
|
|
147
|
-
form.setValue('words', newWords);
|
|
148
|
-
form.setValue('answer', newAnswer);
|
|
149
|
-
}, [form]);
|
|
150
|
-
const removeWord = useCallback((index) => {
|
|
151
|
-
const currentWords = form.getValues('words');
|
|
152
|
-
const currentAnswer = form.getValues('answer');
|
|
153
|
-
if (currentWords.length <= 2)
|
|
154
|
-
return;
|
|
155
|
-
form.setValue('words', currentWords.filter((_, i) => i !== index), { shouldValidate: true });
|
|
156
|
-
form.setValue('answer', currentAnswer.filter((_, i) => i !== index), { shouldValidate: true });
|
|
157
|
-
}, [form]);
|
|
158
|
-
const shuffleWords = useCallback(() => {
|
|
159
|
-
const answer = form.getValues('answer');
|
|
160
|
-
const shuffled = [...answer].sort(() => Math.random() - 0.5);
|
|
161
|
-
form.setValue('words', shuffled, { shouldValidate: true });
|
|
162
|
-
}, [form]);
|
|
163
|
-
const handleSave = form.handleSubmit((data) => {
|
|
164
|
-
onSave?.(data);
|
|
165
|
-
});
|
|
166
|
-
// Client mode preview
|
|
167
|
-
if (isClientMode) {
|
|
168
|
-
const questionData = {
|
|
169
|
-
words: watchedWords,
|
|
170
|
-
answer: watchedAnswer,
|
|
171
|
-
imageUrl: displayPreviewUrl || watchedImageUrl,
|
|
172
|
-
isExample: watchedIsExample,
|
|
173
|
-
explanation: watchedExplanation,
|
|
174
|
-
};
|
|
175
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "S\u1EAFp x\u1EBFp t\u1EEB (Xem tr\u01B0\u1EDBc)" }), _jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Ch\u1EBF \u0111\u1ED9 xem tr\u01B0\u1EDBc - Hi\u1EC7n \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 ch\u1EC9nh s\u1EEDa"] })] }) }) }), _jsx(WordOrderingClient, { questionData: questionData, isReviewMode: true, userAnswer: watchedAnswer })] }));
|
|
176
|
-
}
|
|
177
|
-
// Creator UI
|
|
178
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-blue-600 shadow-sm", children: _jsx(ArrowUpDown, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-base font-bold text-gray-900", children: "S\u1EAFp x\u1EBFp t\u1EEB" }), _jsx("p", { className: "text-xs text-gray-500", children: "S\u1EAFp x\u1EBFp c\u00E1c ch\u1EEF c\u00E1i / t\u1EEB th\u00E0nh t\u1EEB / c\u00E2u \u0111\u00FAng" })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [headerExtra, _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: watchedWords.filter((w) => w.trim()).length < 2, className: "gap-2 border-amber-200 text-amber-600 hover:bg-amber-50 hover:text-amber-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Xem tr\u01B0\u1EDBc"] })] })] }) }), _jsxs(CardContent, { className: "space-y-4 px-4 pb-4", children: [_jsx("div", { className: "rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-5 shadow-sm", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-blue-100", children: _jsx(Lightbulb, { className: "h-4 w-4 text-blue-600" }) }), _jsxs("div", { className: "text-sm text-blue-900", children: [_jsx("p", { className: "font-semibold text-blue-800", children: "H\u01B0\u1EDBng d\u1EABn t\u1EA1o c\u00E2u h\u1ECFi" }), _jsxs("ul", { className: "mt-2 space-y-1.5 text-blue-700", children: [_jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), "Nh\u1EADp ", _jsx("strong", { children: "\u0111\u00E1p \u00E1n \u0111\u00FAng" }), " theo th\u1EE9 t\u1EF1 ch\u00EDnh x\u00E1c (ph\u00EDa d\u01B0\u1EDBi)"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), "Nh\u1EA5n ", _jsx("strong", { children: "\"X\u00E1o tr\u1ED9n \u2192 T\u1EEB\"" }), " \u0111\u1EC3 t\u1EF1 \u0111\u1ED9ng t\u1EA1o danh s\u00E1ch t\u1EEB hi\u1EC3n th\u1ECB 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-blue-400" }), "Upload ", _jsx("strong", { children: "h\u00ECnh \u1EA3nh" }), " minh ho\u1EA1 (n\u1EBFu c\u00F3)"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), "Nh\u1EADp ", _jsx("strong", { children: "gi\u1EA3i th\u00EDch" }), " cho \u0111\u00E1p \u00E1n \u0111\u00FAng (tu\u1EF3 ch\u1ECDn)"] })] })] })] }) }), _jsxs("div", { className: "flex items-center gap-6", children: [_jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), error: form.hasError('points') ? form.getFieldError('points') : undefined }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: "isExample", checked: watchedIsExample || false, onCheckedChange: (checked) => form.setValue('isExample', !!checked) }), _jsx(Label, { htmlFor: "isExample", className: "flex items-center gap-1.5 cursor-pointer text-sm font-medium text-gray-700", children: "\u0110\u00E1nh d\u1EA5u l\u00E0 c\u00E2u h\u1ECFi Example" })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-amber-500" }), "H\u00ECnh \u1EA3nh"] }), _jsx(FileUpload, { id: "word-ordering-image", label: "", accept: "image/*", value: watchedImageUrl || '', onChange: (url) => {
|
|
179
|
-
form.setValue('imageUrl', url, { shouldValidate: true });
|
|
180
|
-
}, onPresignedUrlChange: setImagePreviewUrl, maxSize: 5, placeholder: "Upload h\u00ECnh \u1EA3nh ho\u1EB7c paste URL", autoUpload: true, prefix: "questions" }), displayPreviewUrl && (_jsx(ImagePreview, { src: displayPreviewUrl, alt: "Preview", className: "mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200" }))] })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-green-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-green-100", children: _jsx(ArrowUpDown, { className: "h-3.5 w-3.5 text-green-600" }) }), _jsxs(CardTitle, { className: "text-base font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng (", watchedAnswer.length, " t\u1EEB/ch\u1EEF)"] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: shuffleWords, disabled: watchedAnswer.filter((a) => a.trim()).length < 2, className: "gap-2 border-amber-200 text-amber-600 hover:bg-amber-50", title: "X\u00E1o tr\u1ED9n th\u1EE9 t\u1EF1 \u0111\u00E1p \u00E1n \u0111\u1EC3 t\u1EA1o danh s\u00E1ch t\u1EEB", children: [_jsx(Shuffle, { className: "h-4 w-4" }), "X\u00E1o tr\u1ED9n \u2192 T\u1EEB"] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: addWord, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm"] })] })] }) }), _jsxs(CardContent, { className: "space-y-3 px-4 pb-4", children: [_jsxs("p", { className: "text-xs text-gray-500", children: ["Nh\u1EADp c\u00E1c t\u1EEB/ch\u1EEF c\u00E1i theo ", _jsx("strong", { children: "th\u1EE9 t\u1EF1 \u0111\u00FAng" }), ". Nh\u1EA5n \"X\u00E1o tr\u1ED9n \u2192 T\u1EEB\" \u0111\u1EC3 t\u1EA1o danh s\u00E1ch t\u1EEB x\u00E1o tr\u1ED9n t\u1EF1 \u0111\u1ED9ng."] }), watchedAnswer.map((word, index) => (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 text-white text-sm font-bold shadow-sm", children: index + 1 }), _jsx("div", { className: "flex-1", children: _jsx(Input, { value: word, onChange: (e) => {
|
|
181
|
-
const newAnswer = [...watchedAnswer];
|
|
182
|
-
newAnswer[index] = e.target.value;
|
|
183
|
-
form.setValue('answer', newAnswer, { shouldValidate: true });
|
|
184
|
-
}, placeholder: `Từ/chữ cái thứ ${index + 1}`, className: "border-gray-200" }) }), watchedAnswer.length > 2 && (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => removeWord(index), className: "text-red-500 hover:text-red-700 hover:bg-red-50", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }, index))), form.formState.errors.answer?.message && (_jsx("p", { className: "text-sm text-red-600", children: form.formState.errors.answer.message }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-orange-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-orange-100", children: _jsx(GripVertical, { className: "h-3.5 w-3.5 text-orange-600" }) }), _jsxs(CardTitle, { className: "text-base font-semibold text-gray-800", children: ["T\u1EEB hi\u1EC3n th\u1ECB (x\u00E1o tr\u1ED9n) (", watchedWords.length, " t\u1EEB/ch\u1EEF)"] })] }) }), _jsxs(CardContent, { className: "space-y-3 px-4 pb-4", children: [_jsx("p", { className: "text-xs text-gray-500", children: "Danh s\u00E1ch t\u1EEB/ch\u1EEF c\u00E1i hi\u1EC3n th\u1ECB cho h\u1ECDc sinh (\u0111\u00E3 x\u00E1o tr\u1ED9n). C\u00F3 th\u1EC3 ch\u1EC9nh s\u1EEDa tr\u1EF1c ti\u1EBFp." }), watchedWords.map((word, index) => (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-orange-400 to-amber-500 text-white text-sm font-bold shadow-sm", children: index + 1 }), _jsx("div", { className: "flex-1", children: _jsx(Input, { value: word, onChange: (e) => {
|
|
185
|
-
const newWords = [...watchedWords];
|
|
186
|
-
newWords[index] = e.target.value;
|
|
187
|
-
form.setValue('words', newWords, { shouldValidate: true });
|
|
188
|
-
}, placeholder: `Từ/chữ cái thứ ${index + 1}`, className: "border-gray-200" }) })] }, index))), form.formState.errors.words?.message && (_jsx("p", { className: "text-sm text-red-600", children: form.formState.errors.words.message }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-3.5 w-3.5 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-base font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch cho \u0111\u00E1p \u00E1n \u0111\u00FAng \u2014 hi\u1EC3n th\u1ECB sau khi h\u1ECDc sinh ho\u00E0n th\u00E0nh c\u00E2u h\u1ECFi" })] })] }) }), _jsx(CardContent, { className: "px-4 pb-4", children: _jsx(Textarea, { ...form.register('explanation'), placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0... v\u00EC...", rows: 3, className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none" }) })] })] }));
|
|
189
|
-
}
|
|
190
|
-
export function WordOrderingCreator(props) {
|
|
191
|
-
return _jsx(WordOrderingCreatorContent, { ...props });
|
|
192
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{ImagePreview as t}from"../../../../components/ui/image-preview";import{useState as a,useRef as s,useEffect as n,useCallback as i}from"react";import{Button as l}from"../../../../components/ui/button";import{Label as o}from"../../../../components/ui/label";import{Input as c}from"../../../../components/ui/input";import{Textarea as d}from"../../../../components/ui/textarea";import{PointsInput as m}from"../../../../components/ui/points-input";import{Card as h,CardContent as g,CardHeader as p,CardTitle as x}from"../../../../components/ui/card";import{Checkbox as u}from"../../../../components/ui/checkbox";import{usePresignedFileUrl as f,useDebouncedCallback as w}from"../../../../shared/lib/hooks";import{useReactHookForm as b}from"../../../../shared/lib/hooks/useReactHookForm";import{Pencil as N,Eye as v,Plus as y,Trash2 as S,ArrowUpDown as V,GripVertical as U,Shuffle as C,ImageIcon as E,BookOpen as O,Lightbulb as k}from"lucide-react";import{FileUpload as J}from"../../../../components/ui/file-upload";import{WordOrderingClient as j}from"./WordOrderingClient";import{wordOrderingFormSchema as z}from"../../../../shared/lib/validation/word-ordering.validation";function T({initialData:T,onSave:X,onCancel:D,onChange:F,externalErrors:H,onUnsavedChangesChange:R,validationRef:q,headerExtra:A}){const G=T,M=b({defaultValues:{words:G?.words?.length>0?G.words:["",""],answer:G?.answer?.length>0?G.answer:["",""],imageUrl:G?.imageUrl||"",isExample:G?.isExample||!1,explanation:G?.explanation||"",points:G?.points??1},zodSchema:z,mode:"onBlur"}),[P,W]=a(!1),[$,B]=a(""),K=s(T),L=s(!1),Q=s(F),I=s("");n(()=>{Q.current=F},[F]);const Y=w(e=>{Q.current?.(e)},300),Z=M.watch("words"),_=M.watch("answer"),ee=M.watch("imageUrl"),re=M.watch("isExample"),te=M.watch("explanation"),ae=M.watch("points"),{previewUrl:se}=f(ee||""),ne=$||se||"";n(()=>{q&&(q.current={triggerValidation:async()=>await M.trigger(),getFormData:()=>({words:Z,answer:_,imageUrl:ee,isExample:re,explanation:te,points:ae})})},[q,M,Z,_,ee,re,te,ae]),n(()=>{const e=T,r={words:e?.words?.length>0?e.words:["",""],answer:e?.answer?.length>0?e.answer:["",""],imageUrl:e?.imageUrl||"",isExample:e?.isExample||!1,explanation:e?.explanation||"",points:e?.points??1};if(!(JSON.stringify(K.current)!==JSON.stringify(T)))return;K.current=T;const t={words:Z,answer:_,imageUrl:ee,isExample:re,explanation:te,points:ae},a=JSON.stringify(t.words)===JSON.stringify(r.words)&&JSON.stringify(t.answer)===JSON.stringify(r.answer)&&t.imageUrl===r.imageUrl&&t.points===r.points;if(a&&!0===T?.isCompleted)return;!a&&(JSON.stringify(r.words)!==JSON.stringify(t.words)||JSON.stringify(r.answer)!==JSON.stringify(t.answer)||r.imageUrl!==t.imageUrl)&&r.words.length>0&&(L.current=!0,M.reset(r),setTimeout(()=>{L.current=!1},150))},[T,M,Z,_,ee,re,te,ae]);const ie=JSON.stringify(Z),le=JSON.stringify(_);n(()=>{if(L.current){const e=JSON.stringify({words:Z,answer:_,imageUrl:ee,isExample:re,explanation:te,points:ae});return void(I.current=e)}const e={words:Z,answer:_,imageUrl:ee,isExample:re,explanation:te,points:ae},r=JSON.stringify(e);I.current!==r&&(I.current=r,Y(e))},[ie,le,ee,re,te,ae]);const oe=i(()=>{const e=[...M.getValues("words"),""],r=[...M.getValues("answer"),""];M.setValue("words",e),M.setValue("answer",r)},[M]),ce=i(e=>{const r=M.getValues("words"),t=M.getValues("answer");r.length<=2||(M.setValue("words",r.filter((r,t)=>t!==e),{shouldValidate:!0}),M.setValue("answer",t.filter((r,t)=>t!==e),{shouldValidate:!0}))},[M]),de=i(()=>{const e=[...M.getValues("answer")].sort(()=>Math.random()-.5);M.setValue("words",e,{shouldValidate:!0})},[M]);M.handleSubmit(e=>{X?.(e)});if(P){const t={words:Z,answer:_,imageUrl:ne||ee,isExample:re,explanation:te};return r("div",{className:"space-y-4",children:[e(h,{children:e(p,{children:r("div",{className:"flex items-center justify-between",children:[r("div",{children:[e(x,{children:"Sắp xếp từ (Xem trước)"}),e("p",{className:"mt-1 text-sm text-gray-500",children:"Chế độ xem trước - Hiện đáp án đúng"})]}),r(l,{variant:"outline",size:"sm",onClick:()=>W(!1),children:[e(N,{className:"mr-2 h-4 w-4"}),"Quay lại chỉnh sửa"]})]})})}),e(j,{questionData:t,isReviewMode:!0,userAnswer:_})]})}return r("div",{className:"space-y-4",children:[r(h,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50",children:[e(p,{className:"px-4 py-3",children:r("div",{className:"flex items-center justify-between",children:[r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-blue-600 shadow-sm",children:e(V,{className:"h-4 w-4 text-white"})}),r("div",{children:[e(x,{className:"text-base font-bold text-gray-900",children:"Sắp xếp từ"}),e("p",{className:"text-xs text-gray-500",children:"Sắp xếp các chữ cái / từ thành từ / câu đúng"})]})]}),r("div",{className:"flex items-center gap-2",children:[A,r(l,{variant:"outline",size:"sm",onClick:()=>W(!0),disabled:Z.filter(e=>e.trim()).length<2,className:"gap-2 border-amber-200 text-amber-600 hover:bg-amber-50 hover:text-amber-700",children:[e(v,{className:"h-4 w-4"}),"Xem trước"]})]})]})}),r(g,{className:"space-y-4 px-4 pb-4",children:[e("div",{className:"rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-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-blue-100",children:e(k,{className:"h-4 w-4 text-blue-600"})}),r("div",{className:"text-sm text-blue-900",children:[e("p",{className:"font-semibold text-blue-800",children:"Hướng dẫn tạo câu hỏi"}),r("ul",{className:"mt-2 space-y-1.5 text-blue-700",children:[r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-blue-400"}),"Nhập ",e("strong",{children:"đáp án đúng"})," theo thứ tự chính xác (phía dưới)"]}),r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-blue-400"}),"Nhấn ",e("strong",{children:'"Xáo trộn → Từ"'})," để tự động tạo danh sách từ hiển 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-blue-400"}),"Upload ",e("strong",{children:"hình ảnh"})," minh hoạ (nếu có)"]}),r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-blue-400"}),"Nhập ",e("strong",{children:"giải thích"})," cho đáp án đúng (tuỳ chọn)"]})]})]})]})}),r("div",{className:"flex items-center gap-6",children:[e(m,{...M.register("points",{valueAsNumber:!0}),error:M.hasError("points")?M.getFieldError("points"):void 0}),r("div",{className:"flex items-center gap-2",children:[e(u,{id:"isExample",checked:re||!1,onCheckedChange:e=>M.setValue("isExample",!!e)}),e(o,{htmlFor:"isExample",className:"flex items-center gap-1.5 cursor-pointer text-sm font-medium text-gray-700",children:"Đánh dấu là câu hỏi Example"})]})]}),r("div",{className:"space-y-2",children:[r(o,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(E,{className:"h-4 w-4 text-amber-500"}),"Hình ảnh"]}),e(J,{id:"word-ordering-image",label:"",accept:"image/*",value:ee||"",onChange:e=>{M.setValue("imageUrl",e,{shouldValidate:!0})},onPresignedUrlChange:B,maxSize:5,placeholder:"Upload hình ảnh hoặc paste URL",autoUpload:!0,prefix:"questions"}),ne&&e(t,{src:ne,alt:"Preview",className:"mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200"})]})]})]}),r(h,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-green-100/50",children:[e(p,{className:"px-4 py-3",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-md bg-green-100",children:e(V,{className:"h-3.5 w-3.5 text-green-600"})}),r(x,{className:"text-base font-semibold text-gray-800",children:["Đáp án đúng (",_.length," từ/chữ)"]})]}),r("div",{className:"flex gap-2",children:[r(l,{variant:"outline",size:"sm",onClick:de,disabled:_.filter(e=>e.trim()).length<2,className:"gap-2 border-amber-200 text-amber-600 hover:bg-amber-50",title:"Xáo trộn thứ tự đáp án để tạo danh sách từ",children:[e(C,{className:"h-4 w-4"}),"Xáo trộn → Từ"]}),r(l,{variant:"outline",size:"sm",onClick:oe,className:"gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700",children:[e(y,{className:"h-4 w-4"}),"Thêm"]})]})]})}),r(g,{className:"space-y-3 px-4 pb-4",children:[r("p",{className:"text-xs text-gray-500",children:["Nhập các từ/chữ cái theo ",e("strong",{children:"thứ tự đúng"}),'. Nhấn "Xáo trộn → Từ" để tạo danh sách từ xáo trộn tự động.']}),_.map((t,a)=>r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 text-white text-sm font-bold shadow-sm",children:a+1}),e("div",{className:"flex-1",children:e(c,{value:t,onChange:e=>{const r=[..._];r[a]=e.target.value,M.setValue("answer",r,{shouldValidate:!0})},placeholder:`Từ/chữ cái thứ ${a+1}`,className:"border-gray-200"})}),_.length>2&&e(l,{variant:"ghost",size:"sm",onClick:()=>ce(a),className:"text-red-500 hover:text-red-700 hover:bg-red-50",children:e(S,{className:"h-4 w-4"})})]},a)),M.formState.errors.answer?.message&&e("p",{className:"text-sm text-red-600",children:M.formState.errors.answer.message})]})]}),r(h,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-orange-100/50",children:[e(p,{className:"px-4 py-3",children:r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-orange-100",children:e(U,{className:"h-3.5 w-3.5 text-orange-600"})}),r(x,{className:"text-base font-semibold text-gray-800",children:["Từ hiển thị (xáo trộn) (",Z.length," từ/chữ)"]})]})}),r(g,{className:"space-y-3 px-4 pb-4",children:[e("p",{className:"text-xs text-gray-500",children:"Danh sách từ/chữ cái hiển thị cho học sinh (đã xáo trộn). Có thể chỉnh sửa trực tiếp."}),Z.map((t,a)=>r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-orange-400 to-amber-500 text-white text-sm font-bold shadow-sm",children:a+1}),e("div",{className:"flex-1",children:e(c,{value:t,onChange:e=>{const r=[...Z];r[a]=e.target.value,M.setValue("words",r,{shouldValidate:!0})},placeholder:`Từ/chữ cái thứ ${a+1}`,className:"border-gray-200"})})]},a)),M.formState.errors.words?.message&&e("p",{className:"text-sm text-red-600",children:M.formState.errors.words.message})]})]}),r(h,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50",children:[e(p,{className:"px-4 py-3",children:r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm",children:e(O,{className:"h-3.5 w-3.5 text-white"})}),r("div",{children:[r("div",{className:"flex items-center gap-2",children:[e(x,{className:"text-base font-semibold text-gray-800",children:"Giải thích"}),e("span",{className:"inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600",children:"Không bắt buộc"})]}),e("p",{className:"text-xs text-gray-500 mt-0.5",children:"Giải thích cho đáp án đúng — hiển thị sau khi học sinh hoàn thành câu hỏi"})]})]})}),e(g,{className:"px-4 pb-4",children:e(d,{...M.register("explanation"),placeholder:"VD: Câu trả lời đúng là... vì...",rows:3,className:"min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none"})})]})]})}export function WordOrderingCreator(r){return e(T,{...r})}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from './WordOrderingClient';
|
|
1
|
+
export*from"./WordOrderingCreator";export*from"./WordOrderingClient";
|
|
@@ -1,46 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const wordOrderingRegistration = {
|
|
3
|
-
component: WordOrderingCreator,
|
|
4
|
-
transformInitialData: (initialData) => {
|
|
5
|
-
const content = initialData?.content || {};
|
|
6
|
-
const answer = initialData?.answer || {};
|
|
7
|
-
const points = initialData?.total_points
|
|
8
|
-
? parseFloat(initialData.total_points)
|
|
9
|
-
: (answer?.points || initialData?.points || 1);
|
|
10
|
-
const explanation = initialData?.explanation || answer?.explanation || '';
|
|
11
|
-
// API format: content.words, correctAnswer.answer
|
|
12
|
-
// Internal onChange format: answer.words, answer.answer
|
|
13
|
-
return {
|
|
14
|
-
words: content?.words || answer?.words || [],
|
|
15
|
-
answer: answer?.answer || [],
|
|
16
|
-
imageUrl: content?.imageUrl || answer?.imageUrl || '',
|
|
17
|
-
isExample: initialData?.isExample || answer?.isExample || false,
|
|
18
|
-
explanation,
|
|
19
|
-
points,
|
|
20
|
-
};
|
|
21
|
-
},
|
|
22
|
-
getExtraProps: (ctx) => ({
|
|
23
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
24
|
-
validationRef: ctx.validationRef,
|
|
25
|
-
}),
|
|
26
|
-
wrapOnSave: (data, ctx) => ({
|
|
27
|
-
type: ctx.questionType,
|
|
28
|
-
content: ctx.state.content,
|
|
29
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
30
|
-
level: ctx.state.level,
|
|
31
|
-
difficulty: ctx.state.difficulty,
|
|
32
|
-
skill: ctx.state.skill,
|
|
33
|
-
answer: data,
|
|
34
|
-
explanation: data?.explanation || '',
|
|
35
|
-
}),
|
|
36
|
-
wrapOnChange: (data, ctx) => ({
|
|
37
|
-
type: ctx.questionType,
|
|
38
|
-
content: ctx.state.content,
|
|
39
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
40
|
-
level: ctx.state.level,
|
|
41
|
-
difficulty: ctx.state.difficulty,
|
|
42
|
-
skill: ctx.state.skill,
|
|
43
|
-
answer: data,
|
|
44
|
-
explanation: data?.explanation || '',
|
|
45
|
-
}),
|
|
46
|
-
};
|
|
1
|
+
import{WordOrderingCreator as t}from"./WordOrderingCreator";export const wordOrderingRegistration={component:t,transformInitialData:t=>{const e=t?.content||{},n=t?.answer||{},a=t?.total_points?parseFloat(t.total_points):n?.points||t?.points||1,i=t?.explanation||n?.explanation||"";return{words:e?.words||n?.words||[],answer:n?.answer||[],imageUrl:e?.imageUrl||n?.imageUrl||"",isExample:t?.isExample||n?.isExample||!1,explanation:i,points:a}},getExtraProps:t=>({onUnsavedChangesChange:t.onUnsavedChangesChange,validationRef:t.validationRef}),wrapOnSave:(t,e)=>({type:e.questionType,content:e.state.content,points:t?.points??e.state.points??1,level:e.state.level,difficulty:e.state.difficulty,skill:e.state.skill,answer:t,explanation:t?.explanation||""}),wrapOnChange:(t,e)=>({type:e.questionType,content:e.state.content,points:t?.points??e.state.points??1,level:e.state.level,difficulty:e.state.difficulty,skill:e.state.skill,answer:t,explanation:t?.explanation||""})};
|
|
@@ -1,24 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Transform WORD_ORDERING creator data → API format
|
|
3
|
-
* Creator: { words, answer, imageUrl }
|
|
4
|
-
* API: content: { words, imageUrl }, correctAnswer: { answer: [...] }
|
|
5
|
-
*/
|
|
6
|
-
export const transformWordOrdering = (question) => {
|
|
7
|
-
const answerData = question.answer;
|
|
8
|
-
if (!answerData?.words || !answerData?.answer) {
|
|
9
|
-
return { apiContent: {}, apiCorrectAnswer: {} };
|
|
10
|
-
}
|
|
11
|
-
const apiContent = {
|
|
12
|
-
words: answerData.words,
|
|
13
|
-
};
|
|
14
|
-
if (answerData.imageUrl) {
|
|
15
|
-
apiContent.imageUrl = answerData.imageUrl;
|
|
16
|
-
}
|
|
17
|
-
return {
|
|
18
|
-
apiContent,
|
|
19
|
-
apiCorrectAnswer: {
|
|
20
|
-
answer: answerData.answer,
|
|
21
|
-
},
|
|
22
|
-
isExample: answerData.isExample || false,
|
|
23
|
-
};
|
|
24
|
-
};
|
|
1
|
+
export const transformWordOrdering=r=>{const e=r.answer;if(!e?.words||!e?.answer)return{apiContent:{},apiCorrectAnswer:{}};const n={words:e.words};return e.imageUrl&&(n.imageUrl=e.imageUrl),{apiContent:n,apiCorrectAnswer:{answer:e.answer},isExample:e.isExample||!1}};
|
|
@@ -1,98 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useEffect, useMemo } from 'react';
|
|
4
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
5
|
-
import { cn } from '../../../../shared/lib/utils';
|
|
6
|
-
import { FileText, PenLine, Check, AlertCircle, BookOpen, Lightbulb, ImageIcon } from 'lucide-react';
|
|
7
|
-
import { usePresignedFileUrl } from '../../../../shared/lib/hooks/usePresignedFileUrl';
|
|
8
|
-
// Component to render a single guide image in client mode
|
|
9
|
-
function GuideImageDisplay({ imageKey }) {
|
|
10
|
-
const { previewUrl } = usePresignedFileUrl(imageKey);
|
|
11
|
-
if (!previewUrl) {
|
|
12
|
-
return (_jsx("div", { className: "flex aspect-[4/3] w-full items-center justify-center rounded-lg border border-gray-200 bg-gray-50", children: _jsxs("div", { className: "flex flex-col items-center gap-2 text-gray-400", children: [_jsx(ImageIcon, { className: "h-8 w-8" }), _jsx("span", { className: "text-xs", children: "\u0110ang t\u1EA3i..." })] }) }));
|
|
13
|
-
}
|
|
14
|
-
return (_jsx("div", { className: "mx-auto w-full max-w-sm overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm", children: _jsx("img", { src: previewUrl, alt: "\u1EA2nh h\u01B0\u1EDBng d\u1EABn", className: "h-auto w-full object-contain", onError: (e) => {
|
|
15
|
-
e.target.src = '/images/placeholder-image.svg';
|
|
16
|
-
} }) }));
|
|
17
|
-
}
|
|
18
|
-
export function WriteAShortLetterClient({ questionData, onSubmit, isReviewMode = false, userAnswer, explanation, }) {
|
|
19
|
-
const { informations, minWords, maxWords, instructions, guideImages } = questionData;
|
|
20
|
-
const instruction = instructions?.mainInstruction || '';
|
|
21
|
-
// Initialize answer state - ensure it's always a string
|
|
22
|
-
const [answer, setAnswer] = useState(typeof userAnswer === 'string' ? userAnswer : '');
|
|
23
|
-
// Sync userAnswer when it changes (e.g., when loading from server in review mode)
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
if (userAnswer !== undefined && typeof userAnswer === 'string') {
|
|
26
|
-
setAnswer(userAnswer);
|
|
27
|
-
}
|
|
28
|
-
}, [userAnswer]);
|
|
29
|
-
// Count words
|
|
30
|
-
const wordCount = useMemo(() => {
|
|
31
|
-
const answerStr = typeof answer === 'string' ? answer : '';
|
|
32
|
-
if (!answerStr.trim())
|
|
33
|
-
return 0;
|
|
34
|
-
return answerStr
|
|
35
|
-
.trim()
|
|
36
|
-
.split(/\s+/)
|
|
37
|
-
.filter((word) => word.length > 0).length;
|
|
38
|
-
}, [answer]);
|
|
39
|
-
// Check if word count is valid
|
|
40
|
-
const isValidWordCount = useMemo(() => {
|
|
41
|
-
if (wordCount < minWords)
|
|
42
|
-
return false;
|
|
43
|
-
if (maxWords && wordCount > maxWords)
|
|
44
|
-
return false;
|
|
45
|
-
return true;
|
|
46
|
-
}, [wordCount, minWords, maxWords]);
|
|
47
|
-
// Check if exceeded max words
|
|
48
|
-
const isExceeded = useMemo(() => {
|
|
49
|
-
return maxWords && wordCount > maxWords;
|
|
50
|
-
}, [wordCount, maxWords]);
|
|
51
|
-
// Handle answer change
|
|
52
|
-
const handleAnswerChange = (value) => {
|
|
53
|
-
if (isReviewMode)
|
|
54
|
-
return;
|
|
55
|
-
setAnswer(value);
|
|
56
|
-
onSubmit?.(value);
|
|
57
|
-
};
|
|
58
|
-
// Calculate progress percentage
|
|
59
|
-
const progressPercentage = useMemo(() => {
|
|
60
|
-
const target = minWords;
|
|
61
|
-
return Math.min((wordCount / target) * 100, 100);
|
|
62
|
-
}, [wordCount, minWords]);
|
|
63
|
-
return (_jsxs("div", { className: "space-y-5", children: [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(BookOpen, { 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: instruction })] })] }) })), guideImages && guideImages.length > 0 && (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-3 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-violet-500 to-purple-600 text-white shadow-sm", children: _jsx(ImageIcon, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "\u1EA2nh h\u01B0\u1EDBng d\u1EABn" })] }), _jsx("div", { className: "p-5", children: _jsx("div", { className: "flex flex-col gap-4", children: guideImages.map((imageKey, index) => (_jsx(GuideImageDisplay, { imageKey: imageKey }, `${imageKey}-${index}`))) }) })] })), informations && informations.filter((i) => i?.trim()).length > 0 && (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-3 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-emerald-500 to-teal-600 text-white shadow-sm", children: _jsx(FileText, { className: "h-4 w-4" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["Th\u00F4ng tin c\u1EA7n bao g\u1ED3m (", informations.length, ")"] })] }), _jsx("div", { className: "p-5", children: _jsx("div", { className: "space-y-3", children: informations.filter((i) => i?.trim()).map((info, index) => (_jsxs("div", { className: "flex items-start gap-3 rounded-lg border border-gray-100 bg-gradient-to-r from-white to-gray-50 p-3 transition-all hover:border-gray-200 hover:shadow-sm", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-emerald-400 to-teal-500 text-xs font-bold text-white shadow-sm", children: index + 1 }), _jsx("p", { className: "text-sm text-gray-700 leading-relaxed pt-0.5", children: info })] }, index))) }) })] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-purple-500 to-violet-600 text-white shadow-sm", children: _jsx(PenLine, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Vi\u1EBFt th\u01B0 c\u1EE7a b\u1EA1n" })] }), _jsxs("div", { className: cn('flex items-center gap-2 rounded-full px-3 py-1.5 text-xs font-semibold transition-all', isExceeded
|
|
64
|
-
? 'bg-red-100 text-red-700'
|
|
65
|
-
: isValidWordCount
|
|
66
|
-
? 'bg-green-100 text-green-700'
|
|
67
|
-
: 'bg-amber-100 text-amber-700'), children: [isValidWordCount && !isExceeded && _jsx(Check, { className: "h-3.5 w-3.5" }), isExceeded && _jsx(AlertCircle, { className: "h-3.5 w-3.5" }), _jsxs("span", { children: [wordCount, " / ", minWords, maxWords ? ` - ${maxWords}` : '+', " t\u1EEB"] })] })] }), _jsxs("div", { className: "p-5 space-y-4", children: [!isReviewMode && (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between text-xs text-gray-500", children: [_jsx("span", { children: "Ti\u1EBFn \u0111\u1ED9 vi\u1EBFt" }), _jsxs("span", { children: [Math.round(progressPercentage), "%"] })] }), _jsx("div", { className: "h-2 w-full overflow-hidden rounded-full bg-gray-100", children: _jsx("div", { className: cn('h-full transition-all duration-300 ease-out rounded-full', isExceeded
|
|
68
|
-
? 'bg-gradient-to-r from-red-400 to-rose-500'
|
|
69
|
-
: isValidWordCount
|
|
70
|
-
? 'bg-gradient-to-r from-green-400 to-emerald-500'
|
|
71
|
-
: 'bg-gradient-to-r from-amber-400 to-orange-500'), style: { width: `${progressPercentage}%` } }) })] })), _jsxs("div", { className: "relative", children: [_jsx(Textarea, { id: "writing-answer", value: answer, onChange: (e) => handleAnswerChange(e.target.value), placeholder: "B\u1EAFt \u0111\u1EA7u vi\u1EBFt th\u01B0 c\u1EE7a b\u1EA1n t\u1EA1i \u0111\u00E2y...", className: cn('min-h-[280px] resize-none rounded-xl border-2 p-4 text-base leading-relaxed transition-all', 'focus:ring-2 focus:ring-offset-1', isReviewMode
|
|
72
|
-
? 'bg-gray-50 cursor-not-allowed'
|
|
73
|
-
: 'bg-white hover:border-gray-300', isExceeded
|
|
74
|
-
? 'border-red-300 focus:border-red-400 focus:ring-red-100'
|
|
75
|
-
: isValidWordCount
|
|
76
|
-
? 'border-green-300 focus:border-green-400 focus:ring-green-100'
|
|
77
|
-
: 'border-gray-200 focus:border-purple-400 focus:ring-purple-100'), rows: 12, disabled: isReviewMode }), _jsx("div", { className: "absolute bottom-3 right-3 pointer-events-none", children: _jsx("div", { className: "flex items-center gap-1 text-gray-300", children: _jsx(PenLine, { className: "h-4 w-4" }) }) })] }), !isReviewMode && (_jsx("div", { className: cn('rounded-xl border p-4 transition-all', isExceeded
|
|
78
|
-
? 'border-red-200 bg-gradient-to-r from-red-50 to-rose-50'
|
|
79
|
-
: isValidWordCount
|
|
80
|
-
? 'border-green-200 bg-gradient-to-r from-green-50 to-emerald-50'
|
|
81
|
-
: 'border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50'), children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: cn('flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg text-white shadow-sm', isExceeded
|
|
82
|
-
? 'bg-gradient-to-br from-red-500 to-rose-600'
|
|
83
|
-
: isValidWordCount
|
|
84
|
-
? 'bg-gradient-to-br from-green-500 to-emerald-600'
|
|
85
|
-
: 'bg-gradient-to-br from-amber-500 to-orange-600'), children: isExceeded ? (_jsx(AlertCircle, { className: "h-4 w-4" })) : isValidWordCount ? (_jsx(Check, { className: "h-4 w-4" })) : (_jsx(AlertCircle, { className: "h-4 w-4" })) }), _jsxs("div", { className: "flex-1", children: [_jsx("p", { className: cn('text-sm font-semibold', isExceeded
|
|
86
|
-
? 'text-red-800'
|
|
87
|
-
: isValidWordCount
|
|
88
|
-
? 'text-green-800'
|
|
89
|
-
: 'text-amber-800'), children: isExceeded
|
|
90
|
-
? 'Vượt quá số từ cho phép!'
|
|
91
|
-
: isValidWordCount
|
|
92
|
-
? 'Tuyệt vời! Đã đủ số từ yêu cầu'
|
|
93
|
-
: 'Chưa đủ số từ yêu cầu' }), _jsx("p", { className: cn('text-xs mt-0.5', isExceeded
|
|
94
|
-
? 'text-red-600'
|
|
95
|
-
: isValidWordCount
|
|
96
|
-
? 'text-green-600'
|
|
97
|
-
: 'text-amber-600'), children: isExceeded ? (_jsxs(_Fragment, { children: ["B\u1EA1n \u0111\u00E3 vi\u1EBFt ", wordCount, " t\u1EEB. T\u1ED1i \u0111a ", maxWords, " t\u1EEB."] })) : isValidWordCount ? (_jsxs(_Fragment, { children: ["B\u1EA1n \u0111\u00E3 vi\u1EBFt ", wordCount, " t\u1EEB. Y\u00EAu c\u1EA7u: ", minWords, maxWords ? ` - ${maxWords}` : '+', " t\u1EEB."] })) : (_jsxs(_Fragment, { children: ["B\u1EA1n \u0111\u00E3 vi\u1EBFt ", wordCount, " t\u1EEB. C\u1EA7n \u00EDt nh\u1EA5t ", minWords, " t\u1EEB."] })) })] })] }) })), isReviewMode && answer && (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-gradient-to-r from-slate-50 to-gray-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3 pb-3 border-b border-gray-200", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-lg bg-gray-200", children: _jsx(FileText, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsx("span", { className: "text-sm font-medium text-gray-600", children: "B\u00E0i vi\u1EBFt \u0111\u00E3 n\u1ED9p" }), _jsxs("span", { className: cn('ml-auto text-xs font-semibold px-2 py-1 rounded-full', isValidWordCount ? 'bg-green-100 text-green-700' : 'bg-amber-100 text-amber-700'), children: [wordCount, " t\u1EEB"] })] }), _jsx("p", { className: "text-sm text-gray-700 whitespace-pre-wrap leading-relaxed", children: answer })] })), isReviewMode && explanation && (_jsx("div", { className: "rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-semibold text-blue-700 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-gray-700 leading-relaxed", children: explanation })] })] }) }))] })] })] }));
|
|
98
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r,Fragment as t}from"react/jsx-runtime";import{useState as a,useEffect as s,useMemo as i}from"react";import{Textarea as d}from"../../../../components/ui/textarea";import{cn as l}from"../../../../shared/lib/utils";import{FileText as n,PenLine as o,Check as c,AlertCircle as m,BookOpen as g,Lightbulb as h,ImageIcon as b}from"lucide-react";import{usePresignedFileUrl as x}from"../../../../shared/lib/hooks/usePresignedFileUrl";function f({imageKey:t}){const{previewUrl:a}=x(t);return e("div",a?{className:"mx-auto w-full max-w-sm overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm",children:e("img",{src:a,alt:"Ảnh hướng dẫn",className:"h-auto w-full object-contain",onError:e=>{e.target.src="/images/placeholder-image.svg"}})}:{className:"flex aspect-[4/3] w-full items-center justify-center rounded-lg border border-gray-200 bg-gray-50",children:r("div",{className:"flex flex-col items-center gap-2 text-gray-400",children:[e(b,{className:"h-8 w-8"}),e("span",{className:"text-xs",children:"Đang tải..."})]})})}export function WriteAShortLetterClient({questionData:x,onSubmit:u,isReviewMode:p=!1,userAnswer:w,explanation:v}){const{informations:N,minWords:y,maxWords:j,instructions:k,guideImages:B}=x,$=k?.mainInstruction||"",[C,T]=a("string"==typeof w?w:"");s(()=>{void 0!==w&&"string"==typeof w&&T(w)},[w]);const M=i(()=>{const e="string"==typeof C?C:"";return e.trim()?e.trim().split(/\s+/).filter(e=>e.length>0).length:0},[C]),W=i(()=>!(M<y)&&!(j&&M>j),[M,y,j]),q=i(()=>j&&M>j,[M,j]),A=i(()=>{const e=y;return Math.min(M/e*100,100)},[M,y]);return r("div",{className:"space-y-5",children:[$&&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(g,{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:$})]})]})}),B&&B.length>0&&r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:[r("div",{className:"flex items-center gap-3 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-violet-500 to-purple-600 text-white shadow-sm",children:e(b,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Ảnh hướng dẫn"})]}),e("div",{className:"p-5",children:e("div",{className:"flex flex-col gap-4",children:B.map((r,t)=>e(f,{imageKey:r},`${r}-${t}`))})})]}),N&&N.filter(e=>e?.trim()).length>0&&r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:[r("div",{className:"flex items-center gap-3 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-emerald-500 to-teal-600 text-white shadow-sm",children:e(n,{className:"h-4 w-4"})}),r("span",{className:"text-sm font-medium text-gray-700",children:["Thông tin cần bao gồm (",N.length,")"]})]}),e("div",{className:"p-5",children:e("div",{className:"space-y-3",children:N.filter(e=>e?.trim()).map((t,a)=>r("div",{className:"flex items-start gap-3 rounded-lg border border-gray-100 bg-gradient-to-r from-white to-gray-50 p-3 transition-all hover:border-gray-200 hover:shadow-sm",children:[e("div",{className:"flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-emerald-400 to-teal-500 text-xs font-bold text-white shadow-sm",children:a+1}),e("p",{className:"text-sm text-gray-700 leading-relaxed pt-0.5",children:t})]},a))})})]}),r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:[r("div",{className:"flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3",children:[r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-purple-500 to-violet-600 text-white shadow-sm",children:e(o,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Viết thư của bạn"})]}),r("div",{className:l("flex items-center gap-2 rounded-full px-3 py-1.5 text-xs font-semibold transition-all",q?"bg-red-100 text-red-700":W?"bg-green-100 text-green-700":"bg-amber-100 text-amber-700"),children:[W&&!q&&e(c,{className:"h-3.5 w-3.5"}),q&&e(m,{className:"h-3.5 w-3.5"}),r("span",{children:[M," / ",y,j?` - ${j}`:"+"," từ"]})]})]}),r("div",{className:"p-5 space-y-4",children:[!p&&r("div",{className:"space-y-2",children:[r("div",{className:"flex items-center justify-between text-xs text-gray-500",children:[e("span",{children:"Tiến độ viết"}),r("span",{children:[Math.round(A),"%"]})]}),e("div",{className:"h-2 w-full overflow-hidden rounded-full bg-gray-100",children:e("div",{className:l("h-full transition-all duration-300 ease-out rounded-full",q?"bg-gradient-to-r from-red-400 to-rose-500":W?"bg-gradient-to-r from-green-400 to-emerald-500":"bg-gradient-to-r from-amber-400 to-orange-500"),style:{width:`${A}%`}})})]}),r("div",{className:"relative",children:[e(d,{id:"writing-answer",value:C,onChange:e=>{return r=e.target.value,void(p||(T(r),u?.(r)));var r},placeholder:"Bắt đầu viết thư của bạn tại đây...",className:l("min-h-[280px] resize-none rounded-xl border-2 p-4 text-base leading-relaxed transition-all","focus:ring-2 focus:ring-offset-1",p?"bg-gray-50 cursor-not-allowed":"bg-white hover:border-gray-300",q?"border-red-300 focus:border-red-400 focus:ring-red-100":W?"border-green-300 focus:border-green-400 focus:ring-green-100":"border-gray-200 focus:border-purple-400 focus:ring-purple-100"),rows:12,disabled:p}),e("div",{className:"absolute bottom-3 right-3 pointer-events-none",children:e("div",{className:"flex items-center gap-1 text-gray-300",children:e(o,{className:"h-4 w-4"})})})]}),!p&&e("div",{className:l("rounded-xl border p-4 transition-all",q?"border-red-200 bg-gradient-to-r from-red-50 to-rose-50":W?"border-green-200 bg-gradient-to-r from-green-50 to-emerald-50":"border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50"),children:r("div",{className:"flex items-start gap-3",children:[e("div",{className:l("flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg text-white shadow-sm",q?"bg-gradient-to-br from-red-500 to-rose-600":W?"bg-gradient-to-br from-green-500 to-emerald-600":"bg-gradient-to-br from-amber-500 to-orange-600"),children:e(q?m:W?c:m,{className:"h-4 w-4"})}),r("div",{className:"flex-1",children:[e("p",{className:l("text-sm font-semibold",q?"text-red-800":W?"text-green-800":"text-amber-800"),children:q?"Vượt quá số từ cho phép!":W?"Tuyệt vời! Đã đủ số từ yêu cầu":"Chưa đủ số từ yêu cầu"}),e("p",{className:l("text-xs mt-0.5",q?"text-red-600":W?"text-green-600":"text-amber-600"),children:r(t,q?{children:["Bạn đã viết ",M," từ. Tối đa ",j," từ."]}:W?{children:["Bạn đã viết ",M," từ. Yêu cầu: ",y,j?` - ${j}`:"+"," từ."]}:{children:["Bạn đã viết ",M," từ. Cần ít nhất ",y," từ."]})})]})]})}),p&&C&&r("div",{className:"rounded-xl border border-gray-200 bg-gradient-to-r from-slate-50 to-gray-50 p-4",children:[r("div",{className:"flex items-center gap-2 mb-3 pb-3 border-b border-gray-200",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-lg bg-gray-200",children:e(n,{className:"h-3.5 w-3.5 text-gray-600"})}),e("span",{className:"text-sm font-medium text-gray-600",children:"Bài viết đã nộp"}),r("span",{className:l("ml-auto text-xs font-semibold px-2 py-1 rounded-full",W?"bg-green-100 text-green-700":"bg-amber-100 text-amber-700"),children:[M," từ"]})]}),e("p",{className:"text-sm text-gray-700 whitespace-pre-wrap leading-relaxed",children:C})]}),p&&v&&e("div",{className:"rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200",children:r("div",{className:"flex items-start gap-3",children:[e("div",{className:"flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-white",children:e(h,{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:v})]})]})})]})]})]})}
|