@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,26 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Mic, Volume2, CheckCircle2, Play, Pause, ListChecks, Info } from 'lucide-react';
|
|
4
|
-
import { Button } from '../../../../components/ui/button';
|
|
5
|
-
import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
|
|
6
|
-
import { cn } from '../../../../shared/lib/utils';
|
|
7
|
-
// ─── Sub-component: Custom Audio Player ──────────────────────────────────────
|
|
8
|
-
function QuestionAudioPlayer({ audioUrl }) {
|
|
9
|
-
const { previewUrl } = usePresignedFileUrl(audioUrl);
|
|
10
|
-
const { isPlaying, currentTime, duration, progressPercentage, handlePlayPause, handleProgressClick, formatTime, } = useAudioPreview(previewUrl);
|
|
11
|
-
return (_jsxs("div", { className: "flex items-center gap-3 rounded-lg bg-gradient-to-r from-indigo-50 to-purple-50 border border-indigo-200 px-4 py-2.5", children: [_jsx(Button, { variant: "outline", size: "icon", onClick: handlePlayPause, disabled: !previewUrl, className: "h-8 w-8 border-indigo-300 text-indigo-600 hover:bg-indigo-100", children: isPlaying ? _jsx(Pause, { className: "h-3.5 w-3.5" }) : _jsx(Play, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "text-muted-foreground min-w-[70px] text-xs", children: [formatTime(currentTime), " / ", formatTime(duration)] }), _jsx("div", { className: "bg-indigo-200 relative h-1.5 flex-1 cursor-pointer rounded-full", onClick: handleProgressClick, children: _jsx("div", { className: "bg-indigo-500 absolute top-0 left-0 h-full rounded-full transition-all", style: { width: `${progressPercentage}%` } }) }), _jsx(Volume2, { className: "h-4 w-4 text-indigo-400" })] }));
|
|
12
|
-
}
|
|
13
|
-
// ─── Main Client Component ───────────────────────────────────────────────────
|
|
14
|
-
export function ListenAndSpeakQuestionListClient({ questionData, isReviewMode = false, }) {
|
|
15
|
-
const { audioUrl, expectedAnswers, activeQuestionId, questions = [], explanation } = questionData;
|
|
16
|
-
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-indigo-50 to-blue-50 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-indigo-500 to-blue-600 text-white shadow-sm", children: _jsx(ListChecks, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 n\u00F3i (Danh s\u00E1ch c\u00E2u h\u1ECFi)" })] }) }), _jsxs("div", { className: "p-5 space-y-5", children: [audioUrl ? (_jsx(QuestionAudioPlayer, { audioUrl: audioUrl })) : (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3", children: [_jsx(Volume2, { className: "h-4 w-4 text-gray-400" }), _jsx("span", { className: "text-xs text-gray-400", children: "Ch\u01B0a c\u00F3 audio c\u00E2u h\u1ECFi" })] })), questions.length > 0 ? (_jsx("div", { className: "rounded-xl border border-gray-100 bg-gradient-to-b from-gray-50/80 to-white p-5 space-y-2.5", children: questions.map((q, idx) => {
|
|
17
|
-
const isActive = q.id === activeQuestionId;
|
|
18
|
-
return (_jsxs("div", { className: cn('flex items-center gap-3 rounded-lg px-4 py-3 transition-all duration-200', isActive
|
|
19
|
-
? 'bg-blue-50 border-[2px] border-blue-300 shadow-sm shadow-blue-100/50'
|
|
20
|
-
: 'bg-white border border-gray-200 hover:border-gray-300 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg text-xs font-bold shadow-sm', isActive
|
|
21
|
-
? 'bg-blue-500 text-white'
|
|
22
|
-
: 'bg-gray-100 text-gray-500 border border-gray-200'), children: idx + 1 }), _jsx("span", { className: cn('text-sm font-medium flex-1', isActive ? 'text-blue-800' : 'text-gray-700'), children: q.text || _jsx("span", { className: "italic text-gray-400", children: "(Ch\u01B0a nh\u1EADp n\u1ED9i dung)" }) }), isActive && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-[10px] font-medium text-blue-500 animate-pulse", children: "\u0110ang h\u1ECFi" }), _jsx(CheckCircle2, { className: "h-4 w-4 text-blue-500" })] }))] }, q.id));
|
|
23
|
-
}) })) : (
|
|
24
|
-
/* Empty state */
|
|
25
|
-
_jsxs("div", { className: "flex flex-col items-center justify-center py-10 px-4 rounded-xl border-2 border-dashed border-gray-200 bg-gray-50/50", children: [_jsx(ListChecks, { className: "h-12 w-12 text-gray-300 mb-3" }), _jsx("p", { className: "text-sm font-medium text-gray-500", children: "Ch\u01B0a c\u00F3 c\u00E2u h\u1ECFi n\u00E0o" }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: "Danh s\u00E1ch c\u00E2u h\u1ECFi s\u1EBD hi\u1EC3n th\u1ECB t\u1EA1i \u0111\u00E2y" })] })), _jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: "H\u1ECDc sinh s\u1EBD nh\u1EA5n n\u00FAt ghi \u00E2m v\u00E0 n\u00F3i c\u00E2u tr\u1EA3 l\u1EDDi t\u1EA1i \u0111\u00E2y" })] }), isReviewMode && expectedAnswers && expectedAnswers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsx("h4", { className: "text-sm font-semibold text-green-800", children: "C\u00E1c \u0111\u00E1p \u00E1n mong mu\u1ED1n" })] }), _jsx("div", { className: "space-y-2", children: expectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold", children: index + 1 }), _jsx("span", { className: "text-sm text-green-800", children: answer })] }, index))) })] })), isReviewMode && explanation && (_jsxs("div", { className: "rounded-xl border border-indigo-200 bg-indigo-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-indigo-600" }), _jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("p", { className: "text-sm text-indigo-700 leading-relaxed", children: explanation })] }))] })] })] }));
|
|
26
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Mic as i,Volume2 as t,CheckCircle2 as s,Play as a,Pause as d,ListChecks as n,Info as l}from"lucide-react";import{Button as o}from"../../../../components/ui/button";import{usePresignedFileUrl as c,useAudioPreview as m}from"../../../../shared/lib/hooks";import{cn as h}from"../../../../shared/lib/utils";function g({audioUrl:i}){const{previewUrl:s}=c(i),{isPlaying:n,currentTime:l,duration:h,progressPercentage:g,handlePlayPause:x,handleProgressClick:b,formatTime:u}=m(s);return r("div",{className:"flex items-center gap-3 rounded-lg bg-gradient-to-r from-indigo-50 to-purple-50 border border-indigo-200 px-4 py-2.5",children:[e(o,{variant:"outline",size:"icon",onClick:x,disabled:!s,className:"h-8 w-8 border-indigo-300 text-indigo-600 hover:bg-indigo-100",children:e(n?d:a,{className:"h-3.5 w-3.5"})}),r("div",{className:"text-muted-foreground min-w-[70px] text-xs",children:[u(l)," / ",u(h)]}),e("div",{className:"bg-indigo-200 relative h-1.5 flex-1 cursor-pointer rounded-full",onClick:b,children:e("div",{className:"bg-indigo-500 absolute top-0 left-0 h-full rounded-full transition-all",style:{width:`${g}%`}})}),e(t,{className:"h-4 w-4 text-indigo-400"})]})}export function ListenAndSpeakQuestionListClient({questionData:a,isReviewMode:d=!1}){const{audioUrl:o,expectedAnswers:c,activeQuestionId:m,questions:x=[],explanation:b}=a;return r("div",{className:"space-y-4",children:[a.isBackup&&r("div",{className:"inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm",children:[e("span",{children:"🔄"})," Câu hỏi dự phòng"]}),r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:[e("div",{className:"flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-indigo-50 to-blue-50 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-indigo-500 to-blue-600 text-white shadow-sm",children:e(n,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Nghe và nói (Danh sách câu hỏi)"})]})}),r("div",{className:"p-5 space-y-5",children:[o?e(g,{audioUrl:o}):r("div",{className:"flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3",children:[e(t,{className:"h-4 w-4 text-gray-400"}),e("span",{className:"text-xs text-gray-400",children:"Chưa có audio câu hỏi"})]}),x.length>0?e("div",{className:"rounded-xl border border-gray-100 bg-gradient-to-b from-gray-50/80 to-white p-5 space-y-2.5",children:x.map((i,t)=>{const a=i.id===m;return r("div",{className:h("flex items-center gap-3 rounded-lg px-4 py-3 transition-all duration-200",a?"bg-blue-50 border-[2px] border-blue-300 shadow-sm shadow-blue-100/50":"bg-white border border-gray-200 hover:border-gray-300 hover:shadow-sm"),children:[e("div",{className:h("flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg text-xs font-bold shadow-sm",a?"bg-blue-500 text-white":"bg-gray-100 text-gray-500 border border-gray-200"),children:t+1}),e("span",{className:h("text-sm font-medium flex-1",a?"text-blue-800":"text-gray-700"),children:i.text||e("span",{className:"italic text-gray-400",children:"(Chưa nhập nội dung)"})}),a&&r("div",{className:"flex items-center gap-1.5",children:[e("span",{className:"text-[10px] font-medium text-blue-500 animate-pulse",children:"Đang hỏi"}),e(s,{className:"h-4 w-4 text-blue-500"})]})]},i.id)})}):r("div",{className:"flex flex-col items-center justify-center py-10 px-4 rounded-xl border-2 border-dashed border-gray-200 bg-gray-50/50",children:[e(n,{className:"h-12 w-12 text-gray-300 mb-3"}),e("p",{className:"text-sm font-medium text-gray-500",children:"Chưa có câu hỏi nào"}),e("p",{className:"text-xs text-gray-400 mt-1",children:"Danh sách câu hỏi sẽ hiển thị tại đây"})]}),r("div",{className:"flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50",children:e(i,{className:"w-4 h-4 text-indigo-400"})}),e("p",{className:"text-xs text-gray-400",children:"Học sinh sẽ nhấn nút ghi âm và nói câu trả lời tại đây"})]}),d&&c&&c.length>0&&r("div",{className:"rounded-xl border border-green-200 bg-green-50/80 p-4",children:[r("div",{className:"flex items-center gap-2 mb-3",children:[e(s,{className:"h-4 w-4 text-green-600"}),e("h4",{className:"text-sm font-semibold text-green-800",children:"Các đáp án mong muốn"})]}),e("div",{className:"space-y-2",children:c.map((i,t)=>r("div",{className:"flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2",children:[e("span",{className:"flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold",children:t+1}),e("span",{className:"text-sm text-green-800",children:i})]},t))})]}),d&&b&&r("div",{className:"rounded-xl border border-indigo-200 bg-indigo-50 p-4",children:[r("div",{className:"flex items-center gap-2 mb-2",children:[e(l,{className:"h-4 w-4 text-indigo-600"}),e("p",{className:"text-sm font-semibold text-indigo-800",children:"Giải thích"})]}),e("p",{className:"text-sm text-indigo-700 leading-relaxed",children:b})]})]})]})]})}
|
|
@@ -1,256 +1 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
5
|
-
import { Label } from '../../../../components/ui/label';
|
|
6
|
-
import { Button } from '../../../../components/ui/button';
|
|
7
|
-
import { Input } from '../../../../components/ui/input';
|
|
8
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
9
|
-
import { Form, FormControl, FormField, FormItem, FormMessage } from '../../../../components/ui/form';
|
|
10
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../../components/ui/select';
|
|
11
|
-
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
12
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
13
|
-
// Stores & hooks
|
|
14
|
-
import { useQuestionListGroup } from '../../_shared/hooks/useQuestionListGroup';
|
|
15
|
-
import { generateObjectId } from '../../_shared/utils/id-generator';
|
|
16
|
-
// Client component
|
|
17
|
-
import { ListenAndSpeakQuestionListClient } from './ListenAndSpeakQuestionListClient';
|
|
18
|
-
import { listenAndSpeakQuestionListFormSchema } from '../../../../shared/lib/validation/listen-and-speak-question-list.validation';
|
|
19
|
-
// Utilities & Icons
|
|
20
|
-
import { Plus, Trash2, Mic, Volume2, ListChecks, MessageSquareText, Info, BookOpen, Eye, Pencil } from 'lucide-react';
|
|
21
|
-
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
22
|
-
import { useDebouncedCallback } from '../../../../shared/lib/hooks/useDebouncedCallback';
|
|
23
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
24
|
-
export function ListenAndSpeakQuestionListCreator({ questionIndex, initialData, onChange, validationRef, isFirstInGroup: isFirstInGroupProp, partId, groups, questionIndexInPart, }) {
|
|
25
|
-
const [isClientMode, setIsClientMode] = useState(false);
|
|
26
|
-
const isGrouped = Boolean(groups && groups.length > 0);
|
|
27
|
-
const isFirstInGroup = useMemo(() => {
|
|
28
|
-
if (isFirstInGroupProp !== undefined)
|
|
29
|
-
return isFirstInGroupProp;
|
|
30
|
-
if (!groups || !groups.length || questionIndexInPart === undefined)
|
|
31
|
-
return true;
|
|
32
|
-
let cumulative = 0;
|
|
33
|
-
for (const group of groups) {
|
|
34
|
-
if (questionIndexInPart === cumulative)
|
|
35
|
-
return true;
|
|
36
|
-
cumulative += group.questionCount;
|
|
37
|
-
}
|
|
38
|
-
return false;
|
|
39
|
-
}, [isFirstInGroupProp, groups, questionIndexInPart]);
|
|
40
|
-
// 1. Group level state (Questions list)
|
|
41
|
-
const { questions, setQuestions, currentGroupNumber } = useQuestionListGroup({
|
|
42
|
-
partId,
|
|
43
|
-
groups,
|
|
44
|
-
questionIndexInPart,
|
|
45
|
-
initialData: initialData?.groupQuestions ? { questions: initialData.groupQuestions } : undefined,
|
|
46
|
-
});
|
|
47
|
-
// Cast initialData to access extra fields from register transformInitialData
|
|
48
|
-
const apiData = initialData;
|
|
49
|
-
// 2. Question level form data (Audio, ActiveQuestionId, ExpectedAnswers, Points, Explanation)
|
|
50
|
-
const form = useReactHookForm({
|
|
51
|
-
zodSchema: listenAndSpeakQuestionListFormSchema,
|
|
52
|
-
defaultValues: {
|
|
53
|
-
audioUrl: apiData?.audioUrl || '',
|
|
54
|
-
expectedAnswers: apiData?.expectedAnswers?.length > 0 ? apiData.expectedAnswers : [''],
|
|
55
|
-
activeQuestionId: apiData?.activeQuestionId || undefined,
|
|
56
|
-
points: apiData?.points ?? 1,
|
|
57
|
-
explanation: apiData?.explanation || '',
|
|
58
|
-
isBackup: apiData?.isBackup || false,
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
const { control, watch, setValue, trigger, clearErrors } = form;
|
|
62
|
-
const watchedAudioUrl = watch('audioUrl');
|
|
63
|
-
const watchedActiveQuestionId = watch('activeQuestionId');
|
|
64
|
-
const watchedExpectedAnswers = watch('expectedAnswers');
|
|
65
|
-
const watchedPoints = watch('points');
|
|
66
|
-
const watchedExplanation = watch('explanation');
|
|
67
|
-
const watchedIsBackup = watch('isBackup');
|
|
68
|
-
// ---------------------------------------------------------------
|
|
69
|
-
// Refs to prevent infinite loops
|
|
70
|
-
// ---------------------------------------------------------------
|
|
71
|
-
const previousInitialDataRef = useRef(initialData);
|
|
72
|
-
const isInitializingRef = useRef(false);
|
|
73
|
-
const onChangeRef = useRef(onChange);
|
|
74
|
-
const previousPayloadRef = useRef('');
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
onChangeRef.current = onChange;
|
|
77
|
-
}, [onChange]);
|
|
78
|
-
// Notify parent onChange
|
|
79
|
-
const debouncedOnChange = useDebouncedCallback((data) => {
|
|
80
|
-
onChangeRef.current?.(data);
|
|
81
|
-
}, 300);
|
|
82
|
-
// ---------------------------------------------------------------
|
|
83
|
-
// Sync form values if initialData props change
|
|
84
|
-
// ---------------------------------------------------------------
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
const normalizedInitialData = {
|
|
87
|
-
audioUrl: apiData?.audioUrl || '',
|
|
88
|
-
expectedAnswers: apiData?.expectedAnswers?.length > 0 ? apiData.expectedAnswers : [''],
|
|
89
|
-
activeQuestionId: apiData?.activeQuestionId || undefined,
|
|
90
|
-
points: apiData?.points ?? 1,
|
|
91
|
-
explanation: apiData?.explanation || '',
|
|
92
|
-
isBackup: apiData?.isBackup || false,
|
|
93
|
-
};
|
|
94
|
-
const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
|
|
95
|
-
if (!initialDataChanged)
|
|
96
|
-
return;
|
|
97
|
-
previousInitialDataRef.current = initialData;
|
|
98
|
-
const currentFormData = {
|
|
99
|
-
audioUrl: watchedAudioUrl,
|
|
100
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
101
|
-
activeQuestionId: watchedActiveQuestionId,
|
|
102
|
-
points: watchedPoints,
|
|
103
|
-
explanation: watchedExplanation,
|
|
104
|
-
isBackup: watchedIsBackup,
|
|
105
|
-
};
|
|
106
|
-
const isEchoFromOnChange = currentFormData.points === normalizedInitialData.points &&
|
|
107
|
-
currentFormData.explanation === normalizedInitialData.explanation &&
|
|
108
|
-
currentFormData.audioUrl === normalizedInitialData.audioUrl &&
|
|
109
|
-
currentFormData.activeQuestionId === normalizedInitialData.activeQuestionId &&
|
|
110
|
-
JSON.stringify(currentFormData.expectedAnswers) === JSON.stringify(normalizedInitialData.expectedAnswers);
|
|
111
|
-
const wasJustSaved = initialData?.isCompleted === true;
|
|
112
|
-
if (isEchoFromOnChange && wasJustSaved)
|
|
113
|
-
return;
|
|
114
|
-
const shouldReset = !isEchoFromOnChange &&
|
|
115
|
-
(normalizedInitialData.audioUrl !== currentFormData.audioUrl ||
|
|
116
|
-
normalizedInitialData.activeQuestionId !== currentFormData.activeQuestionId ||
|
|
117
|
-
JSON.stringify(normalizedInitialData.expectedAnswers) !== JSON.stringify(currentFormData.expectedAnswers));
|
|
118
|
-
if (shouldReset) {
|
|
119
|
-
isInitializingRef.current = true;
|
|
120
|
-
form.reset(normalizedInitialData);
|
|
121
|
-
setTimeout(() => {
|
|
122
|
-
isInitializingRef.current = false;
|
|
123
|
-
}, 150);
|
|
124
|
-
}
|
|
125
|
-
}, [initialData, form, watchedAudioUrl, watchedExpectedAnswers, watchedExplanation, watchedPoints, watchedActiveQuestionId, watchedIsBackup]);
|
|
126
|
-
// ---------------------------------------------------------------
|
|
127
|
-
// Fire onChange when form values change
|
|
128
|
-
// ---------------------------------------------------------------
|
|
129
|
-
const watchedExpectedAnswersString = JSON.stringify(watchedExpectedAnswers);
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
if (isInitializingRef.current) {
|
|
132
|
-
const currentPayload = JSON.stringify({
|
|
133
|
-
audioUrl: watchedAudioUrl,
|
|
134
|
-
activeQuestionId: watchedActiveQuestionId,
|
|
135
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
136
|
-
points: watchedPoints,
|
|
137
|
-
explanation: watchedExplanation,
|
|
138
|
-
isBackup: watchedIsBackup,
|
|
139
|
-
questions,
|
|
140
|
-
groupNumber: isGrouped ? currentGroupNumber : undefined,
|
|
141
|
-
});
|
|
142
|
-
previousPayloadRef.current = currentPayload;
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
const payload = {
|
|
146
|
-
audioUrl: watchedAudioUrl,
|
|
147
|
-
activeQuestionId: watchedActiveQuestionId,
|
|
148
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
149
|
-
points: watchedPoints,
|
|
150
|
-
explanation: watchedExplanation,
|
|
151
|
-
isBackup: watchedIsBackup,
|
|
152
|
-
questions,
|
|
153
|
-
groupNumber: isGrouped ? currentGroupNumber : undefined,
|
|
154
|
-
};
|
|
155
|
-
const payloadString = JSON.stringify(payload);
|
|
156
|
-
if (previousPayloadRef.current !== payloadString) {
|
|
157
|
-
previousPayloadRef.current = payloadString;
|
|
158
|
-
debouncedOnChange(payload);
|
|
159
|
-
}
|
|
160
|
-
}, [
|
|
161
|
-
watchedAudioUrl,
|
|
162
|
-
watchedActiveQuestionId,
|
|
163
|
-
watchedExpectedAnswersString,
|
|
164
|
-
watchedPoints,
|
|
165
|
-
watchedExplanation,
|
|
166
|
-
watchedIsBackup,
|
|
167
|
-
questions,
|
|
168
|
-
isGrouped,
|
|
169
|
-
currentGroupNumber,
|
|
170
|
-
debouncedOnChange,
|
|
171
|
-
]);
|
|
172
|
-
// Add validation ref
|
|
173
|
-
useEffect(() => {
|
|
174
|
-
if (validationRef) {
|
|
175
|
-
validationRef.current = {
|
|
176
|
-
triggerValidation: async () => {
|
|
177
|
-
const isQuestionValid = await trigger();
|
|
178
|
-
let isGroupValid = true;
|
|
179
|
-
if (isGrouped && isFirstInGroup) {
|
|
180
|
-
isGroupValid = questions.length > 0 && questions.every(q => q.text.trim().length > 0);
|
|
181
|
-
}
|
|
182
|
-
return isQuestionValid && isGroupValid;
|
|
183
|
-
},
|
|
184
|
-
getFormData: () => ({
|
|
185
|
-
...form.getValues(),
|
|
186
|
-
questions,
|
|
187
|
-
groupNumber: isGrouped ? currentGroupNumber : undefined,
|
|
188
|
-
}),
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
}, [trigger, validationRef, isFirstInGroup, questions, isGrouped, currentGroupNumber, form]);
|
|
192
|
-
// Handlers for Group Questions List
|
|
193
|
-
const handleAddListQuestion = () => {
|
|
194
|
-
setQuestions([
|
|
195
|
-
...questions,
|
|
196
|
-
{ id: generateObjectId(), text: '' }
|
|
197
|
-
]);
|
|
198
|
-
};
|
|
199
|
-
const handleRemoveListQuestion = (index) => {
|
|
200
|
-
const newQuestions = questions.filter((_, i) => i !== index);
|
|
201
|
-
setQuestions(newQuestions);
|
|
202
|
-
if (watchedActiveQuestionId && !newQuestions.find(q => q.id === watchedActiveQuestionId)) {
|
|
203
|
-
setValue('activeQuestionId', '', { shouldValidate: true, shouldDirty: true });
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
const handleUpdateListQuestion = (index, text) => {
|
|
207
|
-
const newQuestions = [...questions];
|
|
208
|
-
newQuestions[index] = { ...newQuestions[index], text };
|
|
209
|
-
setQuestions(newQuestions);
|
|
210
|
-
};
|
|
211
|
-
// Handlers for Expected Answers
|
|
212
|
-
const addExpectedAnswer = () => {
|
|
213
|
-
setValue('expectedAnswers', [...watchedExpectedAnswers, ''], { shouldValidate: true, shouldDirty: true });
|
|
214
|
-
};
|
|
215
|
-
const handleExpectedAnswerChange = (index, value) => {
|
|
216
|
-
const newAnswers = [...watchedExpectedAnswers];
|
|
217
|
-
newAnswers[index] = value;
|
|
218
|
-
setValue('expectedAnswers', newAnswers, { shouldValidate: true, shouldDirty: true });
|
|
219
|
-
};
|
|
220
|
-
const removeExpectedAnswer = (index) => {
|
|
221
|
-
if (watchedExpectedAnswers.length > 1) {
|
|
222
|
-
const newAnswers = watchedExpectedAnswers.filter((_, i) => i !== index);
|
|
223
|
-
setValue('expectedAnswers', newAnswers, { shouldValidate: true, shouldDirty: true });
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
// Compute display question number safely
|
|
227
|
-
const displayQuestionNumber = typeof questionIndex === 'number' && !isNaN(questionIndex)
|
|
228
|
-
? questionIndex + 1
|
|
229
|
-
: (typeof questionIndexInPart === 'number' && !isNaN(questionIndexInPart)
|
|
230
|
-
? questionIndexInPart + 1
|
|
231
|
-
: '?');
|
|
232
|
-
// ─── Client Mode Preview ──────────────────────────────────────────────────
|
|
233
|
-
if (isClientMode) {
|
|
234
|
-
const questionData = {
|
|
235
|
-
audioUrl: watchedAudioUrl,
|
|
236
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
237
|
-
explanation: watchedExplanation,
|
|
238
|
-
points: watchedPoints,
|
|
239
|
-
activeQuestionId: watchedActiveQuestionId,
|
|
240
|
-
isBackup: watchedIsBackup,
|
|
241
|
-
questions,
|
|
242
|
-
groupNumber: isGrouped ? currentGroupNumber : undefined,
|
|
243
|
-
};
|
|
244
|
-
return (_jsxs("div", { className: "space-y-6", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Nghe & Tr\u1EA3 l\u1EDDi theo danh s\u00E1ch c\u00E2u h\u1ECFi (Preview)" }), _jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Ch\u1EBF \u0111\u1ED9 xem tr\u01B0\u1EDBc" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(false), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Quay l\u1EA1i Edit Mode"] })] }) }) }), _jsx(ListenAndSpeakQuestionListClient, { questionData: questionData, isReviewMode: true })] }));
|
|
245
|
-
}
|
|
246
|
-
// ─── Creator UI ────────────────────────────────────────────────────────────
|
|
247
|
-
return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "blue", steps: [
|
|
248
|
-
{ text: 'Nhập danh sách câu hỏi cho nhóm — câu hỏi đầu tiên sẽ quản lý danh sách chung' },
|
|
249
|
-
{ text: 'Upload audio câu hỏi cho từng câu trong nhóm' },
|
|
250
|
-
{ text: 'Chọn câu hỏi đang active (active question) tương ứng với câu đang hỏi', highlight: true },
|
|
251
|
-
{ text: 'Nhập các đáp án mong muốn cho mỗi câu' },
|
|
252
|
-
{ text: 'Thêm giải thích nếu cần thiết' },
|
|
253
|
-
], tips: ['Câu hỏi nên ngắn gọn, rõ ràng và phù hợp với trình độ học sinh'] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 shadow-sm", children: _jsx(ListChecks, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-lg font-bold text-gray-900", children: "Nh\u00F3m \u0111\u00E1p \u00E1n" }), _jsxs("p", { className: "text-sm text-gray-500", children: ["Nh\u00F3m ", currentGroupNumber, " \u2014 D\u00F9ng chung cho t\u1EA5t c\u1EA3 c\u00E2u h\u1ECFi"] })] })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: !watchedAudioUrl || watchedExpectedAnswers.filter((a) => a.trim()).length === 0, className: "gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 disabled:opacity-50", children: [_jsx(Eye, { className: "h-4 w-4" }), "Preview"] })] }) })] }), isFirstInGroup ? (_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-cyan-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-cyan-400 to-sky-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-cyan-100", children: _jsx(ListChecks, { className: "h-3.5 w-3.5 text-cyan-600" }) }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-bold text-gray-900", children: "Danh s\u00E1ch c\u00E2u h\u1ECFi" }), _jsxs("span", { className: "inline-flex items-center rounded-full border border-indigo-300 bg-indigo-100 px-2.5 py-0.5 text-xs font-semibold text-indigo-700", children: ["Nh\u00F3m ", currentGroupNumber] })] }), _jsx("p", { className: "text-sm text-gray-500", children: "D\u00F9ng chung cho t\u1EA5t c\u1EA3 c\u00E2u h\u1ECFi trong nh\u00F3m \u2014 Th\u00EAm c\u00E1c c\u00E2u h\u1ECFi text \u0111\u1EC3 h\u1ECDc sinh ch\u1ECDn v\u00E0 tr\u1EA3 l\u1EDDi" })] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsx("div", { className: "space-y-3", children: questions.map((q, index) => (_jsxs("div", { className: "flex gap-3 items-center", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-indigo-100 text-indigo-700 font-semibold text-sm shadow-sm", children: index + 1 }), _jsx(Input, { value: q.text, onChange: (e) => handleUpdateListQuestion(index, e.target.value), placeholder: "VD: Now, how old are you?", className: "flex-1 border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200" }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => handleRemoveListQuestion(index), className: "shrink-0 text-gray-400 hover:text-red-600 hover:bg-red-50", "aria-label": `Xóa câu hỏi ${index + 1}`, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, q.id))) }), questions.length === 0 && (_jsxs("div", { className: "flex flex-col items-center justify-center rounded-xl border-2 border-dashed border-indigo-200 bg-indigo-50/50 py-8 px-4 text-center", children: [_jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-indigo-100 mb-3", children: _jsx(ListChecks, { className: "h-6 w-6 text-indigo-500" }) }), _jsx("p", { className: "text-sm font-medium text-gray-700", children: "Ch\u01B0a c\u00F3 c\u00E2u h\u1ECFi n\u00E0o" }), _jsx("p", { className: "text-xs text-gray-500 mt-1", children: "Vui l\u00F2ng th\u00EAm \u00EDt nh\u1EA5t m\u1ED9t c\u00E2u h\u1ECFi cho nh\u00F3m n\u00E0y" })] })), _jsxs(Button, { type: "button", variant: "outline", onClick: handleAddListQuestion, className: "w-full gap-2 border-indigo-200 text-indigo-600 hover:bg-indigo-50 hover:border-indigo-300", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm c\u00E2u h\u1ECFi"] })] })] })) : (
|
|
254
|
-
/* Read-only view of the shared question list for non-first questions in the group */
|
|
255
|
-
_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow shadow-indigo-100/30", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-indigo-400 via-blue-400 to-cyan-400" }), _jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-indigo-100 shadow-sm", children: _jsx(ListChecks, { className: "h-4 w-4 text-indigo-600" }) }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-base font-semibold text-gray-800", children: "Danh s\u00E1ch c\u00E2u h\u1ECFi" }), _jsxs("span", { className: "inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-500", children: ["Nh\u00F3m ", currentGroupNumber, " \u2014 Ch\u1EC9 \u0111\u1ECDc"] })] }), _jsx("p", { className: "text-xs text-gray-400 mt-0.5", children: "\u0110\u01B0\u1EE3c qu\u1EA3n l\u00FD b\u1EDFi c\u00E2u h\u1ECFi \u0111\u1EA7u ti\u00EAn trong nh\u00F3m" })] })] }) }), _jsx(CardContent, { className: "pt-0", children: questions.length > 0 ? (_jsx("div", { className: "space-y-2", children: questions.map((q, index) => (_jsxs("div", { className: "flex gap-2.5 items-center px-3 py-2 rounded-lg bg-gray-50 border border-gray-100", children: [_jsx("div", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md bg-indigo-50 text-indigo-600 font-semibold text-xs", children: index + 1 }), _jsx("span", { className: "text-sm text-gray-700", children: q.text || _jsx("span", { className: "italic text-gray-400", children: "(Ch\u01B0a nh\u1EADp n\u1ED9i dung)" }) })] }, q.id))) })) : (_jsx("div", { className: "text-center py-4 text-sm text-gray-400 italic", children: "Ch\u01B0a c\u00F3 c\u00E2u h\u1ECFi n\u00E0o trong nh\u00F3m" })) })] })), _jsxs(Form, { ...form, children: [_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 shadow-sm", children: _jsx(Mic, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-lg font-bold text-gray-900", children: "C\u00E2u h\u1ECFi Nghe & Tr\u1EA3 l\u1EDDi" }), _jsx("p", { className: "text-sm text-gray-500", children: "H\u1ECDc sinh nghe \u00E2m thanh v\u00E0 n\u00F3i c\u00E2u tr\u1EA3 l\u1EDDi" })] })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsx("div", { className: "rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4", children: _jsx("div", { className: "flex flex-col gap-4", children: _jsx("div", { className: "w-full md:w-1/2", children: _jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), error: form.formState.errors?.points?.message }) }) }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(MessageSquareText, { className: "h-4 w-4 text-indigo-500" }), "C\u00E2u h\u1ECFi \u0111\u01B0\u1EE3c h\u1ECFi trong audio ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2.5", children: [_jsx(Info, { className: "h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0" }), _jsxs("div", { className: "text-xs text-blue-700 space-y-1", children: [_jsx("p", { children: "Ch\u1ECDn c\u00E2u h\u1ECFi m\u00E0 h\u1ECDc sinh s\u1EBD nghe trong \u0111o\u1EA1n audio n\u00E0y." }), _jsxs("p", { children: [_jsx("span", { className: "font-semibold", children: "\u2022 M\u1ED7i audio t\u01B0\u01A1ng \u1EE9ng m\u1ED9t c\u00E2u h\u1ECFi:" }), " X\u00E1c \u0111\u1ECBnh c\u00E2u h\u1ECFi n\u00E0o t\u1EEB danh s\u00E1ch \u0111ang \u0111\u01B0\u1EE3c h\u1ECFi trong audio."] }), _jsxs("p", { children: [_jsx("span", { className: "font-semibold", children: "\u2022 Hi\u1EC3n th\u1ECB cho h\u1ECDc sinh:" }), " H\u1ECDc sinh s\u1EBD th\u1EA5y danh s\u00E1ch c\u00E2u h\u1ECFi v\u00E0 c\u1EA7n ch\u1ECDn \u0111\u00FAng c\u00E2u h\u1ECFi kh\u1EDBp v\u1EDBi audio \u0111\u1EC3 tr\u1EA3 l\u1EDDi."] })] })] }), _jsx(FormField, { control: control, name: "activeQuestionId", render: ({ field }) => (_jsxs(FormItem, { children: [_jsxs(Select, { value: field.value || '', onValueChange: field.onChange, children: [_jsx(FormControl, { children: _jsx(SelectTrigger, { className: "w-full bg-white border-gray-200 transition-all focus:border-indigo-300 focus:ring-indigo-200", children: _jsx(SelectValue, { placeholder: "Ch\u1ECDn c\u00E2u h\u1ECFi t\u1EEB danh s\u00E1ch" }) }) }), _jsxs(SelectContent, { children: [questions.map((q, idx) => (_jsxs(SelectItem, { value: q.id, children: [idx + 1, ". ", q.text || '(Chưa nhập nội dung)'] }, q.id))), questions.length === 0 && (_jsx("div", { className: "p-3 text-sm text-gray-500 italic text-center", children: "Vui l\u00F2ng t\u1EA1o c\u00E2u h\u1ECFi \u1EDF nh\u00F3m tr\u00EAn tr\u01B0\u1EDBc" }))] })] }), _jsx(FormMessage, {})] })) })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(Volume2, { className: "h-4 w-4 text-indigo-500" }), "\u00C2m thanh c\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FormField, { control: control, name: "audioUrl", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormControl, { children: _jsx(FileUpload, { id: `audio-question-${displayQuestionNumber}`, label: "", accept: "audio/*", value: field.value, onChange: (url) => { field.onChange(url); clearErrors('audioUrl'); }, maxSize: 10, placeholder: "T\u1EA3i l\u00EAn file \u00E2m thanh (mp3, wav...)", autoUpload: true, prefix: "listen-and-speak" }) }), _jsx(FormMessage, {})] })) })] })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-green-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-green-400 to-emerald-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 shadow-sm", children: _jsx(MessageSquareText, { className: "h-4 w-4 text-white" }) }), _jsx("div", { children: _jsxs(CardTitle, { className: "text-lg font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n mong mu\u1ED1n (", watchedExpectedAnswers.filter((a) => a.trim()).length, ")"] }) })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addExpectedAnswer, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n"] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2.5", children: [_jsx(Info, { className: "h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0" }), _jsxs("div", { className: "text-xs text-blue-700 space-y-1", children: [_jsx("p", { children: "Nh\u1EADp c\u00E1c c\u00E2u tr\u1EA3 l\u1EDDi mong mu\u1ED1n. C\u00F3 th\u1EC3 th\u00EAm nhi\u1EC1u \u0111\u00E1p \u00E1n n\u1EBFu c\u00F3 nhi\u1EC1u c\u00E1ch tr\u1EA3 l\u1EDDi \u0111\u00FAng." }), _jsxs("p", { children: [_jsx("span", { className: "font-semibold", children: "\u2022 Ch\u1EA5m th\u1EE7 c\u00F4ng:" }), " Gi\u00E1o vi\u00EAn d\u00F9ng c\u00E1c \u0111\u00E1p \u00E1n n\u00E0y l\u00E0m tham kh\u1EA3o \u0111\u1EC3 ch\u1EA5m \u0111i\u1EC3m."] }), _jsxs("p", { children: [_jsx("span", { className: "font-semibold", children: "\u2022 Ch\u1EA5m t\u1EF1 \u0111\u1ED9ng (AI):" }), " H\u1EC7 th\u1ED1ng d\u00F9ng AI so s\u00E1nh gi\u1ECDng n\u00F3i c\u1EE7a h\u1ECDc sinh v\u1EDBi c\u00E1c \u0111\u00E1p \u00E1n \u0111\u1EC3 t\u1EF1 \u0111\u1ED9ng ch\u1EA5m \u0111i\u1EC3m."] })] })] }), _jsx(FormField, { control: control, name: "expectedAnswers", render: () => (_jsxs(FormItem, { children: [_jsx("div", { className: "space-y-3", children: watchedExpectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-start gap-3 group/answer", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 font-semibold shadow-sm text-sm", children: index + 1 }), _jsxs("div", { className: "flex-1", children: [_jsx(Input, { placeholder: `Ví dụ: ${index === 0 ? 'I am doing well' : 'I feel great'}`, value: answer || '', onChange: (e) => handleExpectedAnswerChange(index, e.target.value), className: "border-gray-200 focus:border-green-300 focus:ring-green-200" }), form.formState.errors?.expectedAnswers?.[index]?.message && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.expectedAnswers[index]?.message }))] }), watchedExpectedAnswers.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeExpectedAnswer(index), className: "text-red-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity", "aria-label": `Xóa đáp án ${index + 1}`, children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }, index))) }), form.formState.errors?.expectedAnswers && !Array.isArray(form.formState.errors.expectedAnswers) && (_jsx("p", { className: "text-sm text-red-500", children: form.formState.errors.expectedAnswers.message }))] })) })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-amber-400 to-orange-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-sm text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch cho \u0111\u00E1p \u00E1n \u0111\u00FAng \u2014 hi\u1EC3n th\u1ECB sau khi h\u1ECDc sinh ho\u00E0n th\u00E0nh c\u00E2u h\u1ECFi" })] })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { ...form.register('explanation'), placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0 'I am 7 years old' v\u00EC c\u00E2u h\u1ECFi h\u1ECFi v\u1EC1 tu\u1ED5i...", className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none", rows: 3 }) })] })] })] }));
|
|
256
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useEffect as i,useMemo as n,useRef as r,useState as s}from"react";import{Card as a,CardContent as l,CardHeader as c,CardTitle as o}from"../../../../components/ui/card";import{Label as d}from"../../../../components/ui/label";import{Button as h}from"../../../../components/ui/button";import{Input as m}from"../../../../components/ui/input";import{Textarea as u}from"../../../../components/ui/textarea";import{Form as g,FormControl as p,FormField as x,FormItem as f,FormMessage as b}from"../../../../components/ui/form";import{Select as N,SelectContent as v,SelectItem as w,SelectTrigger as y,SelectValue as k}from"../../../../components/ui/select";import{FileUpload as C}from"../../../../components/ui/file-upload";import{PointsInput as A}from"../../../../components/ui/points-input";import{useQuestionListGroup as I}from"../../_shared/hooks/useQuestionListGroup";import{generateObjectId as Q}from"../../_shared/utils/id-generator";import{ListenAndSpeakQuestionListClient as S}from"./ListenAndSpeakQuestionListClient";import{listenAndSpeakQuestionListFormSchema as j}from"../../../../shared/lib/validation/listen-and-speak-question-list.validation";import{Plus as U,Trash2 as q,Mic as D,Volume2 as V,ListChecks as B,MessageSquareText as J,Info as O,BookOpen as z,Eye as T,Pencil as G}from"lucide-react";import{useReactHookForm as H}from"../../../../shared/lib/hooks/useReactHookForm";import{useDebouncedCallback as L}from"../../../../shared/lib/hooks/useDebouncedCallback";import{CreatorGuide as P}from"../../components/CreatorGuide";export function ListenAndSpeakQuestionListCreator({questionIndex:F,initialData:M,onChange:R,validationRef:X,isFirstInGroup:$,partId:E,groups:_,questionIndexInPart:K}){const[W,Y]=s(!1),Z=Boolean(_&&_.length>0),ee=n(()=>{if(void 0!==$)return $;if(!_||!_.length||void 0===K)return!0;let e=0;for(const t of _){if(K===e)return!0;e+=t.questionCount}return!1},[$,_,K]),{questions:te,setQuestions:ie,currentGroupNumber:ne}=I({partId:E,groups:_,questionIndexInPart:K,initialData:M?.groupQuestions?{questions:M.groupQuestions}:void 0}),re=M,se=H({zodSchema:j,defaultValues:{audioUrl:re?.audioUrl||"",expectedAnswers:re?.expectedAnswers?.length>0?re.expectedAnswers:[""],activeQuestionId:re?.activeQuestionId||void 0,points:re?.points??1,explanation:re?.explanation||"",isBackup:re?.isBackup||!1}}),{control:ae,watch:le,setValue:ce,trigger:oe,clearErrors:de}=se,he=le("audioUrl"),me=le("activeQuestionId"),ue=le("expectedAnswers"),ge=le("points"),pe=le("explanation"),xe=le("isBackup"),fe=r(M),be=r(!1),Ne=r(R),ve=r("");i(()=>{Ne.current=R},[R]);const we=L(e=>{Ne.current?.(e)},300);i(()=>{const e={audioUrl:re?.audioUrl||"",expectedAnswers:re?.expectedAnswers?.length>0?re.expectedAnswers:[""],activeQuestionId:re?.activeQuestionId||void 0,points:re?.points??1,explanation:re?.explanation||"",isBackup:re?.isBackup||!1};if(!(JSON.stringify(fe.current)!==JSON.stringify(M)))return;fe.current=M;const t={audioUrl:he,expectedAnswers:ue,activeQuestionId:me,points:ge,explanation:pe,isBackup:xe},i=t.points===e.points&&t.explanation===e.explanation&&t.audioUrl===e.audioUrl&&t.activeQuestionId===e.activeQuestionId&&JSON.stringify(t.expectedAnswers)===JSON.stringify(e.expectedAnswers);if(i&&!0===M?.isCompleted)return;!i&&(e.audioUrl!==t.audioUrl||e.activeQuestionId!==t.activeQuestionId||JSON.stringify(e.expectedAnswers)!==JSON.stringify(t.expectedAnswers))&&(be.current=!0,se.reset(e),setTimeout(()=>{be.current=!1},150))},[M,se,he,ue,pe,ge,me,xe]);const ye=JSON.stringify(ue);i(()=>{if(be.current){const e=JSON.stringify({audioUrl:he,activeQuestionId:me,expectedAnswers:ue,points:ge,explanation:pe,isBackup:xe,questions:te,groupNumber:Z?ne:void 0});return void(ve.current=e)}const e={audioUrl:he,activeQuestionId:me,expectedAnswers:ue,points:ge,explanation:pe,isBackup:xe,questions:te,groupNumber:Z?ne:void 0},t=JSON.stringify(e);ve.current!==t&&(ve.current=t,we(e))},[he,me,ye,ge,pe,xe,te,Z,ne,we]),i(()=>{X&&(X.current={triggerValidation:async()=>{const e=await oe();let t=!0;return Z&&ee&&(t=te.length>0&&te.every(e=>e.text.trim().length>0)),e&&t},getFormData:()=>({...se.getValues(),questions:te,groupNumber:Z?ne:void 0})})},[oe,X,ee,te,Z,ne,se]);const ke="number"!=typeof F||isNaN(F)?"number"!=typeof K||isNaN(K)?"?":K+1:F+1;if(W){const i={audioUrl:he,expectedAnswers:ue,explanation:pe,points:ge,activeQuestionId:me,isBackup:xe,questions:te,groupNumber:Z?ne:void 0};return t("div",{className:"space-y-6",children:[e(a,{children:e(c,{children:t("div",{className:"flex items-center justify-between",children:[t("div",{children:[e(o,{children:"Nghe & Trả lời theo danh sách câu hỏi (Preview)"}),e("p",{className:"mt-1 text-sm text-gray-500",children:"Chế độ xem trước"})]}),t(h,{variant:"outline",size:"sm",onClick:()=>Y(!1),children:[e(G,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})})}),e(S,{questionData:i,isReviewMode:!0})]})}return t("div",{className:"space-y-6",children:[e(P,{colorScheme:"blue",steps:[{text:"Nhập danh sách câu hỏi cho nhóm — câu hỏi đầu tiên sẽ quản lý danh sách chung"},{text:"Upload audio câu hỏi cho từng câu trong nhóm"},{text:"Chọn câu hỏi đang active (active question) tương ứng với câu đang hỏi",highlight:!0},{text:"Nhập các đáp án mong muốn cho mỗi câu"},{text:"Thêm giải thích nếu cần thiết"}],tips:["Câu hỏi nên ngắn gọn, rõ ràng và phù hợp với trình độ học sinh"]}),t(a,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500"}),e(c,{className:"pb-4",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 shadow-sm",children:e(B,{className:"h-4 w-4 text-white"})}),t("div",{children:[e(o,{className:"text-lg font-bold text-gray-900",children:"Nhóm đáp án"}),t("p",{className:"text-sm text-gray-500",children:["Nhóm ",ne," — Dùng chung cho tất cả câu hỏi"]})]})]}),t(h,{type:"button",variant:"outline",size:"sm",onClick:()=>Y(!0),disabled:!he||0===ue.filter(e=>e.trim()).length,className:"gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 disabled:opacity-50",children:[e(T,{className:"h-4 w-4"}),"Preview"]})]})})]}),t(a,ee?{className:"overflow-hidden border-0 bg-white shadow-lg shadow-cyan-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-cyan-400 to-sky-400"}),e(c,{className:"pb-4",children:t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-cyan-100",children:e(B,{className:"h-3.5 w-3.5 text-cyan-600"})}),t("div",{className:"flex-1",children:[t("div",{className:"flex items-center gap-2",children:[e(o,{className:"text-lg font-bold text-gray-900",children:"Danh sách câu hỏi"}),t("span",{className:"inline-flex items-center rounded-full border border-indigo-300 bg-indigo-100 px-2.5 py-0.5 text-xs font-semibold text-indigo-700",children:["Nhóm ",ne]})]}),e("p",{className:"text-sm text-gray-500",children:"Dùng chung cho tất cả câu hỏi trong nhóm — Thêm các câu hỏi text để học sinh chọn và trả lời"})]})]})}),t(l,{className:"space-y-4",children:[e("div",{className:"space-y-3",children:te.map((i,n)=>t("div",{className:"flex gap-3 items-center",children:[e("div",{className:"flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-indigo-100 text-indigo-700 font-semibold text-sm shadow-sm",children:n+1}),e(m,{value:i.text,onChange:e=>((e,t)=>{const i=[...te];i[e]={...i[e],text:t},ie(i)})(n,e.target.value),placeholder:"VD: Now, how old are you?",className:"flex-1 border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200"}),e(h,{type:"button",variant:"ghost",size:"sm",onClick:()=>(e=>{const t=te.filter((t,i)=>i!==e);ie(t),me&&!t.find(e=>e.id===me)&&ce("activeQuestionId","",{shouldValidate:!0,shouldDirty:!0})})(n),className:"shrink-0 text-gray-400 hover:text-red-600 hover:bg-red-50","aria-label":`Xóa câu hỏi ${n+1}`,children:e(q,{className:"h-4 w-4"})})]},i.id))}),0===te.length&&t("div",{className:"flex flex-col items-center justify-center rounded-xl border-2 border-dashed border-indigo-200 bg-indigo-50/50 py-8 px-4 text-center",children:[e("div",{className:"flex h-12 w-12 items-center justify-center rounded-full bg-indigo-100 mb-3",children:e(B,{className:"h-6 w-6 text-indigo-500"})}),e("p",{className:"text-sm font-medium text-gray-700",children:"Chưa có câu hỏi nào"}),e("p",{className:"text-xs text-gray-500 mt-1",children:"Vui lòng thêm ít nhất một câu hỏi cho nhóm này"})]}),t(h,{type:"button",variant:"outline",onClick:()=>{ie([...te,{id:Q(),text:""}])},className:"w-full gap-2 border-indigo-200 text-indigo-600 hover:bg-indigo-50 hover:border-indigo-300",children:[e(U,{className:"h-4 w-4"}),"Thêm câu hỏi"]})]})]}:{className:"overflow-hidden border-0 bg-white shadow shadow-indigo-100/30",children:[e("div",{className:"h-1 bg-gradient-to-r from-indigo-400 via-blue-400 to-cyan-400"}),e(c,{className:"pb-3",children:t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-indigo-100 shadow-sm",children:e(B,{className:"h-4 w-4 text-indigo-600"})}),t("div",{className:"flex-1",children:[t("div",{className:"flex items-center gap-2",children:[e(o,{className:"text-base font-semibold text-gray-800",children:"Danh sách câu hỏi"}),t("span",{className:"inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-500",children:["Nhóm ",ne," — Chỉ đọc"]})]}),e("p",{className:"text-xs text-gray-400 mt-0.5",children:"Được quản lý bởi câu hỏi đầu tiên trong nhóm"})]})]})}),e(l,{className:"pt-0",children:te.length>0?e("div",{className:"space-y-2",children:te.map((i,n)=>t("div",{className:"flex gap-2.5 items-center px-3 py-2 rounded-lg bg-gray-50 border border-gray-100",children:[e("div",{className:"flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md bg-indigo-50 text-indigo-600 font-semibold text-xs",children:n+1}),e("span",{className:"text-sm text-gray-700",children:i.text||e("span",{className:"italic text-gray-400",children:"(Chưa nhập nội dung)"})})]},i.id))}):e("div",{className:"text-center py-4 text-sm text-gray-400 italic",children:"Chưa có câu hỏi nào trong nhóm"})})]}),t(g,{...se,children:[t(a,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500"}),e(c,{className:"pb-4",children:t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 shadow-sm",children:e(D,{className:"h-4 w-4 text-white"})}),t("div",{children:[e(o,{className:"text-lg font-bold text-gray-900",children:"Câu hỏi Nghe & Trả lời"}),e("p",{className:"text-sm text-gray-500",children:"Học sinh nghe âm thanh và nói câu trả lời"})]})]})}),t(l,{className:"space-y-6",children:[e("div",{className:"rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4",children:e("div",{className:"flex flex-col gap-4",children:e("div",{className:"w-full md:w-1/2",children:e(A,{...se.register("points",{valueAsNumber:!0}),error:se.formState.errors?.points?.message})})})}),t("div",{className:"space-y-2",children:[t(d,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(J,{className:"h-4 w-4 text-indigo-500"}),"Câu hỏi được hỏi trong audio ",e("span",{className:"text-red-500",children:"*"})]}),t("div",{className:"flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2.5",children:[e(O,{className:"h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0"}),t("div",{className:"text-xs text-blue-700 space-y-1",children:[e("p",{children:"Chọn câu hỏi mà học sinh sẽ nghe trong đoạn audio này."}),t("p",{children:[e("span",{className:"font-semibold",children:"• Mỗi audio tương ứng một câu hỏi:"})," Xác định câu hỏi nào từ danh sách đang được hỏi trong audio."]}),t("p",{children:[e("span",{className:"font-semibold",children:"• Hiển thị cho học sinh:"})," Học sinh sẽ thấy danh sách câu hỏi và cần chọn đúng câu hỏi khớp với audio để trả lời."]})]})]}),e(x,{control:ae,name:"activeQuestionId",render:({field:i})=>t(f,{children:[t(N,{value:i.value||"",onValueChange:i.onChange,children:[e(p,{children:e(y,{className:"w-full bg-white border-gray-200 transition-all focus:border-indigo-300 focus:ring-indigo-200",children:e(k,{placeholder:"Chọn câu hỏi từ danh sách"})})}),t(v,{children:[te.map((e,i)=>t(w,{value:e.id,children:[i+1,". ",e.text||"(Chưa nhập nội dung)"]},e.id)),0===te.length&&e("div",{className:"p-3 text-sm text-gray-500 italic text-center",children:"Vui lòng tạo câu hỏi ở nhóm trên trước"})]})]}),e(b,{})]})})]}),t("div",{className:"space-y-2",children:[t(d,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(V,{className:"h-4 w-4 text-indigo-500"}),"Âm thanh câu hỏi ",e("span",{className:"text-red-500",children:"*"})]}),e(x,{control:ae,name:"audioUrl",render:({field:i})=>t(f,{children:[e(p,{children:e(C,{id:`audio-question-${ke}`,label:"",accept:"audio/*",value:i.value,onChange:e=>{i.onChange(e),de("audioUrl")},maxSize:10,placeholder:"Tải lên file âm thanh (mp3, wav...)",autoUpload:!0,prefix:"listen-and-speak"})}),e(b,{})]})})]})]})]}),t(a,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-green-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-green-400 to-emerald-500"}),e(c,{className:"pb-4",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 shadow-sm",children:e(J,{className:"h-4 w-4 text-white"})}),e("div",{children:t(o,{className:"text-lg font-semibold text-gray-800",children:["Đáp án mong muốn (",ue.filter(e=>e.trim()).length,")"]})})]}),t(h,{type:"button",variant:"outline",size:"sm",onClick:()=>{ce("expectedAnswers",[...ue,""],{shouldValidate:!0,shouldDirty:!0})},className:"gap-2 border-green-200 text-green-600 hover:bg-green-50",children:[e(U,{className:"h-4 w-4"}),"Thêm đáp án"]})]})}),t(l,{className:"space-y-4",children:[t("div",{className:"flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2.5",children:[e(O,{className:"h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0"}),t("div",{className:"text-xs text-blue-700 space-y-1",children:[e("p",{children:"Nhập các câu trả lời mong muốn. Có thể thêm nhiều đáp án nếu có nhiều cách trả lời đúng."}),t("p",{children:[e("span",{className:"font-semibold",children:"• Chấm thủ công:"})," Giáo viên dùng các đáp án này làm tham khảo để chấm điểm."]}),t("p",{children:[e("span",{className:"font-semibold",children:"• Chấm tự động (AI):"})," Hệ thống dùng AI so sánh giọng nói của học sinh với các đáp án để tự động chấm điểm."]})]})]}),e(x,{control:ae,name:"expectedAnswers",render:()=>t(f,{children:[e("div",{className:"space-y-3",children:ue.map((i,n)=>t("div",{className:"flex items-start gap-3 group/answer",children:[e("div",{className:"flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 font-semibold shadow-sm text-sm",children:n+1}),t("div",{className:"flex-1",children:[e(m,{placeholder:"Ví dụ: "+(0===n?"I am doing well":"I feel great"),value:i||"",onChange:e=>((e,t)=>{const i=[...ue];i[e]=t,ce("expectedAnswers",i,{shouldValidate:!0,shouldDirty:!0})})(n,e.target.value),className:"border-gray-200 focus:border-green-300 focus:ring-green-200"}),se.formState.errors?.expectedAnswers?.[n]?.message&&e("p",{className:"mt-1 text-sm text-red-500",children:se.formState.errors.expectedAnswers[n]?.message})]}),ue.length>1&&e(h,{type:"button",variant:"ghost",size:"sm",onClick:()=>(e=>{if(ue.length>1){const t=ue.filter((t,i)=>i!==e);ce("expectedAnswers",t,{shouldValidate:!0,shouldDirty:!0})}})(n),className:"text-red-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity","aria-label":`Xóa đáp án ${n+1}`,children:e(q,{className:"h-4 w-4"})})]},n))}),se.formState.errors?.expectedAnswers&&!Array.isArray(se.formState.errors.expectedAnswers)&&e("p",{className:"text-sm text-red-500",children:se.formState.errors.expectedAnswers.message})]})})]})]}),t(a,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-amber-400 to-orange-400"}),e(c,{className:"pb-4",children:t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm",children:e(z,{className:"h-4 w-4 text-white"})}),t("div",{children:[t("div",{className:"flex items-center gap-2",children:[e(o,{className:"text-lg font-semibold text-gray-800",children:"Giải thích"}),e("span",{className:"inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600",children:"Không bắt buộc"})]}),e("p",{className:"text-sm text-gray-500 mt-0.5",children:"Giải thích cho đáp án đúng — hiển thị sau khi học sinh hoàn thành câu hỏi"})]})]})}),e(l,{children:e(u,{...se.register("explanation"),placeholder:"VD: Câu trả lời đúng là 'I am 7 years old' vì câu hỏi hỏi về tuổi...",className:"min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none",rows:3})})]})]})]})}
|
|
@@ -1,81 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const listenAndSpeakQuestionListRegistration = {
|
|
3
|
-
component: ListenAndSpeakQuestionListCreator,
|
|
4
|
-
transformInitialData: (initialData) => {
|
|
5
|
-
let creatorData = initialData?.answer;
|
|
6
|
-
const content = initialData?.content;
|
|
7
|
-
const answer = initialData?.answer || initialData?.correctAnswer || initialData?.correct_answer;
|
|
8
|
-
const points = initialData?.total_points
|
|
9
|
-
? parseFloat(initialData.total_points)
|
|
10
|
-
: (initialData?.points || content?.points || answer?.points || 1);
|
|
11
|
-
const explanation = initialData?.explanation || content?.explanation || answer?.explanation || '';
|
|
12
|
-
if (content && typeof content === 'object' && content.audioUrl !== undefined) {
|
|
13
|
-
creatorData = {
|
|
14
|
-
audioUrl: content.audioUrl || '',
|
|
15
|
-
activeQuestionId: content.activeQuestionId,
|
|
16
|
-
expectedAnswers: answer?.expectedAnswers || [],
|
|
17
|
-
explanation,
|
|
18
|
-
points,
|
|
19
|
-
isBackup: content.isBackup || false,
|
|
20
|
-
groupQuestions: initialData?.answer?.groupQuestions || [],
|
|
21
|
-
groupNumber: initialData?.answer?.groupNumber || 1,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
else if (answer && typeof answer === 'object' && answer.expectedAnswers !== undefined) {
|
|
25
|
-
creatorData = {
|
|
26
|
-
...answer,
|
|
27
|
-
audioUrl: answer.audioUrl || content?.audioUrl || '',
|
|
28
|
-
activeQuestionId: answer.activeQuestionId || content?.activeQuestionId,
|
|
29
|
-
explanation,
|
|
30
|
-
points,
|
|
31
|
-
isBackup: content?.isBackup || false,
|
|
32
|
-
groupQuestions: answer.groupQuestions || [],
|
|
33
|
-
groupNumber: answer.groupNumber || 1,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
return creatorData;
|
|
37
|
-
},
|
|
38
|
-
getExtraProps: (ctx) => ({
|
|
39
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
40
|
-
validationRef: ctx.validationRef,
|
|
41
|
-
groups: ctx.groups,
|
|
42
|
-
questionIndexInPart: ctx.questionIndexInPart,
|
|
43
|
-
partId: ctx.partId,
|
|
44
|
-
}),
|
|
45
|
-
wrapOnSave: (data, ctx) => ({
|
|
46
|
-
type: ctx.questionType,
|
|
47
|
-
content: {
|
|
48
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
|
|
49
|
-
audioUrl: data.audioUrl,
|
|
50
|
-
activeQuestionId: data.activeQuestionId,
|
|
51
|
-
isBackup: data.isBackup || false,
|
|
52
|
-
},
|
|
53
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
54
|
-
level: ctx.state.level,
|
|
55
|
-
difficulty: ctx.state.difficulty,
|
|
56
|
-
skill: ctx.state.skill,
|
|
57
|
-
answer: {
|
|
58
|
-
expectedAnswers: data.expectedAnswers,
|
|
59
|
-
},
|
|
60
|
-
explanation: data?.explanation || '',
|
|
61
|
-
...(data.groupNumber && { questionGroup: { payload: { questions: data.questions }, groupNumber: data.groupNumber } })
|
|
62
|
-
}),
|
|
63
|
-
wrapOnChange: (data, ctx) => ({
|
|
64
|
-
type: ctx.questionType,
|
|
65
|
-
content: {
|
|
66
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
|
|
67
|
-
audioUrl: data.audioUrl,
|
|
68
|
-
activeQuestionId: data.activeQuestionId,
|
|
69
|
-
isBackup: data.isBackup || false,
|
|
70
|
-
},
|
|
71
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
72
|
-
level: ctx.state.level,
|
|
73
|
-
difficulty: ctx.state.difficulty,
|
|
74
|
-
skill: ctx.state.skill,
|
|
75
|
-
answer: {
|
|
76
|
-
expectedAnswers: data.expectedAnswers,
|
|
77
|
-
},
|
|
78
|
-
explanation: data?.explanation || '',
|
|
79
|
-
...(data.groupNumber && { questionGroup: { payload: { questions: data.questions }, groupNumber: data.groupNumber } })
|
|
80
|
-
}),
|
|
81
|
-
};
|
|
1
|
+
import{ListenAndSpeakQuestionListCreator as e}from"./ListenAndSpeakQuestionListCreator";export const listenAndSpeakQuestionListRegistration={component:e,transformInitialData:e=>{let t=e?.answer;const n=e?.content,o=e?.answer||e?.correctAnswer||e?.correct_answer,s=e?.total_points?parseFloat(e.total_points):e?.points||n?.points||o?.points||1,a=e?.explanation||n?.explanation||o?.explanation||"";return n&&"object"==typeof n&&void 0!==n.audioUrl?t={audioUrl:n.audioUrl||"",activeQuestionId:n.activeQuestionId,expectedAnswers:o?.expectedAnswers||[],explanation:a,points:s,isBackup:n.isBackup||!1,groupQuestions:e?.answer?.groupQuestions||[],groupNumber:e?.answer?.groupNumber||1}:o&&"object"==typeof o&&void 0!==o.expectedAnswers&&(t={...o,audioUrl:o.audioUrl||n?.audioUrl||"",activeQuestionId:o.activeQuestionId||n?.activeQuestionId,explanation:a,points:s,isBackup:n?.isBackup||!1,groupQuestions:o.groupQuestions||[],groupNumber:o.groupNumber||1}),t},getExtraProps:e=>({onUnsavedChangesChange:e.onUnsavedChangesChange,validationRef:e.validationRef,groups:e.groups,questionIndexInPart:e.questionIndexInPart,partId:e.partId}),wrapOnSave:(e,t)=>({type:t.questionType,content:{..."object"==typeof t.state.content&&null!==t.state.content?t.state.content:{},audioUrl:e.audioUrl,activeQuestionId:e.activeQuestionId,isBackup:e.isBackup||!1},points:e?.points??t.state.points??1,level:t.state.level,difficulty:t.state.difficulty,skill:t.state.skill,answer:{expectedAnswers:e.expectedAnswers},explanation:e?.explanation||"",...e.groupNumber&&{questionGroup:{payload:{questions:e.questions},groupNumber:e.groupNumber}}}),wrapOnChange:(e,t)=>({type:t.questionType,content:{..."object"==typeof t.state.content&&null!==t.state.content?t.state.content:{},audioUrl:e.audioUrl,activeQuestionId:e.activeQuestionId,isBackup:e.isBackup||!1},points:e?.points??t.state.points??1,level:t.state.level,difficulty:t.state.difficulty,skill:t.state.skill,answer:{expectedAnswers:e.expectedAnswers},explanation:e?.explanation||"",...e.groupNumber&&{questionGroup:{payload:{questions:e.questions},groupNumber:e.groupNumber}}})};
|
|
@@ -1,26 +1 @@
|
|
|
1
|
-
export function transformListenAndSpeakQuestionList(
|
|
2
|
-
const answerData = question.answer;
|
|
3
|
-
// Group logic handling
|
|
4
|
-
const isValidGroup = answerData?.questions && answerData.questions.length > 0;
|
|
5
|
-
// Extract question list group data if it's a group
|
|
6
|
-
let questionListGroupData = undefined;
|
|
7
|
-
if (isValidGroup) {
|
|
8
|
-
questionListGroupData = {
|
|
9
|
-
questions: answerData?.questions || [],
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
return {
|
|
13
|
-
isExample: !!answerData?.isBackup,
|
|
14
|
-
apiContent: {
|
|
15
|
-
audioUrl: answerData?.audioUrl || '',
|
|
16
|
-
activeQuestionId: answerData?.activeQuestionId || '',
|
|
17
|
-
},
|
|
18
|
-
apiCorrectAnswer: {
|
|
19
|
-
expectedAnswers: answerData?.expectedAnswers || [],
|
|
20
|
-
},
|
|
21
|
-
totalPoints: question.points || 1,
|
|
22
|
-
// Using questionListGroupData for questionGroup.payload
|
|
23
|
-
questionListGroupData: isValidGroup ? questionListGroupData : undefined,
|
|
24
|
-
groupNumber: answerData?.groupNumber || 1,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
1
|
+
export function transformListenAndSpeakQuestionList(e){const t=e.answer,s=t?.questions&&t.questions.length>0;let n;return s&&(n={questions:t?.questions||[]}),{isExample:!!t?.isBackup,apiContent:{audioUrl:t?.audioUrl||"",activeQuestionId:t?.activeQuestionId||""},apiCorrectAnswer:{expectedAnswers:t?.expectedAnswers||[]},totalPoints:e.points||1,questionListGroupData:s?n:void 0,groupNumber:t?.groupNumber||1}}
|
|
@@ -1,26 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Mic, Volume2, CheckCircle2, Play, Pause, ImageIcon, Info, BookImage } from 'lucide-react';
|
|
4
|
-
import { Button } from '../../../../components/ui/button';
|
|
5
|
-
import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
|
|
6
|
-
// ─── Sub-component: Custom Audio Player ──────────────────────────────────────
|
|
7
|
-
function QuestionAudioPlayer({ audioUrl }) {
|
|
8
|
-
const { previewUrl } = usePresignedFileUrl(audioUrl);
|
|
9
|
-
const { isPlaying, currentTime, duration, progressPercentage, handlePlayPause, handleProgressClick, formatTime, } = useAudioPreview(previewUrl);
|
|
10
|
-
return (_jsxs("div", { className: "flex items-center gap-3 rounded-lg bg-gradient-to-r from-indigo-50 to-purple-50 border border-indigo-200 px-4 py-2.5", children: [_jsx(Button, { variant: "outline", size: "icon", onClick: handlePlayPause, disabled: !previewUrl, className: "h-8 w-8 border-indigo-300 text-indigo-600 hover:bg-indigo-100", children: isPlaying ? _jsx(Pause, { className: "h-3.5 w-3.5" }) : _jsx(Play, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "text-muted-foreground min-w-[70px] text-xs", children: [formatTime(currentTime), " / ", formatTime(duration)] }), _jsx("div", { className: "bg-indigo-200 relative h-1.5 flex-1 cursor-pointer rounded-full", onClick: handleProgressClick, children: _jsx("div", { className: "bg-indigo-500 absolute top-0 left-0 h-full rounded-full transition-all", style: { width: `${progressPercentage}%` } }) }), _jsx(Volume2, { className: "h-4 w-4 text-indigo-400" })] }));
|
|
11
|
-
}
|
|
12
|
-
// ─── Sub-component: Presigned image preview ──────────────────────────────────
|
|
13
|
-
function StoryImagePreview({ imageUrl, index }) {
|
|
14
|
-
const { previewUrl } = usePresignedFileUrl(imageUrl);
|
|
15
|
-
if (!previewUrl) {
|
|
16
|
-
return (_jsx("div", { className: "w-full h-full flex items-center justify-center bg-gray-100 text-gray-400", children: _jsx(ImageIcon, { className: "h-8 w-8" }) }));
|
|
17
|
-
}
|
|
18
|
-
return (_jsx("img", { src: previewUrl, alt: `Story image ${index + 1}`, className: "w-full h-full object-cover" }));
|
|
19
|
-
}
|
|
20
|
-
// ─── Main Client Component ───────────────────────────────────────────────────
|
|
21
|
-
export function ListenAndSpeakWithStoryImagesClient({ questionData, isReviewMode = false, }) {
|
|
22
|
-
const { audioUrl, images, expectedAnswers = [], explanation } = questionData;
|
|
23
|
-
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-indigo-50 to-blue-50 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-indigo-500 to-blue-600 text-white shadow-sm", children: _jsx(BookImage, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 n\u00F3i (K\u1EC3 chuy\u1EC7n theo h\u00ECnh)" })] }) }), _jsxs("div", { className: "p-5 space-y-5", children: [audioUrl ? (_jsx(QuestionAudioPlayer, { audioUrl: audioUrl })) : (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3", children: [_jsx(Volume2, { className: "h-4 w-4 text-gray-400" }), _jsx("span", { className: "text-xs text-gray-400", children: "Ch\u01B0a c\u00F3 audio c\u00E2u h\u1ECFi" })] })), images && images.length > 0 && (_jsx("div", { className: "bg-gradient-to-br from-purple-50/80 to-fuchsia-50/60 rounded-2xl p-6 border border-purple-100/80 shadow-sm", children: _jsx("div", { className: "grid gap-4", style: {
|
|
24
|
-
gridTemplateColumns: `repeat(${Math.min(images.length, 4)}, 1fr)`,
|
|
25
|
-
}, children: images.map((imgUrl, index) => (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "flex items-center gap-1.5", children: _jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-purple-200 text-purple-800 text-xs font-bold", children: index + 1 }) }), _jsx("div", { className: "relative aspect-square rounded-xl overflow-hidden border-[3px] border-purple-300 bg-white shadow-md ring-2 ring-purple-100", children: imgUrl ? (_jsx(StoryImagePreview, { imageUrl: imgUrl, index: index })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center text-gray-400", children: _jsx(ImageIcon, { className: "h-6 w-6" }) })) })] }, index))) }) })), _jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: "H\u1ECDc sinh s\u1EBD nh\u1EA5n n\u00FAt ghi \u00E2m v\u00E0 n\u00F3i c\u00E2u tr\u1EA3 l\u1EDDi t\u1EA1i \u0111\u00E2y" })] }), isReviewMode && expectedAnswers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsx("h4", { className: "text-sm font-semibold text-green-800", children: "C\u00E1c \u0111\u00E1p \u00E1n mong mu\u1ED1n" })] }), _jsx("div", { className: "space-y-2", children: expectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold", children: index + 1 }), _jsx("span", { className: "text-sm text-green-800", children: answer })] }, index))) })] })), isReviewMode && explanation && (_jsxs("div", { className: "rounded-xl border border-indigo-200 bg-indigo-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-indigo-600" }), _jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("p", { className: "text-sm text-indigo-700 leading-relaxed", children: explanation })] }))] })] })] }));
|
|
26
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Mic as i,Volume2 as s,CheckCircle2 as t,Play as l,Pause as n,ImageIcon as a,Info as d,BookImage as c}from"lucide-react";import{Button as o}from"../../../../components/ui/button";import{usePresignedFileUrl as m,useAudioPreview as g}from"../../../../shared/lib/hooks";function h({audioUrl:i}){const{previewUrl:t}=m(i),{isPlaying:a,currentTime:d,duration:c,progressPercentage:h,handlePlayPause:x,handleProgressClick:p,formatTime:u}=g(t);return r("div",{className:"flex items-center gap-3 rounded-lg bg-gradient-to-r from-indigo-50 to-purple-50 border border-indigo-200 px-4 py-2.5",children:[e(o,{variant:"outline",size:"icon",onClick:x,disabled:!t,className:"h-8 w-8 border-indigo-300 text-indigo-600 hover:bg-indigo-100",children:e(a?n:l,{className:"h-3.5 w-3.5"})}),r("div",{className:"text-muted-foreground min-w-[70px] text-xs",children:[u(d)," / ",u(c)]}),e("div",{className:"bg-indigo-200 relative h-1.5 flex-1 cursor-pointer rounded-full",onClick:p,children:e("div",{className:"bg-indigo-500 absolute top-0 left-0 h-full rounded-full transition-all",style:{width:`${h}%`}})}),e(s,{className:"h-4 w-4 text-indigo-400"})]})}function x({imageUrl:r,index:i}){const{previewUrl:s}=m(r);return s?e("img",{src:s,alt:`Story image ${i+1}`,className:"w-full h-full object-cover"}):e("div",{className:"w-full h-full flex items-center justify-center bg-gray-100 text-gray-400",children:e(a,{className:"h-8 w-8"})})}export function ListenAndSpeakWithStoryImagesClient({questionData:l,isReviewMode:n=!1}){const{audioUrl:o,images:m,expectedAnswers:g=[],explanation:p}=l;return r("div",{className:"space-y-4",children:[l.isBackup&&r("div",{className:"inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm",children:[e("span",{children:"🔄"})," Câu hỏi dự phòng"]}),r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:[e("div",{className:"flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-indigo-50 to-blue-50 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-indigo-500 to-blue-600 text-white shadow-sm",children:e(c,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Nghe và nói (Kể chuyện theo hình)"})]})}),r("div",{className:"p-5 space-y-5",children:[o?e(h,{audioUrl:o}):r("div",{className:"flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3",children:[e(s,{className:"h-4 w-4 text-gray-400"}),e("span",{className:"text-xs text-gray-400",children:"Chưa có audio câu hỏi"})]}),m&&m.length>0&&e("div",{className:"bg-gradient-to-br from-purple-50/80 to-fuchsia-50/60 rounded-2xl p-6 border border-purple-100/80 shadow-sm",children:e("div",{className:"grid gap-4",style:{gridTemplateColumns:`repeat(${Math.min(m.length,4)}, 1fr)`},children:m.map((i,s)=>r("div",{className:"space-y-2",children:[e("div",{className:"flex items-center gap-1.5",children:e("span",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-purple-200 text-purple-800 text-xs font-bold",children:s+1})}),e("div",{className:"relative aspect-square rounded-xl overflow-hidden border-[3px] border-purple-300 bg-white shadow-md ring-2 ring-purple-100",children:i?e(x,{imageUrl:i,index:s}):e("div",{className:"w-full h-full flex items-center justify-center text-gray-400",children:e(a,{className:"h-6 w-6"})})})]},s))})}),r("div",{className:"flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50",children:e(i,{className:"w-4 h-4 text-indigo-400"})}),e("p",{className:"text-xs text-gray-400",children:"Học sinh sẽ nhấn nút ghi âm và nói câu trả lời tại đây"})]}),n&&g.length>0&&r("div",{className:"rounded-xl border border-green-200 bg-green-50/80 p-4",children:[r("div",{className:"flex items-center gap-2 mb-3",children:[e(t,{className:"h-4 w-4 text-green-600"}),e("h4",{className:"text-sm font-semibold text-green-800",children:"Các đáp án mong muốn"})]}),e("div",{className:"space-y-2",children:g.map((i,s)=>r("div",{className:"flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2",children:[e("span",{className:"flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold",children:s+1}),e("span",{className:"text-sm text-green-800",children:i})]},s))})]}),n&&p&&r("div",{className:"rounded-xl border border-indigo-200 bg-indigo-50 p-4",children:[r("div",{className:"flex items-center gap-2 mb-2",children:[e(d,{className:"h-4 w-4 text-indigo-600"}),e("p",{className:"text-sm font-semibold text-indigo-800",children:"Giải thích"})]}),e("p",{className:"text-sm text-indigo-700 leading-relaxed",children:p})]})]})]})]})}
|