@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/exam-create.js +1 -179
- package/dist/api/exam-create.types.js +1 -1
- package/dist/api/exam-questions.d.ts +1 -1
- package/dist/api/exam-questions.js +1 -98
- package/dist/api/exam-questions.types.d.ts +45 -1
- package/dist/api/exam-questions.types.js +1 -1
- package/dist/api/exam-taking.js +1 -73
- package/dist/api/exam-taking.types.js +1 -1
- package/dist/api/index.js +1 -6
- package/dist/api/result-review.js +1 -50
- package/dist/api/result-review.types.js +1 -1
- package/dist/components/CueCardEditor.js +1 -58
- package/dist/components/common/ResolvedImage.js +1 -15
- package/dist/components/exams/CompletionDialog.d.ts +8 -0
- package/dist/components/exams/CompletionDialog.js +1 -0
- package/dist/components/exams/CreateExamInfoForm.d.ts +4 -1
- package/dist/components/exams/CreateExamInfoForm.js +1 -35
- package/dist/components/exams/CreateExamPageContainer.d.ts +3 -0
- package/dist/components/exams/CreateExamPageContainer.js +1 -103
- package/dist/components/exams/ExamCreator.d.ts +9 -3
- package/dist/components/exams/ExamCreator.js +1 -89
- package/dist/components/exams/ExamPartTabs.d.ts +1 -1
- package/dist/components/exams/ExamPartTabs.js +1 -40
- package/dist/components/exams/ExamPreviewDialog.d.ts +20 -0
- package/dist/components/exams/ExamPreviewDialog.js +1 -0
- package/dist/components/exams/ExamQuestionGrid.d.ts +1 -1
- package/dist/components/exams/ExamQuestionGrid.js +1 -18
- package/dist/components/exams/ExamQuestionsPageContainer.d.ts +3 -0
- package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
- package/dist/components/exams/ExamViewDialog.d.ts +24 -0
- package/dist/components/exams/ExamViewDialog.js +1 -0
- package/dist/components/exams/PartAudioUploader.js +1 -9
- package/dist/components/exams/TemplateSelector.js +1 -66
- package/dist/components/exams/UnsavedChangesDialog.d.ts +8 -0
- package/dist/components/exams/UnsavedChangesDialog.js +1 -0
- package/dist/components/exams/api.d.ts +1 -1
- package/dist/components/exams/api.js +1 -3
- package/dist/components/exams/create-exam.texts.d.ts +58 -0
- package/dist/components/exams/create-exam.texts.js +1 -0
- package/dist/components/exams/create-exam.validation.js +1 -18
- package/dist/components/exams/exam-question-status.d.ts +9 -0
- package/dist/components/exams/exam-question-status.js +1 -0
- package/dist/components/exams/exam-template.utils.js +1 -18
- package/dist/components/exams/index.d.ts +11 -1
- package/dist/components/exams/index.js +1 -10
- package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
- package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
- package/dist/components/exams/take/exam-taking.utils.js +1 -88
- package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
- package/dist/components/exams/take/index.js +1 -2
- package/dist/components/exams/take/types.js +1 -1
- package/dist/components/index.js +1 -42
- package/dist/components/questions/_shared/config/question-types.config.js +1 -333
- package/dist/components/questions/_shared/constants/color-palette.js +1 -21
- package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
- package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
- package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
- package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
- package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
- package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
- package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
- package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
- package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
- package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
- package/dist/components/questions/_shared/index.js +1 -7
- package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
- package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
- package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
- package/dist/components/questions/_shared/types/color-region.config.js +1 -1
- package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
- package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
- package/dist/components/questions/_shared/types/index.js +1 -12
- package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
- package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
- package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
- package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
- package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
- package/dist/components/questions/_shared/types/question-group.type.js +1 -11
- package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
- package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
- package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
- package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
- package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
- package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
- package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
- package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
- package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
- package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
- package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
- package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
- package/dist/components/questions/_shared/utils/id-generator.js +1 -3
- package/dist/components/questions/_shared/utils/question-validation.js +1 -21
- package/dist/components/questions/components/ColorNode.js +1 -85
- package/dist/components/questions/components/ColorRegionClient.js +1 -545
- package/dist/components/questions/components/ColorRegionCreator.js +1 -420
- package/dist/components/questions/components/ColorRegionNode.js +1 -59
- package/dist/components/questions/components/CreatorGuide.js +1 -103
- package/dist/components/questions/components/NameNode.js +1 -26
- package/dist/components/questions/components/QuestionBankOption.js +1 -46
- package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
- package/dist/components/questions/components/QuestionCommonFields.js +1 -33
- package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
- package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
- package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
- package/dist/components/questions/components/RegionNode.js +1 -94
- package/dist/components/questions/components/index.js +1 -14
- package/dist/components/questions/creator/QuestionCreator.js +1 -90
- package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
- package/dist/components/questions/creator/dedicated-component-router.js +1 -79
- package/dist/components/questions/creator/index.js +1 -3
- package/dist/components/questions/creator/question-type-registry.js +1 -80
- package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
- package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
- package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
- package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
- package/dist/components/questions/index.js +1 -10
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
- package/dist/components/questions/types/answer-the-question/register.js +1 -67
- package/dist/components/questions/types/answer-the-question/transform.js +1 -36
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
- package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
- package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
- package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
- package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
- package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
- package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
- package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
- package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
- package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
- package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
- package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
- package/dist/components/questions/types/fill-in-blank/index.js +1 -2
- package/dist/components/questions/types/fill-in-blank/register.js +1 -138
- package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
- package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
- package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
- package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
- package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
- package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
- package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
- package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
- package/dist/components/questions/types/image-object-matching/index.js +1 -2
- package/dist/components/questions/types/image-object-matching/register.js +1 -3
- package/dist/components/questions/types/image-object-matching/transform.js +1 -2
- package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
- package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
- package/dist/components/questions/types/label-the-picture/index.js +1 -2
- package/dist/components/questions/types/label-the-picture/register.js +1 -54
- package/dist/components/questions/types/label-the-picture/transform.js +1 -28
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
- package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
- package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
- package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
- package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
- package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
- package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
- package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
- package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
- package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
- package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
- package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
- package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
- package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
- package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
- package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
- package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
- package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
- package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
- package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
- package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
- package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
- package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
- package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
- package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
- package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
- package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
- package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
- package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
- package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
- package/dist/components/questions/types/read-displayed-content/register.js +1 -64
- package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
- package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
- package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
- package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
- package/dist/components/questions/types/speaking-conversation/register.js +1 -110
- package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
- package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
- package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
- package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
- package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
- package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
- package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
- package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
- package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
- package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
- package/dist/components/questions/types/true-false/index.js +1 -2
- package/dist/components/questions/types/true-false/register.js +1 -77
- package/dist/components/questions/types/true-false/transform.js +1 -32
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
- package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
- package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
- package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
- package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
- package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
- package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
- package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
- package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
- package/dist/components/questions/types/word-ordering/index.js +1 -2
- package/dist/components/questions/types/word-ordering/register.js +1 -46
- package/dist/components/questions/types/word-ordering/transform.js +1 -24
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
- package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
- package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
- package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
- package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
- package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
- package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
- package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
- package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
- package/dist/components/questions/types/write-sentences/index.js +1 -2
- package/dist/components/questions/types/write-sentences/register.js +1 -58
- package/dist/components/questions/types/write-sentences/transform.js +1 -28
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
- package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
- package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
- package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
- package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
- package/dist/components/questions/viewer/QuestionViewer.js +1 -446
- package/dist/components/questions/viewer/index.js +1 -2
- package/dist/components/results/ResultReviewPageContainer.js +1 -98
- package/dist/components/results/ReviewQuestionRenderer.js +1 -72
- package/dist/components/results/index.js +1 -2
- package/dist/components/results/result-review.utils.js +1 -6
- package/dist/components/results/review-data.js +1 -150
- package/dist/components/results/review-renderer.utils.js +1 -97
- package/dist/components/results/review-types.js +1 -1
- package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
- package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
- package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
- package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
- package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
- package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
- package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
- package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
- package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
- package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
- package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
- package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
- package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
- package/dist/components/themes/cambridge-yle/index.js +1 -13
- package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
- package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
- package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
- package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
- package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
- package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
- package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
- package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
- package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
- package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
- package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
- package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
- package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
- package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
- package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
- package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
- package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
- package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
- package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
- package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
- package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
- package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
- package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
- package/dist/components/themes/english-certification/index.js +1 -21
- package/dist/components/themes/index.js +1 -6
- package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
- package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
- package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
- package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
- package/dist/components/themes/types.js +1 -1
- package/dist/components/ui/alert-dialog.js +1 -26
- package/dist/components/ui/alert.js +1 -22
- package/dist/components/ui/avatar.js +1 -12
- package/dist/components/ui/badge.js +1 -38
- package/dist/components/ui/button.js +1 -35
- package/dist/components/ui/calendar.js +1 -76
- package/dist/components/ui/card.js +1 -16
- package/dist/components/ui/carousel.js +1 -91
- package/dist/components/ui/checkbox.js +1 -9
- package/dist/components/ui/collapsible.js +1 -13
- package/dist/components/ui/command.js +1 -34
- package/dist/components/ui/confirm-dialog.js +1 -49
- package/dist/components/ui/dialog.js +1 -23
- package/dist/components/ui/drawer.js +1 -35
- package/dist/components/ui/dropdown-menu.js +1 -33
- package/dist/components/ui/file-upload.js +1 -155
- package/dist/components/ui/form.js +1 -62
- package/dist/components/ui/hover-card.js +1 -10
- package/dist/components/ui/image-preview.js +1 -29
- package/dist/components/ui/input.js +1 -8
- package/dist/components/ui/label.js +1 -9
- package/dist/components/ui/language-switcher.js +1 -15
- package/dist/components/ui/multi-select.js +1 -90
- package/dist/components/ui/points-input.js +1 -33
- package/dist/components/ui/popover.js +1 -26
- package/dist/components/ui/progress.js +1 -16
- package/dist/components/ui/radio-group.js +1 -12
- package/dist/components/ui/scroll-area.js +1 -11
- package/dist/components/ui/select.js +1 -38
- package/dist/components/ui/separator.js +1 -8
- package/dist/components/ui/sheet.js +1 -40
- package/dist/components/ui/single-select.js +1 -76
- package/dist/components/ui/skeleton.js +1 -6
- package/dist/components/ui/switch.js +1 -8
- package/dist/components/ui/table.js +1 -20
- package/dist/components/ui/tabs.js +1 -17
- package/dist/components/ui/textarea.js +1 -8
- package/dist/components/ui/tooltip.js +1 -11
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -19
- package/dist/mcp/config.js +1 -32
- package/dist/mcp/http-client.js +1 -48
- package/dist/mcp/index.js +1 -29
- package/dist/mcp/tools/_helpers.js +1 -25
- package/dist/mcp/tools/exams.js +1 -439
- package/dist/mcp/tools/index.js +1 -6
- package/dist/mcp/tools/results.js +1 -19
- package/dist/shared/constants/ApiConstant.js +1 -276
- package/dist/shared/constants/QueryKeyConstant.js +1 -112
- package/dist/shared/constants/exam-level.enum.js +1 -41
- package/dist/shared/constants/landings/placement-test.constants.js +1 -431
- package/dist/shared/constants/question-skills.js +1 -267
- package/dist/shared/constants/routes.js +1 -81
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.js +1 -7
- package/dist/shared/lib/hooks/index.js +1 -10
- package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
- package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
- package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
- package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
- package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
- package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
- package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
- package/dist/shared/lib/hooks/useFileUpload.js +1 -24
- package/dist/shared/lib/hooks/useImageUrl.js +1 -5
- package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
- package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
- package/dist/shared/lib/hooks/useToast.js +1 -62
- package/dist/shared/lib/i18n/index.js +1 -54
- package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
- package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
- package/dist/shared/lib/i18n/messages/en/common.js +1 -140
- package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
- package/dist/shared/lib/i18n/messages/en/student.js +1 -133
- package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
- package/dist/shared/lib/i18n/messages/en.js +1 -15
- package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
- package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
- package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
- package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
- package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
- package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
- package/dist/shared/lib/i18n/messages/vi.js +1 -15
- package/dist/shared/lib/i18n/types.js +1 -1
- package/dist/shared/lib/stores/examQuestionStore.js +1 -212
- package/dist/shared/lib/stores/localeStore.js +1 -10
- package/dist/shared/lib/utils/public-page.js +1 -44
- package/dist/shared/lib/utils/question-reverse-transform.d.ts +6 -0
- package/dist/shared/lib/utils/question-reverse-transform.js +1 -0
- package/dist/shared/lib/utils/question-transform-types.js +1 -1
- package/dist/shared/lib/utils/question-transform-validation.d.ts +6 -0
- package/dist/shared/lib/utils/question-transform-validation.js +1 -0
- package/dist/shared/lib/utils/question-transform.d.ts +6 -2
- package/dist/shared/lib/utils/question-transform.js +1 -174
- package/dist/shared/lib/utils.js +1 -168
- package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
- package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
- package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
- package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
- package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
- package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
- package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
- package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
- package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
- package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
- package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
- package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
- package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
- package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
- package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
- package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
- package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
- package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
- package/dist/shared/types/branch.types.js +1 -6
- package/dist/shared/types/common.types.js +1 -50
- package/dist/shared/types/entities/exam-schedule.types.js +1 -5
- package/dist/shared/types/entities/exam.types.js +1 -1
- package/dist/shared/types/entities/question.types.js +1 -4
- package/dist/shared/types/entities/result.types.js +1 -4
- package/dist/shared/types/entities/user.types.js +1 -4
- package/dist/shared/types/exam-taking.types.js +1 -2
- package/dist/shared/types/history.types.js +1 -34
- package/dist/shared/types/index.js +1 -1
- package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
- package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
- package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
- package/dist/shared/types/questions/index.js +1 -40
- package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
- package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
- package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
- package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
- package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
- package/dist/shared/types/questions/read-displayed-content.js +1 -1
- package/dist/shared/types/questions/speaking-conversation.js +1 -7
- package/dist/shared/types/questions/speaking-describe-image.js +1 -1
- package/dist/shared/types/questions/spontaneous-qa.js +1 -1
- package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
- package/dist/shared/types/role.types.js +1 -1
- package/dist/shared/types/structure-management.js +1 -28
- package/dist/shared/types/student.types.js +1 -54
- package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
- package/package.json +12 -3
package/dist/api/exam-create.js
CHANGED
|
@@ -1,179 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
const TEMPLATES_PAGE_SIZE = 9;
|
|
3
|
-
const EXAM_LEVELS = ['PRE_A1', 'A1', 'A2', 'B1', 'B2', 'C1', 'C2'].map((id) => ({ id, name: id === 'PRE_A1' ? 'Pre-A1' : id }));
|
|
4
|
-
export function createExamCreateApi(options = {}) {
|
|
5
|
-
const request = createRequest(options);
|
|
6
|
-
return {
|
|
7
|
-
getExamLevels: async () => EXAM_LEVELS,
|
|
8
|
-
getExamTemplates: async ({ page, search }) => {
|
|
9
|
-
const currentPage = page || 1;
|
|
10
|
-
const query = new URLSearchParams({ page: String(currentPage), size: String(TEMPLATES_PAGE_SIZE) });
|
|
11
|
-
if (search)
|
|
12
|
-
query.set('filter', `name:like:${search}`);
|
|
13
|
-
const result = await request(`${API_ENDPOINTS.SHARED.EXAM_TEMPLATES}?${query}`);
|
|
14
|
-
const items = Array.isArray(result.data) ? result.data : result.data.data;
|
|
15
|
-
const meta = result.meta ?? {};
|
|
16
|
-
const totalItems = meta.totalItems ?? items.length;
|
|
17
|
-
return {
|
|
18
|
-
items,
|
|
19
|
-
page: meta.page ?? currentPage,
|
|
20
|
-
totalPages: meta.totalPages ?? Math.max(1, Math.ceil(totalItems / TEMPLATES_PAGE_SIZE)),
|
|
21
|
-
totalItems,
|
|
22
|
-
};
|
|
23
|
-
},
|
|
24
|
-
getExamTemplateDetail: (id) => request(API_ENDPOINTS.SHARED.EXAM_TEMPLATE_DETAIL(id)),
|
|
25
|
-
createExam: (payload) => request(API_ENDPOINTS.MANAGE.EXAMS, {
|
|
26
|
-
method: 'POST',
|
|
27
|
-
body: JSON.stringify(cleanPayload(payload)),
|
|
28
|
-
}),
|
|
29
|
-
updateExamTemplate: async ({ examId, template }) => {
|
|
30
|
-
await request(API_ENDPOINTS.MANAGE.EXAM_TEMPLATE(examId), {
|
|
31
|
-
method: 'PATCH',
|
|
32
|
-
body: JSON.stringify({ template }),
|
|
33
|
-
});
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
export function createMockCreateExamApi(seed) {
|
|
38
|
-
let nextId = 1;
|
|
39
|
-
return {
|
|
40
|
-
getExamLevels: async () => seed.levels ?? EXAM_LEVELS,
|
|
41
|
-
getExamTemplates: async ({ page, search }) => {
|
|
42
|
-
const filtered = seed.templates
|
|
43
|
-
.filter((template) => !search || template.name.toLowerCase().includes(search.toLowerCase()))
|
|
44
|
-
.map(examTemplateToSummary);
|
|
45
|
-
const currentPage = page || 1;
|
|
46
|
-
const totalItems = filtered.length;
|
|
47
|
-
const totalPages = Math.max(1, Math.ceil(totalItems / TEMPLATES_PAGE_SIZE));
|
|
48
|
-
const start = (currentPage - 1) * TEMPLATES_PAGE_SIZE;
|
|
49
|
-
return {
|
|
50
|
-
items: filtered.slice(start, start + TEMPLATES_PAGE_SIZE),
|
|
51
|
-
page: currentPage,
|
|
52
|
-
totalPages,
|
|
53
|
-
totalItems,
|
|
54
|
-
};
|
|
55
|
-
},
|
|
56
|
-
getExamTemplateDetail: async (id) => {
|
|
57
|
-
const template = seed.templates.find((t) => t.id === id);
|
|
58
|
-
if (!template)
|
|
59
|
-
throw new Error(`Mock exam template not found: ${id}`);
|
|
60
|
-
return { data: examTemplateToDetail(template) };
|
|
61
|
-
},
|
|
62
|
-
createExam: async (payload) => ({ data: { id: `mock-exam-${nextId++}`, code: payload.code, name: payload.name, examLevel: payload.examLevel, difficulty: payload.difficulty, totalScore: payload.totalScore, durationMinutes: payload.durationMinutes, description: payload.description, examTemplateId: payload.examTemplateId } }),
|
|
63
|
-
updateExamTemplate: async () => undefined,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
/** Derive a list-row summary from a full mock `ExamTemplate`. */
|
|
67
|
-
function examTemplateToSummary(template) {
|
|
68
|
-
return {
|
|
69
|
-
id: template.id,
|
|
70
|
-
title: template.name,
|
|
71
|
-
subTitle: template.description,
|
|
72
|
-
theme: template.theme,
|
|
73
|
-
type: template.type,
|
|
74
|
-
totalQuestions: template.totalQuestions,
|
|
75
|
-
totalParts: template.parts.length,
|
|
76
|
-
examLevel: template.level ?? '',
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
/** Derive a detail DTO from a full mock `ExamTemplate` (carries parts through). */
|
|
80
|
-
function examTemplateToDetail(template) {
|
|
81
|
-
return {
|
|
82
|
-
id: template.id,
|
|
83
|
-
title: template.name,
|
|
84
|
-
subTitle: template.description,
|
|
85
|
-
description: template.description,
|
|
86
|
-
theme: template.theme,
|
|
87
|
-
type: template.type,
|
|
88
|
-
totalQuestions: template.totalQuestions,
|
|
89
|
-
totalParts: template.parts.length,
|
|
90
|
-
examLevel: template.level ?? '',
|
|
91
|
-
parts: template.parts.map((part) => ({ ...part, title: part.name })),
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
export function buildExamTemplatePayload(template) {
|
|
95
|
-
return {
|
|
96
|
-
id: template.id,
|
|
97
|
-
title: template.name,
|
|
98
|
-
subTitle: '',
|
|
99
|
-
theme: template.theme || '',
|
|
100
|
-
totalQuestions: template.totalQuestions,
|
|
101
|
-
totalParts: template.parts.length,
|
|
102
|
-
description: template.description || '',
|
|
103
|
-
examLevel: template.level || '',
|
|
104
|
-
type: template.type,
|
|
105
|
-
parts: template.parts.map((part) => ({ ...part, name: part.name || '', category: part.category || '', instructions: part.instructions || '', skill: part.skill || '', questionCount: part.questionCount, isGroup: part.isGroup ?? false })),
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Map the template detail DTO (`title`/`subTitle`/`examLevel`/`parts[].title`) returned by
|
|
110
|
-
* `getExamTemplateDetail` into the full `ExamTemplate` entity the create flow expects
|
|
111
|
-
* (`name`/`description`/`level`/`parts[].name`). Ported from the original oe-exam-fe
|
|
112
|
-
* `TemplateSelector.handleConfirm`. Mapping is lenient (`|| ''`, optional fields) so a missing
|
|
113
|
-
* field never throws.
|
|
114
|
-
*/
|
|
115
|
-
export function mapTemplateDetailToExamTemplate(detail) {
|
|
116
|
-
return {
|
|
117
|
-
id: detail.id,
|
|
118
|
-
name: detail.title,
|
|
119
|
-
description: detail.description || detail.subTitle || '',
|
|
120
|
-
theme: detail.theme,
|
|
121
|
-
totalQuestions: detail.totalQuestions,
|
|
122
|
-
totalTime: 0,
|
|
123
|
-
level: detail.examLevel,
|
|
124
|
-
type: detail.type,
|
|
125
|
-
parts: (detail.parts ?? []).map((part) => ({
|
|
126
|
-
id: part.id,
|
|
127
|
-
name: part.name || part.title || '',
|
|
128
|
-
description: part.description || '',
|
|
129
|
-
questionCount: part.questionCount,
|
|
130
|
-
questionType: part.questionType,
|
|
131
|
-
skill: part.skill,
|
|
132
|
-
startIndex: part.startIndex,
|
|
133
|
-
endIndex: part.endIndex,
|
|
134
|
-
points: part.points,
|
|
135
|
-
audioUrl: part.audioUrl,
|
|
136
|
-
optionCount: part.optionCount,
|
|
137
|
-
optionMode: part.optionMode,
|
|
138
|
-
defaultValue: part.defaultValue,
|
|
139
|
-
defaultQuestion: part.defaultQuestion,
|
|
140
|
-
viewMode: part.viewMode,
|
|
141
|
-
instructions: part.instructions,
|
|
142
|
-
group: part.group,
|
|
143
|
-
groups: part.groups,
|
|
144
|
-
groupConfig: part.groupConfig,
|
|
145
|
-
groupType: part.groupType,
|
|
146
|
-
articleCount: part.articleCount,
|
|
147
|
-
questionConfig: part.questionConfig,
|
|
148
|
-
isGroup: part.isGroup,
|
|
149
|
-
suggestedAnswers: part.suggestedAnswers,
|
|
150
|
-
hasSuggestedAnswers: part.hasSuggestedAnswers,
|
|
151
|
-
category: part.category,
|
|
152
|
-
sections: part.sections,
|
|
153
|
-
})),
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
function createRequest({ baseUrl = API_BASE_URL ?? '', fetcher = fetch, headers, apiKey }) {
|
|
157
|
-
return async function request(path, init) {
|
|
158
|
-
const authHeaders = apiKey ? { 'X-API-Key': apiKey } : undefined;
|
|
159
|
-
const response = await fetcher(`${baseUrl}${path}`, {
|
|
160
|
-
// When authenticating via API key, do NOT send browser cookies.
|
|
161
|
-
...(apiKey ? {} : { credentials: 'include' }),
|
|
162
|
-
...init,
|
|
163
|
-
headers: { 'Content-Type': 'application/json', ...authHeaders, ...headers, ...init?.headers },
|
|
164
|
-
});
|
|
165
|
-
if (!response.ok) {
|
|
166
|
-
throw new Error(await readErrorMessage(response));
|
|
167
|
-
}
|
|
168
|
-
return response.json();
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
async function readErrorMessage(response) {
|
|
172
|
-
const body = await response.json().catch(() => undefined);
|
|
173
|
-
if (typeof body === 'object' && body !== null && 'message' in body && typeof body.message === 'string')
|
|
174
|
-
return body.message;
|
|
175
|
-
return response.statusText;
|
|
176
|
-
}
|
|
177
|
-
function cleanPayload(payload) {
|
|
178
|
-
return Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== undefined && value !== ''));
|
|
179
|
-
}
|
|
1
|
+
import{API_BASE_URL as e,API_ENDPOINTS as t}from"../shared/constants/ApiConstant";const a=["PRE_A1","A1","A2","B1","B2","C1","C2"].map(e=>({id:e,name:"PRE_A1"===e?"Pre-A1":e}));export function createExamCreateApi(n={}){const s=function({baseUrl:t=e??"",fetcher:a=fetch,headers:n,apiKey:s}){return async function(e,i){const o=s?{"X-API-Key":s}:void 0,r=await a(`${t}${e}`,{...s?{}:{credentials:"include"},...i,headers:{"Content-Type":"application/json",...o,...n,...i?.headers}});if(!r.ok)throw new Error(await async function(e){const t=await e.json().catch(()=>{});return"object"==typeof t&&null!==t&&"message"in t&&"string"==typeof t.message?t.message:e.statusText}(r));return r.json()}}(n);return{getExamLevels:async()=>a,getExamTemplates:async({page:e,search:a})=>{const n=e||1,i=new URLSearchParams({page:String(n),size:String(9)});a&&i.set("filter",`name:like:${a}`);const o=await s(`${t.SHARED.EXAM_TEMPLATES}?${i}`),r=Array.isArray(o.data)?o.data:o.data.data,l=o.meta??{},p=l.totalItems??r.length;return{items:r,page:l.page??n,totalPages:l.totalPages??Math.max(1,Math.ceil(p/9)),totalItems:p}},getExamTemplateDetail:e=>s(t.SHARED.EXAM_TEMPLATE_DETAIL(e)),createExam:e=>s(t.MANAGE.EXAMS,{method:"POST",body:JSON.stringify(i(e))}),updateExamTemplate:async({examId:e,template:a})=>{await s(t.MANAGE.EXAM_TEMPLATE(e),{method:"PATCH",body:JSON.stringify({template:a})})}}}export function createMockCreateExamApi(e){let t=1;return{getExamLevels:async()=>e.levels??a,getExamTemplates:async({page:t,search:a})=>{const s=e.templates.filter(e=>!a||e.name.toLowerCase().includes(a.toLowerCase())).map(n),i=t||1,o=s.length,r=Math.max(1,Math.ceil(o/9)),l=9*(i-1);return{items:s.slice(l,l+9),page:i,totalPages:r,totalItems:o}},getExamTemplateDetail:async t=>{const a=e.templates.find(e=>e.id===t);if(!a)throw new Error(`Mock exam template not found: ${t}`);return{data:s(a)}},createExam:async e=>({data:{id:"mock-exam-"+t++,code:e.code,name:e.name,examLevel:e.examLevel,difficulty:e.difficulty,totalScore:e.totalScore,durationMinutes:e.durationMinutes,description:e.description,examTemplateId:e.examTemplateId}}),updateExamTemplate:async()=>{}}}function n(e){return{id:e.id,title:e.name,subTitle:e.description,theme:e.theme,type:e.type,totalQuestions:e.totalQuestions,totalParts:e.parts.length,examLevel:e.level??""}}function s(e){return{id:e.id,title:e.name,subTitle:e.description,description:e.description,theme:e.theme,type:e.type,totalQuestions:e.totalQuestions,totalParts:e.parts.length,examLevel:e.level??"",parts:e.parts.map(e=>({...e,title:e.name}))}}export function buildExamTemplatePayload(e){return{id:e.id,title:e.name,subTitle:"",theme:e.theme||"",totalQuestions:e.totalQuestions,totalParts:e.parts.length,description:e.description||"",examLevel:e.level||"",type:e.type,parts:e.parts.map(e=>({...e,name:e.name||"",category:e.category||"",instructions:e.instructions||"",skill:e.skill||"",questionCount:e.questionCount,isGroup:e.isGroup??!1}))}}export function mapTemplateDetailToExamTemplate(e){return{id:e.id,name:e.title,description:e.description||e.subTitle||"",theme:e.theme,totalQuestions:e.totalQuestions,totalTime:0,level:e.examLevel,type:e.type,parts:(e.parts??[]).map(e=>({id:e.id,name:e.name||e.title||"",description:e.description||"",questionCount:e.questionCount,questionType:e.questionType,skill:e.skill,startIndex:e.startIndex,endIndex:e.endIndex,points:e.points,audioUrl:e.audioUrl,optionCount:e.optionCount,optionMode:e.optionMode,defaultValue:e.defaultValue,defaultQuestion:e.defaultQuestion,viewMode:e.viewMode,instructions:e.instructions,group:e.group,groups:e.groups,groupConfig:e.groupConfig,groupType:e.groupType,articleCount:e.articleCount,questionConfig:e.questionConfig,isGroup:e.isGroup,suggestedAnswers:e.suggestedAnswers,hasSuggestedAnswers:e.hasSuggestedAnswers,category:e.category,sections:e.sections}))}}function i(e){return Object.fromEntries(Object.entries(e).filter(([,e])=>void 0!==e&&""!==e))}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export{};
|
|
@@ -6,4 +6,4 @@ export declare function createMockExamQuestionsApi(seed: {
|
|
|
6
6
|
exam: ExamWithTemplate;
|
|
7
7
|
questions?: Partial<ExamQuestion>[];
|
|
8
8
|
}): ExamQuestionsSdkApi;
|
|
9
|
-
export type { ExamQuestionsSdkApi, ExamWithTemplate, FetchApiOptions, SaveExamInput, SaveQuestionInput, UpdatePartAudioInput } from './exam-questions.types';
|
|
9
|
+
export type { ExamQuestionsSdkApi, ExamWithTemplate, ExistingQuestionBank, FetchApiOptions, QuestionSearchItem, SaveExamInput, SaveQuestionInput, SaveQuestionResult, SearchQuestionBankInput, SearchQuestionBankResult, UpdatePartAudioInput, } from './exam-questions.types';
|
|
@@ -1,98 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { transformQuestionToApiFormat } from '../shared/lib/utils/question-transform';
|
|
3
|
-
export function createExamQuestionsApi(options = {}) {
|
|
4
|
-
const request = createRequest(options);
|
|
5
|
-
return {
|
|
6
|
-
getExam: (examId) => request(API_ENDPOINTS.MANAGE.EXAM_DETAIL(examId)),
|
|
7
|
-
getQuestions: async (examId) => {
|
|
8
|
-
const result = await request(API_ENDPOINTS.MANAGE.EXAM_QUESTIONS(examId));
|
|
9
|
-
return Array.isArray(result.data) ? result.data : result.data.questions ?? [];
|
|
10
|
-
},
|
|
11
|
-
saveQuestion: async ({ examId, question, template }) => {
|
|
12
|
-
if (!examId)
|
|
13
|
-
throw new Error('examId is required');
|
|
14
|
-
// Transform the editor's question shape (type/answer/content) into the API
|
|
15
|
-
// payload the exam backend expects (questionType/content/correctAnswer/...).
|
|
16
|
-
const apiQuestion = transformQuestionToApiFormat(question, resolveTransformOptions(question, template));
|
|
17
|
-
const result = await request(API_ENDPOINTS.MANAGE.EXAM_QUESTIONS(examId), {
|
|
18
|
-
method: 'POST',
|
|
19
|
-
body: JSON.stringify({ questions: [apiQuestion] }),
|
|
20
|
-
});
|
|
21
|
-
// Keep the editor-format question (so the card stays editable) and only
|
|
22
|
-
// adopt the server-assigned id from the API response.
|
|
23
|
-
const savedId = result.data.results?.[0]?.data?.id;
|
|
24
|
-
return { ...question, id: savedId ?? question.id, isCompleted: true };
|
|
25
|
-
},
|
|
26
|
-
saveExam: async () => undefined,
|
|
27
|
-
updatePartAudio: (input) => updatePartAudio(request, input),
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
export const createExamQuestionsFetchApi = createExamQuestionsApi;
|
|
31
|
-
export function createMockExamQuestionsApi(seed) {
|
|
32
|
-
const questions = new Map((seed.questions ?? []).map((question) => [(question.questionNumber ?? 1) - 1, question]));
|
|
33
|
-
let exam = seed.exam;
|
|
34
|
-
return {
|
|
35
|
-
getExam: async () => ({ data: exam }),
|
|
36
|
-
getQuestions: async () => Array.from(questions.values()),
|
|
37
|
-
saveQuestion: async ({ question, questionIndex }) => {
|
|
38
|
-
const saved = { ...question, questionNumber: question.questionNumber ?? questionIndex + 1, isCompleted: true };
|
|
39
|
-
questions.set(questionIndex, saved);
|
|
40
|
-
return saved;
|
|
41
|
-
},
|
|
42
|
-
saveExam: async () => undefined,
|
|
43
|
-
updatePartAudio: async ({ partId, audioUrl }) => {
|
|
44
|
-
if (!exam.template || !('parts' in exam.template))
|
|
45
|
-
return;
|
|
46
|
-
exam = {
|
|
47
|
-
...exam,
|
|
48
|
-
template: {
|
|
49
|
-
...exam.template,
|
|
50
|
-
parts: exam.template.parts.map((part) => part.id === partId ? { ...part, audioUrl } : part),
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Build the options the question transform needs from the editor question +
|
|
58
|
-
* template: the 1-based part number (derived from the part's position in the
|
|
59
|
-
* template) plus the positioning fields the question already carries.
|
|
60
|
-
*/
|
|
61
|
-
function resolveTransformOptions(question, template) {
|
|
62
|
-
const partIndex = template?.parts.findIndex((part) => part.id === question.partId) ?? -1;
|
|
63
|
-
const partNo = partIndex >= 0 ? partIndex + 1 : undefined;
|
|
64
|
-
const { partId, questionNumber } = question;
|
|
65
|
-
const sectionId = question.sectionId;
|
|
66
|
-
return { partId, partNo, questionNumber, sectionId };
|
|
67
|
-
}
|
|
68
|
-
function createRequest({ baseUrl = API_BASE_URL ?? '', fetcher = fetch, headers, apiKey }) {
|
|
69
|
-
return async function request(path, init) {
|
|
70
|
-
const authHeaders = apiKey ? { 'X-API-Key': apiKey } : undefined;
|
|
71
|
-
const response = await fetcher(`${baseUrl}${path}`, {
|
|
72
|
-
// When authenticating via API key, do NOT send browser cookies.
|
|
73
|
-
...(apiKey ? {} : { credentials: 'include' }),
|
|
74
|
-
...init,
|
|
75
|
-
headers: { 'Content-Type': 'application/json', ...authHeaders, ...headers, ...init?.headers },
|
|
76
|
-
});
|
|
77
|
-
if (!response.ok) {
|
|
78
|
-
const body = await response.json().catch(() => ({}));
|
|
79
|
-
throw new Error(body.message || response.statusText);
|
|
80
|
-
}
|
|
81
|
-
return response.json();
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
async function updatePartAudio(request, { examId, partId, audioUrl, template }) {
|
|
85
|
-
await request(API_ENDPOINTS.MANAGE.EXAM_TEMPLATE(examId), {
|
|
86
|
-
method: 'PATCH',
|
|
87
|
-
body: JSON.stringify({
|
|
88
|
-
id: template.id,
|
|
89
|
-
title: template.name,
|
|
90
|
-
totalQuestions: template.totalQuestions,
|
|
91
|
-
totalParts: template.parts.length,
|
|
92
|
-
description: template.description || '',
|
|
93
|
-
examLevel: template.level || '',
|
|
94
|
-
type: template.type,
|
|
95
|
-
parts: template.parts.map((part) => ({ ...part, audioUrl: part.id === partId ? audioUrl : part.audioUrl })),
|
|
96
|
-
}),
|
|
97
|
-
});
|
|
98
|
-
}
|
|
1
|
+
import{API_BASE_URL as t,API_ENDPOINTS as e}from"../shared/constants/ApiConstant";import{transformQuestionToApiFormat as s}from"../shared/lib/utils/question-transform";import{transformApiQuestionsToFrontend as a}from"../shared/lib/utils/question-reverse-transform";export function createExamQuestionsApi(n={}){const i=function({baseUrl:e=t??"",fetcher:s=fetch,headers:a,apiKey:n}){return async function(t,i){const o=n?{"X-API-Key":n}:void 0,r=await s(`${e}${t}`,{...n?{}:{credentials:"include"},...i,headers:{"Content-Type":"application/json",...o,...a,...i?.headers}});if(!r.ok){const t=await r.json().catch(()=>({}));throw new Error(t.message||r.statusText)}return r.json()}}(n);return{getExam:t=>i(e.MANAGE.EXAM_DETAIL(t)),getQuestions:async t=>{const s=await i(e.MANAGE.EXAM_QUESTIONS(t)),n=Array.isArray(s.data)?s.data:s.data.questions??[];return a(n)},saveQuestion:async t=>{const{examId:a,question:n,template:o}=t;if(!a)throw new Error("examId is required");const r=s(n,function(t){const{question:e,template:s,addToQuestionBank:a,questionBankCode:n,questionBankTitle:i,sectionId:o,examLevel:r,subjectId:u}=t,d=s?.parts.findIndex(t=>t.id===e.partId)??-1,p=d>=0?d+1:void 0,c=e.sectionId;return{partId:e.partId,partNo:p,questionNumber:e.questionNumber,sectionId:o??c,addToQuestionBank:a,questionBankCode:n,questionBankTitle:i,examLevel:r,subjectId:u}}(t)),u=await i(e.MANAGE.EXAM_QUESTIONS(a),{method:"POST",body:JSON.stringify({examId:a,questions:[r]})});if((u.data.summary?.errorCount??0)>0)throw new Error("Unable to save question");const d=u.data.results?.[0]?.data,p={...n,id:d?.id??n.id,...d?.questionGroup?{questionGroup:d.questionGroup}:{},isCompleted:!0},c=(m=d?.questionBank,"object"!=typeof m||null===m||Array.isArray(m)?void 0:m);var m;return{question:p,questionBank:c?{id:String(c.id??""),code:String(c.code??""),title:String(c.title??"")}:void 0}},saveExam:async()=>{},updatePartAudio:t=>async function(t,{examId:s,partId:a,audioUrl:n,template:i}){await t(e.MANAGE.EXAM_TEMPLATE(s),{method:"PATCH",body:JSON.stringify({id:i.id,title:i.name,subTitle:"",theme:i.theme||"",totalQuestions:i.totalQuestions,totalParts:i.parts.length,description:i.description||"",examLevel:i.level||"",type:i.type,parts:i.parts.map(t=>({...t,audioUrl:t.id===a?n:t.audioUrl}))})})}(i,t),searchQuestions:async({questionType:t,keyword:s,page:a,size:n})=>{const o=new URLSearchParams;t&&o.set("questionType",t),s&&o.set("keyword",s),o.set("page",String(a)),o.set("size",String(n));const r=o.toString()?`?${o.toString()}`:"";return i(`${e.MANAGE.QUESTION_BANKS}${r}`)}}}export const createExamQuestionsFetchApi=createExamQuestionsApi;export function createMockExamQuestionsApi(t){const e=new Map((t.questions??[]).map(t=>[(t.questionNumber??1)-1,t]));let s=t.exam;return{getExam:async()=>({data:s}),getQuestions:async()=>Array.from(e.values()),saveQuestion:async({question:t,questionIndex:s})=>{const a={...t,questionNumber:t.questionNumber??s+1,isCompleted:!0};return e.set(s,a),{question:a}},saveExam:async()=>{},updatePartAudio:async({partId:t,audioUrl:e})=>{s.template&&"parts"in s.template&&(s={...s,template:{...s.template,parts:s.template.parts.map(s=>s.id===t?{...s,audioUrl:e}:s)}})},searchQuestions:async({questionType:t})=>({data:Array.from(e.values()).filter(e=>!t||e.type===t).map(t=>({id:t.id??String(t.questionNumber??""),questionType:t.type??"",content:t.content,correctAnswer:t.answer,points:t.points,difficulty:t.difficulty,skill:t.skill,explanation:t.explanation})),meta:{totalItems:e.size}})}}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import type { QuestionType } from '../shared/types/common.types';
|
|
1
2
|
import type { ExamData, ExamPart, ExamQuestion, ExamTemplate } from '../shared/types/entities/exam.types';
|
|
2
3
|
export interface ExamQuestionsSdkApi {
|
|
3
4
|
getExam: (examId: string) => Promise<ExamQuestionsApiResponse<ExamWithTemplate> | ExamWithTemplate>;
|
|
4
5
|
getQuestions?: (examId: string) => Promise<ExamQuestionsApiResponse<Partial<ExamQuestion>[]> | Partial<ExamQuestion>[]>;
|
|
5
|
-
saveQuestion?: (input: SaveQuestionInput) => Promise<Partial<ExamQuestion> | void>;
|
|
6
|
+
saveQuestion?: (input: SaveQuestionInput) => Promise<Partial<ExamQuestion> | SaveQuestionResult | void>;
|
|
6
7
|
saveExam?: (input: SaveExamInput) => Promise<void>;
|
|
7
8
|
updatePartAudio?: (input: UpdatePartAudioInput) => Promise<void>;
|
|
9
|
+
searchQuestions?: (input: SearchQuestionBankInput) => Promise<SearchQuestionBankResult>;
|
|
8
10
|
}
|
|
9
11
|
export interface ExamQuestionsApiResponse<T> {
|
|
10
12
|
data: T;
|
|
@@ -24,6 +26,7 @@ export interface EmbeddedExamTemplate extends Omit<ExamTemplate, 'name' | 'level
|
|
|
24
26
|
title: string;
|
|
25
27
|
subTitle?: string;
|
|
26
28
|
examLevel: string;
|
|
29
|
+
totalParts?: number;
|
|
27
30
|
parts: Array<ExamPart & {
|
|
28
31
|
title?: string;
|
|
29
32
|
}>;
|
|
@@ -36,6 +39,20 @@ export interface SaveQuestionInput {
|
|
|
36
39
|
examDifficulty?: string;
|
|
37
40
|
examLevel?: string;
|
|
38
41
|
subjectId?: string;
|
|
42
|
+
sectionId?: number;
|
|
43
|
+
addToQuestionBank?: boolean;
|
|
44
|
+
questionBankCode?: string;
|
|
45
|
+
questionBankTitle?: string;
|
|
46
|
+
existingQuestionBankId?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface SaveQuestionResult {
|
|
49
|
+
question: Partial<ExamQuestion>;
|
|
50
|
+
questionBank?: ExistingQuestionBank | null;
|
|
51
|
+
}
|
|
52
|
+
export interface ExistingQuestionBank {
|
|
53
|
+
id: string;
|
|
54
|
+
code: string;
|
|
55
|
+
title: string;
|
|
39
56
|
}
|
|
40
57
|
export interface SaveExamInput {
|
|
41
58
|
examId: string;
|
|
@@ -49,6 +66,33 @@ export interface UpdatePartAudioInput {
|
|
|
49
66
|
audioUrl: string;
|
|
50
67
|
template: ExamTemplate;
|
|
51
68
|
}
|
|
69
|
+
export interface SearchQuestionBankInput {
|
|
70
|
+
questionType?: QuestionType;
|
|
71
|
+
keyword?: string;
|
|
72
|
+
page: number;
|
|
73
|
+
size: number;
|
|
74
|
+
}
|
|
75
|
+
export interface QuestionSearchItem {
|
|
76
|
+
id: string;
|
|
77
|
+
questionType: QuestionType | string;
|
|
78
|
+
content: unknown;
|
|
79
|
+
correctAnswer: unknown;
|
|
80
|
+
points?: string | number;
|
|
81
|
+
difficulty?: string;
|
|
82
|
+
skill?: string;
|
|
83
|
+
explanation?: string | null;
|
|
84
|
+
createdAt?: string;
|
|
85
|
+
updatedAt?: string;
|
|
86
|
+
code?: string;
|
|
87
|
+
title?: string | null;
|
|
88
|
+
usageCount?: number;
|
|
89
|
+
}
|
|
90
|
+
export interface SearchQuestionBankResult {
|
|
91
|
+
data: QuestionSearchItem[];
|
|
92
|
+
meta?: {
|
|
93
|
+
totalItems?: number;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
52
96
|
export interface FetchApiOptions {
|
|
53
97
|
baseUrl?: string;
|
|
54
98
|
fetcher?: typeof fetch;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export{};
|
package/dist/api/exam-taking.js
CHANGED
|
@@ -1,73 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export function createExamTakingApi(options = {}) {
|
|
3
|
-
const request = createRequest(options);
|
|
4
|
-
return {
|
|
5
|
-
getActiveAttempt: (examId) => request(API_ENDPOINTS.STUDENT.ATTEMPTS_ACTIVE_BY_ROOM(examId)),
|
|
6
|
-
getActive: () => request(API_ENDPOINTS.STUDENT.ATTEMPTS_ACTIVE),
|
|
7
|
-
getAttempt: (attemptId, token) => request(`${API_ENDPOINTS.STUDENT.ATTEMPT_DETAIL(attemptId)}${token ? `?token=${encodeURIComponent(token)}` : ''}`),
|
|
8
|
-
deleteAttempt: (attemptId) => request(API_ENDPOINTS.STUDENT.ATTEMPT_DELETE(attemptId), { method: 'DELETE' }),
|
|
9
|
-
getRoom: (roomId) => request(API_ENDPOINTS.STUDENT.ROOM_DETAIL(roomId)),
|
|
10
|
-
startAttempt: (examId) => request(API_ENDPOINTS.STUDENT.ATTEMPTS_START, {
|
|
11
|
-
method: 'POST',
|
|
12
|
-
body: JSON.stringify({ examRoomId: examId }),
|
|
13
|
-
}),
|
|
14
|
-
getQuestions: (attemptId) => request(API_ENDPOINTS.STUDENT.ATTEMPT_QUESTIONS(attemptId)),
|
|
15
|
-
saveAnswers: async ({ attemptId, answers, keepalive }) => {
|
|
16
|
-
await request(API_ENDPOINTS.STUDENT.ATTEMPT_ANSWERS(attemptId), {
|
|
17
|
-
method: 'POST',
|
|
18
|
-
keepalive,
|
|
19
|
-
body: JSON.stringify({ answers }),
|
|
20
|
-
});
|
|
21
|
-
},
|
|
22
|
-
submitAttempt: (attemptId) => request(API_ENDPOINTS.STUDENT.ATTEMPT_SUBMIT(attemptId), { method: 'POST' }),
|
|
23
|
-
uploadAudioAnswer: ({ attemptId, questionId, audioBlob, durationMs }) => {
|
|
24
|
-
const formData = new FormData();
|
|
25
|
-
const ext = audioBlob.type === 'audio/webm' ? 'webm' : audioBlob.type === 'audio/ogg' ? 'ogg' : 'mp3';
|
|
26
|
-
formData.append('file', audioBlob, `recording.${ext}`);
|
|
27
|
-
formData.append('questionId', questionId);
|
|
28
|
-
if (durationMs !== undefined)
|
|
29
|
-
formData.append('durationMs', String(durationMs));
|
|
30
|
-
return request(API_ENDPOINTS.STUDENT.ATTEMPT_AUDIO_ANSWER(attemptId), { method: 'POST', body: formData });
|
|
31
|
-
},
|
|
32
|
-
uploadProctoringSnapshot: ({ attemptId, imageBlob }) => {
|
|
33
|
-
const formData = new FormData();
|
|
34
|
-
formData.append('file', imageBlob, 'snapshot.jpg');
|
|
35
|
-
return request(API_ENDPOINTS.STUDENT.ATTEMPT_PROCTORING_SNAPSHOT(attemptId), { method: 'POST', body: formData });
|
|
36
|
-
},
|
|
37
|
-
realtimeGradeSingle: ({ attemptId, questionId, audioKey }) => request(API_ENDPOINTS.SHARED.SPEAKING_REALTIME_GRADE, { method: 'POST', body: JSON.stringify({ attemptId, questionId, audioKey }) }),
|
|
38
|
-
getGradingSummary: (attemptId) => request(API_ENDPOINTS.STUDENT.ATTEMPT_GRADING_SUMMARY(attemptId)),
|
|
39
|
-
getAnswerDetail: (attemptId, answerId) => request(API_ENDPOINTS.STUDENT.ATTEMPT_ANSWER_DETAIL(attemptId, answerId)),
|
|
40
|
-
getAnswersGradingStatus: (attemptId, answerIds) => request(API_ENDPOINTS.STUDENT.ATTEMPT_ANSWERS_GRADING_STATUS(attemptId), { method: 'POST', body: JSON.stringify({ answerIds }) }),
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
export function createMockExamTakingApi(seed) {
|
|
44
|
-
const answers = new Map();
|
|
45
|
-
return {
|
|
46
|
-
getActiveAttempt: async () => seed.attempt,
|
|
47
|
-
startAttempt: async () => seed.attempt,
|
|
48
|
-
getQuestions: async () => seed.questions,
|
|
49
|
-
saveAnswers: async ({ answers: input }) => { input.forEach((item) => answers.set(item.questionId, item.answer)); },
|
|
50
|
-
submitAttempt: async () => ({ answers: Object.fromEntries(answers) }),
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
function createRequest({ baseUrl = API_BASE_URL ?? '', fetcher = fetch, headers }) {
|
|
54
|
-
return async function request(path, init) {
|
|
55
|
-
const isFormData = typeof FormData !== 'undefined' && init?.body instanceof FormData;
|
|
56
|
-
const response = await fetcher(`${baseUrl}${path}`, {
|
|
57
|
-
credentials: 'include',
|
|
58
|
-
...init,
|
|
59
|
-
headers: { ...(!isFormData && { 'Content-Type': 'application/json' }), ...headers, ...init?.headers },
|
|
60
|
-
});
|
|
61
|
-
if (!response.ok)
|
|
62
|
-
throw new Error(await readErrorMessage(response));
|
|
63
|
-
if (response.status === 204)
|
|
64
|
-
return undefined;
|
|
65
|
-
return response.json();
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
async function readErrorMessage(response) {
|
|
69
|
-
const body = await response.json().catch(() => undefined);
|
|
70
|
-
if (typeof body === 'object' && body !== null && 'message' in body && typeof body.message === 'string')
|
|
71
|
-
return body.message;
|
|
72
|
-
return response.statusText;
|
|
73
|
-
}
|
|
1
|
+
import{API_BASE_URL as t,API_ENDPOINTS as e}from"../shared/constants/ApiConstant";export function createExamTakingApi(n={}){const T=function({baseUrl:e=t??"",fetcher:n=fetch,headers:T}){return async function(t,a){const o="undefined"!=typeof FormData&&a?.body instanceof FormData,s=await n(`${e}${t}`,{credentials:"include",...a,headers:{...!o&&{"Content-Type":"application/json"},...T,...a?.headers}});if(!s.ok)throw new Error(await async function(t){const e=await t.json().catch(()=>{});return"object"==typeof e&&null!==e&&"message"in e&&"string"==typeof e.message?e.message:t.statusText}(s));if(204!==s.status)return s.json()}}(n);return{getActiveAttempt:t=>T(e.STUDENT.ATTEMPTS_ACTIVE_BY_ROOM(t)),getActive:()=>T(e.STUDENT.ATTEMPTS_ACTIVE),getAttempt:(t,n)=>T(`${e.STUDENT.ATTEMPT_DETAIL(t)}${n?`?token=${encodeURIComponent(n)}`:""}`),deleteAttempt:t=>T(e.STUDENT.ATTEMPT_DELETE(t),{method:"DELETE"}),getRoom:t=>T(e.STUDENT.ROOM_DETAIL(t)),startAttempt:t=>T(e.STUDENT.ATTEMPTS_START,{method:"POST",body:JSON.stringify({examRoomId:t})}),getQuestions:t=>T(e.STUDENT.ATTEMPT_QUESTIONS(t)),saveAnswers:async({attemptId:t,answers:n,keepalive:a})=>{await T(e.STUDENT.ATTEMPT_ANSWERS(t),{method:"POST",keepalive:a,body:JSON.stringify({answers:n})})},submitAttempt:t=>T(e.STUDENT.ATTEMPT_SUBMIT(t),{method:"POST"}),uploadAudioAnswer:({attemptId:t,questionId:n,audioBlob:a,durationMs:o})=>{const s=new FormData,i="audio/webm"===a.type?"webm":"audio/ogg"===a.type?"ogg":"mp3";return s.append("file",a,`recording.${i}`),s.append("questionId",n),void 0!==o&&s.append("durationMs",String(o)),T(e.STUDENT.ATTEMPT_AUDIO_ANSWER(t),{method:"POST",body:s})},uploadProctoringSnapshot:({attemptId:t,imageBlob:n})=>{const a=new FormData;return a.append("file",n,"snapshot.jpg"),T(e.STUDENT.ATTEMPT_PROCTORING_SNAPSHOT(t),{method:"POST",body:a})},realtimeGradeSingle:({attemptId:t,questionId:n,audioKey:a})=>T(e.SHARED.SPEAKING_REALTIME_GRADE,{method:"POST",body:JSON.stringify({attemptId:t,questionId:n,audioKey:a})}),getGradingSummary:t=>T(e.STUDENT.ATTEMPT_GRADING_SUMMARY(t)),getAnswerDetail:(t,n)=>T(e.STUDENT.ATTEMPT_ANSWER_DETAIL(t,n)),getAnswersGradingStatus:(t,n)=>T(e.STUDENT.ATTEMPT_ANSWERS_GRADING_STATUS(t),{method:"POST",body:JSON.stringify({answerIds:n})})}}export function createMockExamTakingApi(t){const e=new Map;return{getActiveAttempt:async()=>t.attempt,startAttempt:async()=>t.attempt,getQuestions:async()=>t.questions,saveAnswers:async({answers:t})=>{t.forEach(t=>e.set(t.questionId,t.answer))},submitAttempt:async()=>({answers:Object.fromEntries(e)})}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export{};
|
package/dist/api/index.js
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from './exam-questions';
|
|
3
|
-
export * from './exam-taking';
|
|
4
|
-
export * from './result-review';
|
|
5
|
-
export * from '../shared/constants/ApiConstant';
|
|
6
|
-
export * from '../shared/constants/QueryKeyConstant';
|
|
1
|
+
export*from"./exam-create";export*from"./exam-questions";export*from"./exam-taking";export*from"./result-review";export*from"../shared/constants/ApiConstant";export*from"../shared/constants/QueryKeyConstant";
|
|
@@ -1,50 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export function createResultReviewApi(options = {}) {
|
|
3
|
-
const request = createRequest(options);
|
|
4
|
-
return {
|
|
5
|
-
getDetailedResult: (attemptId, token) => request(withToken(API_ENDPOINTS.STUDENT.RESULT_DETAIL(attemptId), token)),
|
|
6
|
-
getAttempt: (attemptId, token) => request(withToken(API_ENDPOINTS.STUDENT.ATTEMPT_DETAIL(attemptId), token)),
|
|
7
|
-
exportPdf: async (attemptId, examType) => {
|
|
8
|
-
const response = await (options.fetcher ?? fetch)(`${options.baseUrl ?? API_BASE_URL ?? ''}${API_ENDPOINTS.STUDENT.EXPORT_NATIVE_PDF}`, {
|
|
9
|
-
method: 'POST',
|
|
10
|
-
credentials: 'include',
|
|
11
|
-
headers: { 'Content-Type': 'application/json', ...options.headers },
|
|
12
|
-
body: JSON.stringify({ attemptId, examType }),
|
|
13
|
-
});
|
|
14
|
-
if (!response.ok)
|
|
15
|
-
throw new Error(await readErrorMessage(response));
|
|
16
|
-
return { blob: await response.blob(), filename: readFilename(response.headers, attemptId) };
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
export function createMockResultReviewApi(seed) {
|
|
21
|
-
return {
|
|
22
|
-
getDetailedResult: async () => seed.detail,
|
|
23
|
-
getAttempt: async () => seed.attempt ?? { id: seed.detail.id },
|
|
24
|
-
exportPdf: async () => ({ blob: new Blob(['mock pdf'], { type: 'application/pdf' }), filename: `result-${seed.detail.id}.pdf` }),
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
function createRequest({ baseUrl = API_BASE_URL ?? '', fetcher = fetch, headers }) {
|
|
28
|
-
return async function request(path, init) {
|
|
29
|
-
const response = await fetcher(`${baseUrl}${path}`, { credentials: 'include', ...init, headers: { 'Content-Type': 'application/json', ...headers, ...init?.headers } });
|
|
30
|
-
if (!response.ok)
|
|
31
|
-
throw new Error(await readErrorMessage(response));
|
|
32
|
-
return response.json();
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
function withToken(path, token) {
|
|
36
|
-
if (!token)
|
|
37
|
-
return path;
|
|
38
|
-
const separator = path.includes('?') ? '&' : '?';
|
|
39
|
-
return `${path}${separator}token=${encodeURIComponent(token)}`;
|
|
40
|
-
}
|
|
41
|
-
function readFilename(headers, attemptId) {
|
|
42
|
-
const disposition = headers.get('content-disposition') ?? '';
|
|
43
|
-
return disposition.match(/filename="?([^\"]+)"?/)?.[1] ?? `result-${attemptId.slice(0, 8)}.pdf`;
|
|
44
|
-
}
|
|
45
|
-
async function readErrorMessage(response) {
|
|
46
|
-
const body = await response.json().catch(() => undefined);
|
|
47
|
-
if (typeof body === 'object' && body !== null && 'message' in body && typeof body.message === 'string')
|
|
48
|
-
return body.message;
|
|
49
|
-
return response.statusText;
|
|
50
|
-
}
|
|
1
|
+
import{API_BASE_URL as e,API_ENDPOINTS as t}from"../shared/constants/ApiConstant";export function createResultReviewApi(i={}){const r=function({baseUrl:t=e??"",fetcher:n=fetch,headers:a}){return async function(e,i){const r=await n(`${t}${e}`,{credentials:"include",...i,headers:{"Content-Type":"application/json",...a,...i?.headers}});if(!r.ok)throw new Error(await o(r));return r.json()}}(i);return{getDetailedResult:(e,a)=>r(n(t.STUDENT.RESULT_DETAIL(e),a)),getAttempt:(e,a)=>r(n(t.STUDENT.ATTEMPT_DETAIL(e),a)),exportPdf:async(n,r)=>{const s=await(i.fetcher??fetch)(`${i.baseUrl??e??""}${t.STUDENT.EXPORT_NATIVE_PDF}`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...i.headers},body:JSON.stringify({attemptId:n,examType:r})});if(!s.ok)throw new Error(await o(s));return{blob:await s.blob(),filename:a(s.headers,n)}}}}export function createMockResultReviewApi(e){return{getDetailedResult:async()=>e.detail,getAttempt:async()=>e.attempt??{id:e.detail.id},exportPdf:async()=>({blob:new Blob(["mock pdf"],{type:"application/pdf"}),filename:`result-${e.detail.id}.pdf`})}}function n(e,t){if(!t)return e;const n=e.includes("?")?"&":"?";return`${e}${n}token=${encodeURIComponent(t)}`}function a(e,t){const n=e.get("content-disposition")??"";return n.match(/filename="?([^\"]+)"?/)?.[1]??`result-${t.slice(0,8)}.pdf`}async function o(e){const t=await e.json().catch(()=>{});return"object"==typeof t&&null!==t&&"message"in t&&"string"==typeof t.message?t.message:e.statusText}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export{};
|
|
@@ -1,58 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useState } from 'react';
|
|
4
|
-
import { useEditor, EditorContent } from '@tiptap/react';
|
|
5
|
-
import StarterKit from '@tiptap/starter-kit';
|
|
6
|
-
const DEFAULT_CONTENT = `
|
|
7
|
-
<p><strong>Describe a topic you know well.</strong></p>
|
|
8
|
-
<p>You should say:</p>
|
|
9
|
-
<ul>
|
|
10
|
-
<li>What it is</li>
|
|
11
|
-
<li>When you first learned about it</li>
|
|
12
|
-
<li>Why you find it interesting</li>
|
|
13
|
-
</ul>
|
|
14
|
-
<p><em>and explain how it has impacted your life.</em></p>
|
|
15
|
-
`;
|
|
16
|
-
export function CueCardEditor({ value, onChange, label, }) {
|
|
17
|
-
const [mounted, setMounted] = useState(false);
|
|
18
|
-
const editor = useEditor({
|
|
19
|
-
immediatelyRender: false,
|
|
20
|
-
extensions: [StarterKit],
|
|
21
|
-
content: value || DEFAULT_CONTENT,
|
|
22
|
-
onUpdate: ({ editor }) => {
|
|
23
|
-
onChange(editor.getHTML());
|
|
24
|
-
},
|
|
25
|
-
editorProps: {
|
|
26
|
-
attributes: {
|
|
27
|
-
class: 'tiptap-editor focus:outline-none min-h-[250px] p-4 bg-white border border-gray-200 rounded-b-md',
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
setMounted(true);
|
|
33
|
-
}, []);
|
|
34
|
-
// Sync value if changed from outside (e.g., initial load)
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (editor && value !== undefined && value !== editor.getHTML()) {
|
|
37
|
-
// Small timeout to prevent update loops
|
|
38
|
-
setTimeout(() => editor.commands.setContent(value), 0);
|
|
39
|
-
}
|
|
40
|
-
}, [value, editor]);
|
|
41
|
-
if (!mounted)
|
|
42
|
-
return null;
|
|
43
|
-
return (_jsxs("div", { className: "w-full flex flex-col gap-4", children: [label && _jsx("div", { children: label }), _jsxs("div", { className: "flex flex-col border border-gray-300 rounded-md shadow-sm", children: [_jsxs("div", { className: "flex flex-wrap gap-2 p-2 relative bg-gray-50 border-b border-gray-300 rounded-t-md", children: [_jsx("button", { type: "button", onClick: () => editor?.chain().focus().toggleBold().run(), disabled: !editor?.can().chain().focus().toggleBold().run(), className: `px-3 py-1 rounded text-sm font-bold ${editor?.isActive('bold')
|
|
44
|
-
? 'bg-blue-500 text-white'
|
|
45
|
-
: 'bg-white border text-gray-700 hover:bg-gray-100'}`, children: "B" }), _jsx("button", { type: "button", onClick: () => editor?.chain().focus().toggleItalic().run(), disabled: !editor?.can().chain().focus().toggleItalic().run(), className: `px-3 py-1 rounded text-sm italic ${editor?.isActive('italic')
|
|
46
|
-
? 'bg-blue-500 text-white'
|
|
47
|
-
: 'bg-white border text-gray-700 hover:bg-gray-100'}`, children: "I" }), _jsx("div", { className: "w-px h-6 bg-gray-300 mx-1 self-center" }), _jsx("button", { type: "button", onClick: () => editor?.chain().focus().toggleHeading({ level: 1 }).run(), className: `px-3 py-1 rounded text-sm font-bold ${editor?.isActive('heading', { level: 1 })
|
|
48
|
-
? 'bg-blue-500 text-white'
|
|
49
|
-
: 'bg-white border text-gray-700 hover:bg-gray-100'}`, children: "H1" }), _jsx("button", { type: "button", onClick: () => editor?.chain().focus().toggleHeading({ level: 2 }).run(), className: `px-3 py-1 rounded text-sm font-bold ${editor?.isActive('heading', { level: 2 })
|
|
50
|
-
? 'bg-blue-500 text-white'
|
|
51
|
-
: 'bg-white border text-gray-700 hover:bg-gray-100'}`, children: "H2" }), _jsx("button", { type: "button", onClick: () => editor?.chain().focus().toggleHeading({ level: 3 }).run(), className: `px-3 py-1 rounded text-sm font-bold ${editor?.isActive('heading', { level: 3 })
|
|
52
|
-
? 'bg-blue-500 text-white'
|
|
53
|
-
: 'bg-white border text-gray-700 hover:bg-gray-100'}`, children: "H3" }), _jsx("div", { className: "w-px h-6 bg-gray-300 mx-1 self-center" }), _jsx("button", { type: "button", onClick: () => editor?.chain().focus().toggleBulletList().run(), className: `px-3 py-1 rounded text-sm ${editor?.isActive('bulletList')
|
|
54
|
-
? 'bg-blue-500 text-white'
|
|
55
|
-
: 'bg-white border text-gray-700 hover:bg-gray-100'}`, children: "\u2022 List" }), _jsx("button", { type: "button", onClick: () => editor?.chain().focus().toggleOrderedList().run(), className: `px-3 py-1 rounded text-sm ${editor?.isActive('orderedList')
|
|
56
|
-
? 'bg-blue-500 text-white'
|
|
57
|
-
: 'bg-white border text-gray-700 hover:bg-gray-100'}`, children: "1. List" })] }), _jsx(EditorContent, { editor: editor })] })] }));
|
|
58
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useEffect as i,useState as o}from"react";import{useEditor as r,EditorContent as n}from"@tiptap/react";import l from"@tiptap/starter-kit";export function CueCardEditor({value:a,onChange:d,label:s}){const[c,g]=o(!1),b=r({immediatelyRender:!1,extensions:[l],content:a||"\n <p><strong>Describe a topic you know well.</strong></p>\n <p>You should say:</p>\n <ul>\n <li>What it is</li>\n <li>When you first learned about it</li>\n <li>Why you find it interesting</li>\n </ul>\n <p><em>and explain how it has impacted your life.</em></p>\n",onUpdate:({editor:e})=>{d(e.getHTML())},editorProps:{attributes:{class:"tiptap-editor focus:outline-none min-h-[250px] p-4 bg-white border border-gray-200 rounded-b-md"}}});return i(()=>{g(!0)},[]),i(()=>{b&&void 0!==a&&a!==b.getHTML()&&setTimeout(()=>b.commands.setContent(a),0)},[a,b]),c?t("div",{className:"w-full flex flex-col gap-4",children:[s&&e("div",{children:s}),t("div",{className:"flex flex-col border border-gray-300 rounded-md shadow-sm",children:[t("div",{className:"flex flex-wrap gap-2 p-2 relative bg-gray-50 border-b border-gray-300 rounded-t-md",children:[e("button",{type:"button",onClick:()=>b?.chain().focus().toggleBold().run(),disabled:!b?.can().chain().focus().toggleBold().run(),className:"px-3 py-1 rounded text-sm font-bold "+(b?.isActive("bold")?"bg-blue-500 text-white":"bg-white border text-gray-700 hover:bg-gray-100"),children:"B"}),e("button",{type:"button",onClick:()=>b?.chain().focus().toggleItalic().run(),disabled:!b?.can().chain().focus().toggleItalic().run(),className:"px-3 py-1 rounded text-sm italic "+(b?.isActive("italic")?"bg-blue-500 text-white":"bg-white border text-gray-700 hover:bg-gray-100"),children:"I"}),e("div",{className:"w-px h-6 bg-gray-300 mx-1 self-center"}),e("button",{type:"button",onClick:()=>b?.chain().focus().toggleHeading({level:1}).run(),className:"px-3 py-1 rounded text-sm font-bold "+(b?.isActive("heading",{level:1})?"bg-blue-500 text-white":"bg-white border text-gray-700 hover:bg-gray-100"),children:"H1"}),e("button",{type:"button",onClick:()=>b?.chain().focus().toggleHeading({level:2}).run(),className:"px-3 py-1 rounded text-sm font-bold "+(b?.isActive("heading",{level:2})?"bg-blue-500 text-white":"bg-white border text-gray-700 hover:bg-gray-100"),children:"H2"}),e("button",{type:"button",onClick:()=>b?.chain().focus().toggleHeading({level:3}).run(),className:"px-3 py-1 rounded text-sm font-bold "+(b?.isActive("heading",{level:3})?"bg-blue-500 text-white":"bg-white border text-gray-700 hover:bg-gray-100"),children:"H3"}),e("div",{className:"w-px h-6 bg-gray-300 mx-1 self-center"}),e("button",{type:"button",onClick:()=>b?.chain().focus().toggleBulletList().run(),className:"px-3 py-1 rounded text-sm "+(b?.isActive("bulletList")?"bg-blue-500 text-white":"bg-white border text-gray-700 hover:bg-gray-100"),children:"• List"}),e("button",{type:"button",onClick:()=>b?.chain().focus().toggleOrderedList().run(),className:"px-3 py-1 rounded text-sm "+(b?.isActive("orderedList")?"bg-blue-500 text-white":"bg-white border text-gray-700 hover:bg-gray-100"),children:"1. List"})]}),e(n,{editor:b})]})]}):null}
|
|
@@ -1,15 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import { ImageOff } from 'lucide-react';
|
|
5
|
-
import { Skeleton } from '../../components/ui/skeleton';
|
|
6
|
-
import { usePresignedFileUrl } from '../../shared/lib/hooks/usePresignedFileUrl';
|
|
7
|
-
export function ResolvedImage({ src, fallback, showLoadingState = true, className, alt = '', ...props }) {
|
|
8
|
-
const { previewUrl, isLoading, error } = usePresignedFileUrl(src);
|
|
9
|
-
const [hasError, setHasError] = useState(false);
|
|
10
|
-
if (isLoading && showLoadingState)
|
|
11
|
-
return fallback ?? _jsx(Skeleton, { className: className });
|
|
12
|
-
if (error || (!previewUrl && !src) || hasError)
|
|
13
|
-
return fallback ?? _jsx("div", { className: `flex items-center justify-center bg-gray-100 ${className}`, children: _jsx(ImageOff, { className: "h-8 w-8 text-gray-400" }) });
|
|
14
|
-
return _jsx("img", { src: previewUrl || src, alt: alt, className: className, onError: () => setHasError(true), ...props });
|
|
15
|
-
}
|
|
1
|
+
"use client";import{jsx as e}from"react/jsx-runtime";import{useState as r}from"react";import{ImageOff as s}from"lucide-react";import{Skeleton as t}from"../../components/ui/skeleton";import{usePresignedFileUrl as a}from"../../shared/lib/hooks/usePresignedFileUrl";export function ResolvedImage({src:o,fallback:i,showLoadingState:m=!0,className:c,alt:l="",...n}){const{previewUrl:f,isLoading:d,error:g}=a(o),[p,u]=r(!1);return d&&m?i??e(t,{className:c}):g||!f&&!o||p?i??e("div",{className:`flex items-center justify-center bg-gray-100 ${c}`,children:e(s,{className:"h-8 w-8 text-gray-400"})}):e("img",{src:f||o,alt:l,className:c,onError:()=>u(!0),...n})}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface CompletionDialogProps {
|
|
2
|
+
open: boolean;
|
|
3
|
+
onOpenChange: (open: boolean) => void;
|
|
4
|
+
completedCount: number;
|
|
5
|
+
totalQuestions: number;
|
|
6
|
+
onConfirm: () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function CompletionDialog({ open, onOpenChange, completedCount, totalQuestions, onConfirm, }: CompletionDialogProps): import("react").JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsx as e,jsxs as n}from"react/jsx-runtime";import{AlertDialog as t,AlertDialogAction as h,AlertDialogCancel as c,AlertDialogContent as o,AlertDialogDescription as i,AlertDialogFooter as r,AlertDialogHeader as l,AlertDialogTitle as s}from"../../components/ui/alert-dialog";import{AlertTriangle as a,CheckCircle as d}from"lucide-react";export function CompletionDialog({open:m,onOpenChange:p,completedCount:g,totalQuestions:u,onConfirm:x}){const C=g===u;return e(t,{open:m,onOpenChange:p,children:n(o,{children:[n(l,{children:[n(s,{className:"flex items-center gap-2",children:[C?e(d,{className:"h-5 w-5 text-green-500"}):e(a,{className:"h-5 w-5 text-amber-500"}),C?"Lưu đề thi thành công":"Đề thi chưa hoàn thành"]}),e(i,{asChild:!0,children:n("div",{className:"space-y-2 text-sm text-muted-foreground",children:[n("p",{children:["Bạn đã hoàn thành ",n("strong",{children:[g,"/",u]})," câu hỏi."]}),C?e("p",{children:"Đề thi đã sẵn sàng để gán vào phòng thi."}):n("p",{children:["Còn ",e("strong",{children:u-g})," câu chưa được tạo. Bạn có thể tiếp tục tạo sau hoặc thoát và quay lại sau."]})]})})]}),n(r,{children:[e(c,{children:C?"Tiếp tục chỉnh sửa":"Tiếp tục tạo"}),e(h,{onClick:x,className:C?"bg-green-600 hover:bg-green-700":"bg-blue-600 hover:bg-blue-700",children:C?"Xác nhận":"Lưu và thoát"})]})]})})}
|