@tinyweb_dev/oe-exam-sdk 1.0.6 → 1.0.8
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/components/exams/ExamCreator.js +1 -16
- package/dist/components/exams/take/ExamTakingPageContainer.js +0 -12
- package/dist/components/exams/take/components/QuestionRenderer.js +2 -15
- package/dist/components/exams/take/components/question-renderers/index.d.ts +0 -3
- package/dist/components/exams/take/components/question-renderers/index.js +0 -3
- package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionRenderer.js +0 -4
- package/dist/components/exams/take/types/speaking.types.d.ts +0 -31
- package/dist/components/exams/take/types.d.ts +0 -65
- package/dist/components/exams/take/utils/answer-transformers.js +6 -60
- package/dist/components/exams/take/utils/question-transformers.d.ts +2 -15
- package/dist/components/exams/take/utils/question-transformers.js +0 -143
- package/dist/components/questions/_shared/components/compact/CompactCreatorHeader.d.ts +23 -0
- package/dist/components/questions/_shared/components/compact/CompactCreatorHeader.js +9 -0
- package/dist/components/questions/_shared/components/compact/CompactExampleToggle.d.ts +6 -0
- package/dist/components/questions/_shared/components/compact/CompactExampleToggle.js +8 -0
- package/dist/components/questions/_shared/components/compact/CompactExplanationToggle.d.ts +7 -0
- package/dist/components/questions/_shared/components/compact/CompactExplanationToggle.js +14 -0
- package/dist/components/questions/_shared/components/compact/CompactFieldCard.d.ts +9 -0
- package/dist/components/questions/_shared/components/compact/CompactFieldCard.js +7 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewBanner.d.ts +7 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewBanner.js +7 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewButton.d.ts +6 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewButton.js +7 -0
- package/dist/components/questions/_shared/components/compact/index.d.ts +6 -0
- package/dist/components/questions/_shared/components/compact/index.js +6 -0
- package/dist/components/questions/_shared/config/question-types.config.js +0 -24
- package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +0 -9
- package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.d.ts +2 -2
- package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.js +5 -1
- package/dist/components/questions/_shared/types/index.d.ts +0 -1
- package/dist/components/questions/_shared/types/index.js +0 -1
- package/dist/components/questions/_shared/types/look-and-choose-answer.type.d.ts +0 -13
- package/dist/components/questions/_shared/utils/answer-builder.js +0 -11
- package/dist/components/questions/components/ColorRegionCreator.js +66 -63
- package/dist/components/questions/components/QuestionSearchDropdown.js +0 -8
- package/dist/components/questions/components/QuestionTypeSelector.js +0 -2
- package/dist/components/questions/components/index.d.ts +1 -0
- package/dist/components/questions/components/index.js +1 -0
- package/dist/components/questions/creator/question-type-registry.js +0 -8
- package/dist/components/questions/groups/ArticlesGroupCard.js +2 -2
- package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -1
- package/dist/components/questions/groups/DocumentGroupCard.js +1 -1
- package/dist/components/questions/groups/ImageLabelGroupCard.js +1 -1
- package/dist/components/questions/groups/MatchAnswerGroupCard.js +2 -2
- package/dist/components/questions/question-bank/QuestionEditorRenderer.d.ts +0 -3
- package/dist/components/questions/question-bank/QuestionEditorRenderer.js +1 -10
- package/dist/components/questions/question-bank/editor-adapters/choice-adapters.d.ts +0 -2
- package/dist/components/questions/question-bank/editor-adapters/choice-adapters.js +0 -41
- package/dist/components/questions/question-bank/editor-adapters/index.d.ts +2 -2
- package/dist/components/questions/question-bank/editor-adapters/index.js +2 -2
- package/dist/components/questions/question-bank/editor-adapters/media-adapters.d.ts +0 -1
- package/dist/components/questions/question-bank/editor-adapters/media-adapters.js +0 -28
- package/dist/components/questions/question-bank/question-bank-picker.utils.js +0 -7
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +16 -16
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +36 -30
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +1 -1
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +7 -6
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +2 -2
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +11 -7
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -1
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +11 -5
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +5 -5
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +34 -28
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.d.ts +7 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.js +5 -2
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +9 -5
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +65 -62
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupItemEditor.d.ts +6 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupItemEditor.js +145 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.js +6 -15
- package/dist/components/questions/types/choose-the-correct-answer-group/transform.js +5 -4
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +7 -7
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.js +8 -5
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +2 -2
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupCreator.js +20 -16
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +1 -1
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleCreator.js +66 -29
- package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +5 -5
- package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +45 -37
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +24 -20
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +2 -2
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +68 -65
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +1 -1
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.js +45 -5
- package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +2 -2
- package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +18 -14
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +2 -2
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +30 -30
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +2 -2
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +24 -31
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +2 -2
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +29 -32
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +27 -29
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +11 -15
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +22 -24
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +17 -24
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +31 -31
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +11 -15
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +14 -15
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +2 -2
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +27 -25
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +12 -12
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesCreator.js +8 -5
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +8 -8
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureCreator.js +25 -20
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +1 -1
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +13 -10
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +8 -5
- package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +7 -11
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -1
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +15 -12
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +1 -1
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesCreator.js +5 -5
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +35 -39
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +2 -2
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +1 -1
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +7 -8
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -1
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +30 -23
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQAClient.js +1 -1
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +26 -19
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.d.ts +1 -1
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +79 -3
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +13 -11
- package/dist/components/questions/types/spontaneous-qa-group/index.d.ts +1 -0
- package/dist/components/questions/types/spontaneous-qa-group/index.js +1 -0
- package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.d.ts +2 -0
- package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.js +74 -0
- package/dist/components/questions/types/true-false/TrueFalseClient.js +2 -2
- package/dist/components/questions/types/true-false/TrueFalseCreator.js +35 -29
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +1 -1
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupCreator.js +11 -7
- package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +1 -1
- package/dist/components/questions/types/true-false-group/TrueFalseGroupCreator.js +11 -7
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +2 -2
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +21 -56
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -1
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +30 -24
- package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +3 -3
- package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupCreator.js +12 -14
- package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -1
- package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +27 -18
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +7 -7
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupCreator.js +16 -13
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +2 -2
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +34 -28
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +2 -2
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +22 -22
- package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +21 -18
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -1
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +7 -4
- package/dist/components/questions/viewer/QuestionViewer.js +0 -98
- package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +4 -79
- package/dist/components/results/renderers/review-question-body-movers.js +2 -26
- package/dist/components/results/review-data.js +0 -4
- package/dist/components/ui/points-input.d.ts +1 -0
- package/dist/components/ui/points-input.js +12 -2
- package/dist/shared/constants/question-skills.js +0 -11
- package/dist/shared/lib/i18n/messages/en/admin.d.ts +0 -3
- package/dist/shared/lib/i18n/messages/en/admin.js +0 -3
- package/dist/shared/lib/i18n/messages/en.d.ts +0 -3
- package/dist/shared/lib/i18n/messages/vi/admin.d.ts +0 -3
- package/dist/shared/lib/i18n/messages/vi/admin.js +0 -3
- package/dist/shared/lib/i18n/messages/vi.d.ts +0 -3
- package/dist/shared/lib/utils/question-reverse-transform.js +4 -23
- package/dist/shared/lib/utils/question-transform-types.d.ts +1 -23
- package/dist/shared/lib/utils/question-transform-validation.js +0 -40
- package/dist/shared/lib/utils/question-transform.js +0 -9
- package/dist/shared/types/common.types.d.ts +1 -1
- package/dist/shared/types/entities/question.types.d.ts +1 -11
- package/dist/shared/types/questions/choose-the-correct-answer-group.d.ts +1 -1
- package/dist/shared/types/questions/index.d.ts +0 -3
- package/dist/shared/types/questions/index.js +0 -6
- package/package.json +1 -1
- package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.d.ts +0 -2
- package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.js +0 -131
- package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.d.ts +0 -2
- package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.js +0 -76
- package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.d.ts +0 -2
- package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.js +0 -68
- package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.d.ts +0 -16
- package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.js +0 -212
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.d.ts +0 -32
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +0 -1
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.d.ts +0 -47
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +0 -1
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.d.ts +0 -2
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +0 -262
- package/dist/components/questions/types/gn-speaking-interview/register.d.ts +0 -2
- package/dist/components/questions/types/gn-speaking-interview/register.js +0 -121
- package/dist/components/questions/types/gn-speaking-interview/transform.d.ts +0 -2
- package/dist/components/questions/types/gn-speaking-interview/transform.js +0 -48
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.d.ts +0 -20
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +0 -72
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.d.ts +0 -15
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +0 -230
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.d.ts +0 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +0 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.d.ts +0 -2
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +0 -66
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.d.ts +0 -2
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +0 -42
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.d.ts +0 -6
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +0 -96
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.d.ts +0 -15
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +0 -238
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +0 -76
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +0 -19
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.d.ts +0 -7
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +0 -134
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.d.ts +0 -3
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +0 -617
- package/dist/components/questions/types/look-picture-fill-word-hint/index.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/index.js +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.js +0 -82
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +0 -29
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.d.ts +0 -9
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +0 -12
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.d.ts +0 -17
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +0 -47
- package/dist/shared/types/questions/look-picture-choose-correct-answer.d.ts +0 -38
- package/dist/shared/types/questions/look-picture-choose-correct-answer.js +0 -5
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.d.ts +0 -63
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +0 -9
- package/dist/shared/types/questions/look-picture-fill-word-hint.d.ts +0 -57
- package/dist/shared/types/questions/look-picture-fill-word-hint.js +0 -7
|
@@ -3,19 +3,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { Button } from '../../../../components/ui/button';
|
|
5
5
|
import { Label } from '../../../../components/ui/label';
|
|
6
|
-
import { Input } from '../../../../components/ui/input';
|
|
7
6
|
import { Textarea } from '../../../../components/ui/textarea';
|
|
8
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
9
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
10
|
-
import { Switch } from '../../../../components/ui/switch';
|
|
11
|
-
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
12
7
|
import { RichTextEditor } from '../../../../components/shared/RichTextEditor';
|
|
13
8
|
import { useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
14
|
-
import {
|
|
9
|
+
import { BookOpen, HelpCircle, ImageIcon, Plus, Trash2, Volume2 } from 'lucide-react';
|
|
10
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
15
11
|
import { ChooseTheCorrectAnswerGroupClient } from './ChooseTheCorrectAnswerGroupClient';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
|
|
12
|
+
import { ChooseAnswerGroupAudioUploadItem, ChooseAnswerGroupImageUploadItem, } from './ChooseAnswerGroupImageUpload';
|
|
13
|
+
import { ChooseTheCorrectAnswerGroupItemEditor } from './ChooseTheCorrectAnswerGroupItemEditor';
|
|
14
|
+
import { fromChooseAnswerGroupImageUrls, isChooseAnswerGroupOptionFilled, toChooseAnswerGroupImageUrls, toEditableChooseAnswerGroupImageUrls, } from '../../_shared/types/choose-the-correct-answer-group.type';
|
|
19
15
|
function createEmptyItem(questionNumber) {
|
|
20
16
|
return {
|
|
21
17
|
question: '',
|
|
@@ -23,7 +19,6 @@ function createEmptyItem(questionNumber) {
|
|
|
23
19
|
options: [
|
|
24
20
|
{ id: 'opt-1', text: '' },
|
|
25
21
|
{ id: 'opt-2', text: '' },
|
|
26
|
-
{ id: 'opt-3', text: '' },
|
|
27
22
|
],
|
|
28
23
|
correctAnswer: 'opt-1',
|
|
29
24
|
isExample: false,
|
|
@@ -37,6 +32,7 @@ function normalizeItems(rawItems) {
|
|
|
37
32
|
}
|
|
38
33
|
return rawItems.map((item, index) => {
|
|
39
34
|
const imageUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(item.imageUrl));
|
|
35
|
+
const audioUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(item.audioUrl));
|
|
40
36
|
return {
|
|
41
37
|
question: item.question || '',
|
|
42
38
|
optionType: item.optionType === 'image' ? 'image' : 'text',
|
|
@@ -52,44 +48,45 @@ function normalizeItems(rawItems) {
|
|
|
52
48
|
points: item.isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
|
|
53
49
|
questionNumber: item.questionNumber || index + 1,
|
|
54
50
|
...(imageUrl !== undefined ? { imageUrl } : {}),
|
|
55
|
-
...(
|
|
51
|
+
...(audioUrl !== undefined ? { audioUrl } : {}),
|
|
56
52
|
};
|
|
57
53
|
});
|
|
58
54
|
}
|
|
59
55
|
function sumPoints(items) {
|
|
60
56
|
return items.reduce((total, item) => (item.isExample ? total : total + (typeof item.points === 'number' ? item.points : 1)), 0);
|
|
61
57
|
}
|
|
62
|
-
function parsePointsValue(raw, allowZero) {
|
|
63
|
-
const parsed = Number.parseInt(raw.replace(/[^0-9]/g, ''), 10);
|
|
64
|
-
if (Number.isNaN(parsed)) {
|
|
65
|
-
return allowZero ? 0 : 1;
|
|
66
|
-
}
|
|
67
|
-
return allowZero ? Math.max(0, parsed) : Math.max(1, parsed);
|
|
68
|
-
}
|
|
69
58
|
export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, externalErrors, onUnsavedChangesChange, validationRef, }) {
|
|
70
59
|
const [instruction, setInstruction] = useState(initialData?.instruction || 'Choose the correct answer.');
|
|
71
60
|
const [passage, setPassage] = useState(initialData?.passage || '');
|
|
72
|
-
const [
|
|
73
|
-
const [
|
|
61
|
+
const [imageUrls, setImageUrls] = useState(() => (toEditableChooseAnswerGroupImageUrls(initialData?.imageUrl)));
|
|
62
|
+
const [audioUrls, setAudioUrls] = useState(() => (toEditableChooseAnswerGroupImageUrls(initialData?.audioUrl)));
|
|
74
63
|
const [items, setItems] = useState(() => normalizeItems(initialData?.items));
|
|
75
64
|
const [explanation, setExplanation] = useState(initialData?.explanation || '');
|
|
76
65
|
const [isClientMode, setIsClientMode] = useState(false);
|
|
77
66
|
const [errors, setErrors] = useState([]);
|
|
67
|
+
const [isPassageVisible, setIsPassageVisible] = useState(() => {
|
|
68
|
+
const html = initialData?.passage;
|
|
69
|
+
return Boolean(html && html !== '<p></p>' && html !== '<p><br></p>' && html.trim());
|
|
70
|
+
});
|
|
78
71
|
const onChangeRef = useRef(onChange);
|
|
79
72
|
const previousPayloadRef = useRef('');
|
|
80
73
|
useEffect(() => {
|
|
81
74
|
onChangeRef.current = onChange;
|
|
82
75
|
}, [onChange]);
|
|
83
76
|
const isContentEmpty = (html) => !html || html === '<p></p>' || html === '<p><br></p>' || html.trim() === '';
|
|
84
|
-
const buildPayload = (nextInstruction = instruction, nextPassage = passage, nextItems = items, nextExplanation = explanation,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
77
|
+
const buildPayload = (nextInstruction = instruction, nextPassage = passage, nextItems = items, nextExplanation = explanation, nextAudioUrls = audioUrls, nextImageUrls = imageUrls) => {
|
|
78
|
+
const nextAudioUrl = fromChooseAnswerGroupImageUrls(nextAudioUrls);
|
|
79
|
+
const nextImageUrl = fromChooseAnswerGroupImageUrls(nextImageUrls);
|
|
80
|
+
return {
|
|
81
|
+
instruction: nextInstruction,
|
|
82
|
+
...(!isContentEmpty(nextPassage) ? { passage: nextPassage.trim() } : {}),
|
|
83
|
+
...(nextAudioUrl !== undefined ? { audioUrl: nextAudioUrl } : {}),
|
|
84
|
+
...(nextImageUrl !== undefined ? { imageUrl: nextImageUrl } : {}),
|
|
85
|
+
items: nextItems,
|
|
86
|
+
explanation: nextExplanation,
|
|
87
|
+
points: sumPoints(nextItems),
|
|
88
|
+
};
|
|
89
|
+
};
|
|
93
90
|
const debouncedOnChange = useDebouncedCallback((payload) => {
|
|
94
91
|
onChangeRef.current?.(payload);
|
|
95
92
|
}, 300);
|
|
@@ -102,7 +99,7 @@ export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, exte
|
|
|
102
99
|
previousPayloadRef.current = serialized;
|
|
103
100
|
onUnsavedChangesChange?.(true);
|
|
104
101
|
debouncedOnChange(payload);
|
|
105
|
-
}, [instruction, passage,
|
|
102
|
+
}, [instruction, passage, audioUrls, imageUrls, items, explanation, debouncedOnChange, onUnsavedChangesChange]);
|
|
106
103
|
const validate = () => {
|
|
107
104
|
const nextErrors = [];
|
|
108
105
|
if (!instruction.trim()) {
|
|
@@ -112,9 +109,6 @@ export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, exte
|
|
|
112
109
|
nextErrors.push('Cần ít nhất một câu hỏi.');
|
|
113
110
|
}
|
|
114
111
|
items.forEach((item, index) => {
|
|
115
|
-
if (!item.question.trim()) {
|
|
116
|
-
nextErrors.push(`Câu ${index + 1}: nội dung câu hỏi là bắt buộc.`);
|
|
117
|
-
}
|
|
118
112
|
const filledOptions = item.options.filter((option) => (isChooseAnswerGroupOptionFilled(option, item.optionType)));
|
|
119
113
|
if (filledOptions.length < 2) {
|
|
120
114
|
nextErrors.push(`Câu ${index + 1}: cần ít nhất 2 đáp án.`);
|
|
@@ -141,15 +135,6 @@ export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, exte
|
|
|
141
135
|
const updateItem = (index, patch) => {
|
|
142
136
|
setItems((current) => current.map((item, itemIndex) => (itemIndex === index ? { ...item, ...patch } : item)));
|
|
143
137
|
};
|
|
144
|
-
const updateOption = (itemIndex, optionIndex, patch) => {
|
|
145
|
-
setItems((current) => current.map((item, index) => {
|
|
146
|
-
if (index !== itemIndex) {
|
|
147
|
-
return item;
|
|
148
|
-
}
|
|
149
|
-
const options = item.options.map((option, currentOptionIndex) => (currentOptionIndex === optionIndex ? { ...option, ...patch } : option));
|
|
150
|
-
return { ...item, options };
|
|
151
|
-
}));
|
|
152
|
-
};
|
|
153
138
|
const addItem = () => {
|
|
154
139
|
setItems((current) => [...current, createEmptyItem(current.length + 1)]);
|
|
155
140
|
};
|
|
@@ -164,26 +149,44 @@ export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, exte
|
|
|
164
149
|
});
|
|
165
150
|
};
|
|
166
151
|
if (isClientMode) {
|
|
167
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsx(
|
|
152
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(ChooseTheCorrectAnswerGroupClient, { questionData: buildPayload(), isReviewMode: true })] }));
|
|
168
153
|
}
|
|
169
154
|
const allErrors = [...errors, ...(externalErrors || [])];
|
|
170
|
-
return (_jsxs("div", { className: "space-y-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
155
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(HelpCircle, { className: "h-3.5 w-3.5" }), title: "Nh\u00F3m ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng", points: {
|
|
156
|
+
value: sumPoints(items),
|
|
157
|
+
onValueChange: () => undefined,
|
|
158
|
+
allowZero: true,
|
|
159
|
+
}, preview: { onClick: () => setIsClientMode(true) } }), children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "group-instruction", className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsxs("div", { className: "relative", children: [_jsx(Textarea, { id: "group-instruction", value: instruction, onChange: (event) => setInstruction(event.target.value), placeholder: "Choose the correct answer.", rows: 2, className: "min-h-[56px] border-gray-200 pb-11" }), _jsxs("div", { className: "absolute bottom-1.5 right-1.5 z-10 flex items-center gap-1 rounded-md border border-indigo-100 bg-white/95 p-0.5 shadow-sm", children: [_jsx("button", { type: "button", title: "Th\u00EAm h\u00ECnh \u1EA3nh", onClick: () => {
|
|
160
|
+
const hasEmpty = imageUrls.some((url) => !url.trim());
|
|
161
|
+
if (imageUrls.length === 0 || !hasEmpty) {
|
|
162
|
+
setImageUrls([...imageUrls, '']);
|
|
163
|
+
}
|
|
164
|
+
}, className: `inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors ${imageUrls.length > 0
|
|
165
|
+
? 'bg-indigo-600 text-white shadow-sm'
|
|
166
|
+
: 'bg-indigo-50 text-indigo-600 hover:bg-indigo-100'}`, children: _jsx(ImageIcon, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", title: "Th\u00EAm audio", onClick: () => {
|
|
167
|
+
const hasEmpty = audioUrls.some((url) => !url.trim());
|
|
168
|
+
if (audioUrls.length === 0 || !hasEmpty) {
|
|
169
|
+
setAudioUrls([...audioUrls, '']);
|
|
170
|
+
}
|
|
171
|
+
}, className: `inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors ${audioUrls.length > 0
|
|
172
|
+
? 'bg-violet-600 text-white shadow-sm'
|
|
173
|
+
: 'bg-violet-50 text-violet-600 hover:bg-violet-100'}`, children: _jsx(Volume2, { className: "h-4 w-4" }) })] })] })] }), imageUrls.length > 0 && (_jsxs("div", { className: "space-y-2 rounded-md border border-gray-200 bg-slate-50/80 p-2", children: [imageUrls.map((url, imageIndex) => (_jsx(ChooseAnswerGroupImageUploadItem, { index: imageIndex, itemIndex: -1, imageUrl: url, onChange: (nextUrl) => {
|
|
174
|
+
setImageUrls((current) => current.map((item, idx) => (idx === imageIndex ? nextUrl : item)));
|
|
175
|
+
}, onRemove: () => {
|
|
176
|
+
setImageUrls((current) => current.filter((_, idx) => idx !== imageIndex));
|
|
177
|
+
} }, `group-image-${imageIndex}`))), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setImageUrls((current) => (current.length ? [...current, ''] : [''])), className: "h-8 gap-1.5 border-dashed border-indigo-300 text-indigo-600 hover:bg-indigo-50 hover:text-indigo-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm \u1EA3nh"] })] })), audioUrls.length > 0 && (_jsxs("div", { className: "space-y-2 rounded-md border border-gray-200 bg-slate-50/80 p-2", children: [audioUrls.map((url, audioIndex) => (_jsx(ChooseAnswerGroupAudioUploadItem, { index: audioIndex, itemIndex: -1, audioUrl: url, onChange: (nextUrl) => {
|
|
178
|
+
setAudioUrls((current) => current.map((item, idx) => (idx === audioIndex ? nextUrl : item)));
|
|
179
|
+
}, onRemove: () => {
|
|
180
|
+
setAudioUrls((current) => current.filter((_, idx) => idx !== audioIndex));
|
|
181
|
+
} }, `group-audio-${audioIndex}`))), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setAudioUrls((current) => (current.length ? [...current, ''] : [''])), className: "h-8 gap-1.5 border-dashed border-violet-300 text-violet-600 hover:bg-violet-50 hover:text-violet-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm audio"] })] })), !isPassageVisible ? (_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setIsPassageVisible(true), className: "h-8 gap-1.5 border-gray-200 px-2.5 text-xs text-gray-600 hover:bg-gray-50", children: [_jsx(BookOpen, { className: "h-3.5 w-3.5" }), "Th\u00EAm n\u1ED9i dung b\u00E0i \u0111\u1ECDc"] })) : (_jsxs("div", { className: "relative space-y-1.5 pr-10", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setIsPassageVisible(false), className: "absolute right-0 top-0 z-10 h-7 px-2 text-xs text-gray-500 hover:text-gray-700", children: "\u1EA8n" }), _jsxs(Label, { className: "text-sm font-semibold text-gray-800", children: ["N\u1ED9i dung b\u00E0i \u0111\u1ECDc ", _jsx("span", { className: "text-xs font-normal text-gray-500", children: "(tu\u1EF3 ch\u1ECDn)" })] }), _jsx(RichTextEditor, { value: passage, onChange: setPassage, variant: "compact", minHeightClassName: "min-h-[72px]" })] }))] }), items.map((item, itemIndex) => (_jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(HelpCircle, { className: "h-3.5 w-3.5" }), title: `Câu ${itemIndex + 1}`, points: !item.isExample ? {
|
|
182
|
+
value: item.points,
|
|
183
|
+
allowZero: true,
|
|
184
|
+
onValueChange: (value) => updateItem(itemIndex, { points: value }),
|
|
185
|
+
} : undefined, example: {
|
|
186
|
+
checked: item.isExample === true,
|
|
187
|
+
onChange: (checked) => updateItem(itemIndex, {
|
|
188
|
+
isExample: checked,
|
|
189
|
+
points: checked ? 0 : (item.points || 1),
|
|
190
|
+
}),
|
|
191
|
+
}, extraActions: _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeItem(itemIndex), disabled: items.length <= 1, className: "h-8 w-8 text-gray-400 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }) }), children: _jsx(ChooseTheCorrectAnswerGroupItemEditor, { item: item, itemIndex: itemIndex, onChange: (patch) => updateItem(itemIndex, patch) }) }, `item-${itemIndex}`))), _jsxs(Button, { type: "button", variant: "outline", onClick: addItem, className: "h-8 gap-1.5 border-dashed border-green-200 px-2.5 text-xs text-green-600 hover:bg-green-50 hover:text-green-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm c\u00E2u h\u1ECFi"] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) }), allErrors.length > 0 && (_jsx("div", { className: "rounded-md border border-red-200 bg-red-50 p-2 text-xs text-red-700", children: allErrors.map((error) => (_jsx("p", { children: error }, error))) }))] }));
|
|
189
192
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ChooseTheCorrectAnswerGroupItemData } from '../../_shared/types/choose-the-correct-answer-group.type';
|
|
2
|
+
export declare function ChooseTheCorrectAnswerGroupItemEditor({ item, itemIndex, onChange, }: {
|
|
3
|
+
item: ChooseTheCorrectAnswerGroupItemData;
|
|
4
|
+
itemIndex: number;
|
|
5
|
+
onChange: (patch: Partial<ChooseTheCorrectAnswerGroupItemData>) => void;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useRef, useState } from 'react';
|
|
4
|
+
import { Button } from '../../../../components/ui/button';
|
|
5
|
+
import { Label } from '../../../../components/ui/label';
|
|
6
|
+
import { Input } from '../../../../components/ui/input';
|
|
7
|
+
import { Textarea } from '../../../../components/ui/textarea';
|
|
8
|
+
import { Switch } from '../../../../components/ui/switch';
|
|
9
|
+
import { RichTextEditor } from '../../../../components/shared/RichTextEditor';
|
|
10
|
+
import { hasRichTextFormatting, htmlToPlainEditorText } from '../../../../shared/lib/rich-text';
|
|
11
|
+
import { CheckCircle2, ImageIcon, Pilcrow, Plus, Trash2, Type, Volume2 } from 'lucide-react';
|
|
12
|
+
import { ChooseAnswerGroupAudioUploadItem, ChooseAnswerGroupImageUploadItem, ChooseAnswerGroupOptionImageField, } from './ChooseAnswerGroupImageUpload';
|
|
13
|
+
import { fromChooseAnswerGroupImageUrls, getChooseAnswerGroupOptionImageSrc, toEditableChooseAnswerGroupImageUrls, } from '../../_shared/types/choose-the-correct-answer-group.type';
|
|
14
|
+
function optionLetter(index) {
|
|
15
|
+
return String.fromCharCode(65 + index);
|
|
16
|
+
}
|
|
17
|
+
function nextOptionId(options) {
|
|
18
|
+
const used = new Set(options.map((option) => option.id));
|
|
19
|
+
let next = 1;
|
|
20
|
+
while (used.has(`opt-${next}`)) {
|
|
21
|
+
next += 1;
|
|
22
|
+
}
|
|
23
|
+
return `opt-${next}`;
|
|
24
|
+
}
|
|
25
|
+
function shouldStartInTipTapMode(item) {
|
|
26
|
+
if (hasRichTextFormatting(item.question)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return item.options.some((option) => hasRichTextFormatting(option.text));
|
|
30
|
+
}
|
|
31
|
+
function emptyOptions(count) {
|
|
32
|
+
return Array.from({ length: count }, (_, index) => ({
|
|
33
|
+
id: `opt-${index + 1}`,
|
|
34
|
+
text: '',
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
function ChooseAnswerTextInput({ value, onChange, isTipTapMode, placeholder, compact = false, className, }) {
|
|
38
|
+
if (isTipTapMode) {
|
|
39
|
+
return (_jsx(RichTextEditor, { value: value, onChange: onChange, variant: "compact", minHeightClassName: compact ? `min-h-[56px] ${className ?? ''}` : `min-h-[72px] ${className ?? ''}` }));
|
|
40
|
+
}
|
|
41
|
+
if (compact) {
|
|
42
|
+
return (_jsx(Input, { value: htmlToPlainEditorText(value), onChange: (event) => onChange(event.target.value), placeholder: placeholder, className: `h-10 border-gray-200 bg-white ${className ?? ''}` }));
|
|
43
|
+
}
|
|
44
|
+
return (_jsx(Textarea, { value: htmlToPlainEditorText(value), onChange: (event) => onChange(event.target.value), placeholder: placeholder, rows: 2, className: `min-h-[56px] border-gray-200 bg-white ${className ?? ''}` }));
|
|
45
|
+
}
|
|
46
|
+
export function ChooseTheCorrectAnswerGroupItemEditor({ item, itemIndex, onChange, }) {
|
|
47
|
+
const [isTipTapMode, setIsTipTapMode] = useState(() => shouldStartInTipTapMode(item));
|
|
48
|
+
const textOptionsCacheRef = useRef(null);
|
|
49
|
+
const imageOptionsCacheRef = useRef(null);
|
|
50
|
+
const imageUrls = toEditableChooseAnswerGroupImageUrls(item.imageUrl);
|
|
51
|
+
const audioUrls = toEditableChooseAnswerGroupImageUrls(item.audioUrl);
|
|
52
|
+
const hasCorrectAnswer = item.options.some((option) => option.id === item.correctAnswer);
|
|
53
|
+
const updateOption = (optionIndex, patch) => {
|
|
54
|
+
onChange({
|
|
55
|
+
options: item.options.map((option, index) => (index === optionIndex ? { ...option, ...patch } : option)),
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
const switchOptionType = (nextType) => {
|
|
59
|
+
if (nextType === item.optionType) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (item.optionType === 'text') {
|
|
63
|
+
textOptionsCacheRef.current = { options: item.options, correctAnswer: item.correctAnswer };
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
imageOptionsCacheRef.current = { options: item.options, correctAnswer: item.correctAnswer };
|
|
67
|
+
}
|
|
68
|
+
const targetCache = nextType === 'text' ? textOptionsCacheRef.current : imageOptionsCacheRef.current;
|
|
69
|
+
if (targetCache) {
|
|
70
|
+
onChange({
|
|
71
|
+
optionType: nextType,
|
|
72
|
+
options: targetCache.options,
|
|
73
|
+
correctAnswer: targetCache.correctAnswer,
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const nextOptions = emptyOptions(item.options.length);
|
|
78
|
+
onChange({
|
|
79
|
+
optionType: nextType,
|
|
80
|
+
options: nextOptions,
|
|
81
|
+
correctAnswer: nextOptions[0]?.id || 'opt-1',
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
return (_jsxs("div", { className: "space-y-2.5", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs(Label, { className: "text-sm font-semibold text-gray-800", children: ["C\u00E2u h\u1ECFi ", _jsx("span", { className: "text-xs font-normal text-gray-500", children: "(tu\u1EF3 ch\u1ECDn)" })] }), _jsxs("button", { type: "button", onClick: () => setIsTipTapMode((enabled) => !enabled), title: isTipTapMode ? 'Chuyển về ô nhập thường' : 'Bật định dạng chữ (in đậm, danh sách, bảng)', className: `inline-flex h-8 items-center gap-1.5 rounded-md border px-2.5 text-xs font-medium transition-colors select-none ${isTipTapMode
|
|
85
|
+
? 'border-indigo-300 bg-indigo-50 text-indigo-700'
|
|
86
|
+
: 'border-gray-200 bg-white text-gray-400 hover:text-gray-600'}`, children: [_jsx(Pilcrow, { className: "h-3.5 w-3.5" }), "\u0110\u1ECBnh d\u1EA1ng"] })] }), _jsxs("div", { className: "relative", children: [_jsx(ChooseAnswerTextInput, { value: item.question, onChange: (question) => onChange({ question }), isTipTapMode: isTipTapMode, placeholder: "Nh\u1EADp c\u00E2u h\u1ECFi...", className: "pb-11" }), _jsxs("div", { className: "absolute bottom-1.5 right-1.5 z-10 flex items-center gap-1 rounded-md border border-indigo-100 bg-white/95 p-0.5 shadow-sm", children: [_jsx("button", { type: "button", title: "Th\u00EAm h\u00ECnh \u1EA3nh", onClick: () => {
|
|
87
|
+
const current = toEditableChooseAnswerGroupImageUrls(item.imageUrl);
|
|
88
|
+
const hasEmpty = current.some((url) => !url.trim());
|
|
89
|
+
if (current.length === 0 || !hasEmpty) {
|
|
90
|
+
onChange({ imageUrl: [...current, ''] });
|
|
91
|
+
}
|
|
92
|
+
}, className: `inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors ${imageUrls.length > 0
|
|
93
|
+
? 'bg-indigo-600 text-white shadow-sm'
|
|
94
|
+
: 'bg-indigo-50 text-indigo-600 hover:bg-indigo-100'}`, children: _jsx(ImageIcon, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", title: "Th\u00EAm audio", onClick: () => {
|
|
95
|
+
const current = toEditableChooseAnswerGroupImageUrls(item.audioUrl);
|
|
96
|
+
const hasEmpty = current.some((url) => !url.trim());
|
|
97
|
+
if (current.length === 0 || !hasEmpty) {
|
|
98
|
+
onChange({ audioUrl: [...current, ''] });
|
|
99
|
+
}
|
|
100
|
+
}, className: `inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors ${audioUrls.length > 0
|
|
101
|
+
? 'bg-violet-600 text-white shadow-sm'
|
|
102
|
+
: 'bg-violet-50 text-violet-600 hover:bg-violet-100'}`, children: _jsx(Volume2, { className: "h-4 w-4" }) })] })] })] }), imageUrls.length > 0 && (_jsxs("div", { className: "space-y-2 rounded-md border border-gray-200 bg-slate-50/80 p-2", children: [imageUrls.map((imageUrl, imageIndex) => (_jsx(ChooseAnswerGroupImageUploadItem, { index: imageIndex, itemIndex: itemIndex, imageUrl: imageUrl, onChange: (url) => {
|
|
103
|
+
const nextUrls = toEditableChooseAnswerGroupImageUrls(item.imageUrl);
|
|
104
|
+
nextUrls[imageIndex] = url;
|
|
105
|
+
onChange({ imageUrl: nextUrls });
|
|
106
|
+
}, onRemove: () => {
|
|
107
|
+
const nextUrls = toEditableChooseAnswerGroupImageUrls(item.imageUrl)
|
|
108
|
+
.filter((_, idx) => idx !== imageIndex);
|
|
109
|
+
onChange({ imageUrl: fromChooseAnswerGroupImageUrls(nextUrls) });
|
|
110
|
+
} }, `${itemIndex}-image-${imageIndex}`))), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
|
|
111
|
+
const current = toEditableChooseAnswerGroupImageUrls(item.imageUrl);
|
|
112
|
+
onChange({ imageUrl: current.length ? [...current, ''] : [''] });
|
|
113
|
+
}, className: "h-8 gap-1.5 border-dashed border-indigo-300 text-indigo-600 hover:bg-indigo-50 hover:text-indigo-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm \u1EA3nh"] })] })), audioUrls.length > 0 && (_jsxs("div", { className: "space-y-2 rounded-md border border-gray-200 bg-slate-50/80 p-2", children: [audioUrls.map((audioUrl, audioIndex) => (_jsx(ChooseAnswerGroupAudioUploadItem, { index: audioIndex, itemIndex: itemIndex, audioUrl: audioUrl, onChange: (url) => {
|
|
114
|
+
const nextUrls = toEditableChooseAnswerGroupImageUrls(item.audioUrl);
|
|
115
|
+
nextUrls[audioIndex] = url;
|
|
116
|
+
onChange({ audioUrl: nextUrls });
|
|
117
|
+
}, onRemove: () => {
|
|
118
|
+
const nextUrls = toEditableChooseAnswerGroupImageUrls(item.audioUrl)
|
|
119
|
+
.filter((_, idx) => idx !== audioIndex);
|
|
120
|
+
onChange({ audioUrl: fromChooseAnswerGroupImageUrls(nextUrls) });
|
|
121
|
+
} }, `${itemIndex}-audio-${audioIndex}`))), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
|
|
122
|
+
const current = toEditableChooseAnswerGroupImageUrls(item.audioUrl);
|
|
123
|
+
onChange({ audioUrl: current.length ? [...current, ''] : [''] });
|
|
124
|
+
}, className: "h-8 gap-1.5 border-dashed border-violet-300 text-violet-600 hover:bg-violet-50 hover:text-violet-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm audio"] })] })), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "text-sm font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n ", _jsxs("span", { className: "text-xs font-normal text-gray-500", children: ["(", item.options.length, " \u0111\u00E1p \u00E1n)"] })] }), _jsxs("div", { className: "flex h-8 items-center gap-1.5 rounded-md border border-gray-200 bg-gray-50 px-2.5", children: [_jsx(Type, { className: `h-3.5 w-3.5 ${item.optionType === 'text' ? 'text-indigo-600' : 'text-gray-400'}` }), _jsx("span", { className: `text-xs font-medium ${item.optionType === 'text' ? 'text-indigo-600' : 'text-gray-400'}`, children: "Text" }), _jsx(Switch, { checked: item.optionType === 'image', className: item.optionType === 'image' ? '!bg-indigo-600' : undefined, onCheckedChange: (checked) => switchOptionType(checked ? 'image' : 'text') }), _jsx(ImageIcon, { className: `h-3.5 w-3.5 ${item.optionType === 'image' ? 'text-indigo-600' : 'text-gray-400'}` }), _jsx("span", { className: `text-xs font-medium ${item.optionType === 'image' ? 'text-indigo-600' : 'text-gray-400'}`, children: "\u1EA2nh" })] })] }), item.options.map((option, optionIndex) => {
|
|
125
|
+
const isCorrect = option.id === item.correctAnswer;
|
|
126
|
+
const letter = optionLetter(optionIndex);
|
|
127
|
+
return (_jsx("div", { className: `group relative rounded-md px-3 py-2.5 transition-all duration-200 ${isCorrect
|
|
128
|
+
? 'border-2 border-green-400 bg-gradient-to-r from-green-50 to-emerald-50 shadow-sm shadow-green-100'
|
|
129
|
+
: 'border border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm'}`, children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: `flex h-8 w-8 flex-shrink-0 cursor-pointer items-center justify-center rounded-full text-xs font-bold leading-none transition-all ${isCorrect
|
|
130
|
+
? 'bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-sm'
|
|
131
|
+
: 'bg-gray-100 text-gray-800 hover:bg-indigo-100 hover:text-indigo-600'}`, onClick: () => onChange({ correctAnswer: option.id }), children: letter }), _jsx("div", { className: "flex-1 space-y-2", children: item.optionType === 'image' ? (_jsx(ChooseAnswerGroupOptionImageField, { itemIndex: itemIndex, optionIndex: optionIndex, imageUrl: getChooseAnswerGroupOptionImageSrc(option), onChange: (url) => updateOption(optionIndex, { imageUrl: url, text: '' }) })) : (_jsx(ChooseAnswerTextInput, { value: option.text, onChange: (text) => updateOption(optionIndex, { text }), isTipTapMode: isTipTapMode, compact: true, placeholder: `Nhập đáp án ${letter}...` })) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => onChange({ correctAnswer: option.id }), className: `h-7 w-7 ${isCorrect ? 'text-green-600 hover:text-green-700' : 'text-gray-400 hover:text-green-600'}`, title: "Ch\u1ECDn l\u00E0m \u0111\u00E1p \u00E1n \u0111\u00FAng", children: _jsx(CheckCircle2, { className: "h-3.5 w-3.5" }) }), item.options.length > 2 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => {
|
|
132
|
+
const nextOptions = item.options.filter((_, index) => index !== optionIndex);
|
|
133
|
+
onChange({
|
|
134
|
+
options: nextOptions,
|
|
135
|
+
correctAnswer: isCorrect
|
|
136
|
+
? (nextOptions[0]?.id || 'opt-1')
|
|
137
|
+
: item.correctAnswer,
|
|
138
|
+
});
|
|
139
|
+
}, className: "h-7 w-7 text-red-500 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] })] }) }, option.id));
|
|
140
|
+
}), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
|
|
141
|
+
onChange({
|
|
142
|
+
options: [...item.options, { id: nextOptionId(item.options), text: '' }],
|
|
143
|
+
});
|
|
144
|
+
}, className: "h-8 w-full gap-1.5 border-dashed border-green-200 px-2.5 text-xs text-green-600 hover:bg-green-50 hover:text-green-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm \u0111\u00E1p \u00E1n"] }), !hasCorrectAnswer && (_jsx("div", { className: "flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50 p-2 text-xs text-amber-800", children: _jsx("span", { children: "Ch\u01B0a ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng. Click ch\u1EEF c\u00E1i ho\u1EB7c \u2713 \u0111\u1EC3 ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng." }) }))] })] }));
|
|
145
|
+
}
|
|
@@ -18,13 +18,6 @@ function asOptionId(value) {
|
|
|
18
18
|
}
|
|
19
19
|
return '';
|
|
20
20
|
}
|
|
21
|
-
function asOptionalUrl(value) {
|
|
22
|
-
if (typeof value !== 'string') {
|
|
23
|
-
return undefined;
|
|
24
|
-
}
|
|
25
|
-
const trimmed = value.trim();
|
|
26
|
-
return trimmed !== '' ? trimmed : undefined;
|
|
27
|
-
}
|
|
28
21
|
export function createEmptyChooseAnswerGroupItem(questionNumber) {
|
|
29
22
|
return {
|
|
30
23
|
question: '',
|
|
@@ -32,7 +25,6 @@ export function createEmptyChooseAnswerGroupItem(questionNumber) {
|
|
|
32
25
|
options: [
|
|
33
26
|
{ id: 'opt-1', text: '' },
|
|
34
27
|
{ id: 'opt-2', text: '' },
|
|
35
|
-
{ id: 'opt-3', text: '' },
|
|
36
28
|
],
|
|
37
29
|
correctAnswer: 'opt-1',
|
|
38
30
|
isExample: false,
|
|
@@ -64,11 +56,10 @@ export function mapChooseAnswerGroupItem(item, index, fallbackAnswer) {
|
|
|
64
56
|
: [
|
|
65
57
|
{ id: 'opt-1', text: '' },
|
|
66
58
|
{ id: 'opt-2', text: '' },
|
|
67
|
-
{ id: 'opt-3', text: '' },
|
|
68
59
|
];
|
|
69
60
|
const isExample = record.isExample === true;
|
|
70
61
|
const imageUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(content.imageUrl));
|
|
71
|
-
const audioUrl =
|
|
62
|
+
const audioUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(content.audioUrl));
|
|
72
63
|
return {
|
|
73
64
|
question: asString(content.question) || asString(record.question),
|
|
74
65
|
optionType: asString(content.optionType) === 'image' ? 'image' : 'text',
|
|
@@ -78,7 +69,7 @@ export function mapChooseAnswerGroupItem(item, index, fallbackAnswer) {
|
|
|
78
69
|
points: isExample ? 0 : typeof record.points === 'number' ? record.points : 1,
|
|
79
70
|
questionNumber: typeof record.questionNumber === 'number' ? record.questionNumber : index + 1,
|
|
80
71
|
...(imageUrl !== undefined ? { imageUrl } : {}),
|
|
81
|
-
...(audioUrl ? { audioUrl } : {}),
|
|
72
|
+
...(audioUrl !== undefined ? { audioUrl } : {}),
|
|
82
73
|
};
|
|
83
74
|
}
|
|
84
75
|
export function mapQuestionToChooseAnswerGroupData(question) {
|
|
@@ -92,12 +83,12 @@ export function mapQuestionToChooseAnswerGroupData(question) {
|
|
|
92
83
|
: [];
|
|
93
84
|
if (Array.isArray(content.items)) {
|
|
94
85
|
const items = content.items.map((item, index) => mapChooseAnswerGroupItem(item, index, fallbackAnswers[index]));
|
|
95
|
-
const audioUrl =
|
|
86
|
+
const audioUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(asRecord(content.meta).audioUrl));
|
|
96
87
|
const imageUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(asRecord(content.meta).imageUrl));
|
|
97
88
|
return {
|
|
98
89
|
instruction: asString(asRecord(content.meta).instruction) || 'Choose the correct answer.',
|
|
99
90
|
passage: asString(asRecord(content.meta).passage),
|
|
100
|
-
...(audioUrl ? { audioUrl } : {}),
|
|
91
|
+
...(audioUrl !== undefined ? { audioUrl } : {}),
|
|
101
92
|
...(imageUrl !== undefined ? { imageUrl } : {}),
|
|
102
93
|
items,
|
|
103
94
|
explanation,
|
|
@@ -105,12 +96,12 @@ export function mapQuestionToChooseAnswerGroupData(question) {
|
|
|
105
96
|
};
|
|
106
97
|
}
|
|
107
98
|
if (Array.isArray(answer.items)) {
|
|
108
|
-
const audioUrl =
|
|
99
|
+
const audioUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(answer.audioUrl));
|
|
109
100
|
const imageUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(answer.imageUrl));
|
|
110
101
|
return {
|
|
111
102
|
instruction: asString(answer.instruction, 'Choose the correct answer.'),
|
|
112
103
|
passage: asString(answer.passage),
|
|
113
|
-
...(audioUrl ? { audioUrl } : {}),
|
|
104
|
+
...(audioUrl !== undefined ? { audioUrl } : {}),
|
|
114
105
|
...(imageUrl !== undefined ? { imageUrl } : {}),
|
|
115
106
|
items: answer.items.map((item, index) => mapChooseAnswerGroupItem(item, index, fallbackAnswers[index])),
|
|
116
107
|
explanation,
|
|
@@ -26,7 +26,7 @@ export const transformChooseTheCorrectAnswerGroup = (question) => {
|
|
|
26
26
|
}));
|
|
27
27
|
const correctAnswer = item.correctAnswer || options[0]?.id || 'opt-1';
|
|
28
28
|
const imageUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(item.imageUrl));
|
|
29
|
-
const audioUrl =
|
|
29
|
+
const audioUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(item.audioUrl));
|
|
30
30
|
return {
|
|
31
31
|
questionType: CHILD_QUESTION_TYPE,
|
|
32
32
|
...(item.isExample ? { isExample: true } : {}),
|
|
@@ -35,7 +35,7 @@ export const transformChooseTheCorrectAnswerGroup = (question) => {
|
|
|
35
35
|
options,
|
|
36
36
|
optionType: item.optionType || 'text',
|
|
37
37
|
...(imageUrl !== undefined ? { imageUrl } : {}),
|
|
38
|
-
...(audioUrl ? { audioUrl } : {}),
|
|
38
|
+
...(audioUrl !== undefined ? { audioUrl } : {}),
|
|
39
39
|
},
|
|
40
40
|
correctAnswer: { answer: correctAnswer },
|
|
41
41
|
points: item.isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
|
|
@@ -48,8 +48,9 @@ export const transformChooseTheCorrectAnswerGroup = (question) => {
|
|
|
48
48
|
if (answerData?.passage && answerData.passage.trim()) {
|
|
49
49
|
meta.passage = answerData.passage.trim();
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const groupAudioUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(answerData?.audioUrl));
|
|
52
|
+
if (groupAudioUrl !== undefined) {
|
|
53
|
+
meta.audioUrl = groupAudioUrl;
|
|
53
54
|
}
|
|
54
55
|
const groupImageUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(answerData?.imageUrl));
|
|
55
56
|
if (groupImageUrl !== undefined) {
|
package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js
CHANGED
|
@@ -23,12 +23,12 @@ export function ChooseThenAnswerGroupClient({ questionData, isReviewMode = false
|
|
|
23
23
|
text: next.text ?? current.text,
|
|
24
24
|
});
|
|
25
25
|
};
|
|
26
|
-
return (_jsxs("div", { className: "
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-gray-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-full bg-slate-800 text-xs font-bold text-white", children: item.questionNumber || itemIndex + 1 }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-[10px] font-semibold uppercase text-amber-800", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsxs("p", { className: "flex flex-wrap items-center gap-1.5 text-[15px] leading-relaxed text-slate-800", children: [_jsx("span", { children: parts[0] || '' }), _jsx("span", { className: "inline-flex items-center gap-1", children: item.options.map((option, optionIndex) => {
|
|
27
|
+
const selected = current.optionId === option.id;
|
|
28
|
+
const showReview = isReviewMode && !item.isExample && Boolean(expectedId);
|
|
29
|
+
const isCorrect = showReview && option.id === expectedId;
|
|
30
|
+
const isWrong = showReview && selected && option.id !== expectedId;
|
|
31
|
+
return (_jsxs("span", { className: "inline-flex items-center gap-1", children: [optionIndex > 0 && _jsx("span", { className: "text-slate-400", children: "|" }), _jsx("button", { type: "button", disabled: !canEdit || item.isExample, onClick: () => setAnswer({ optionId: option.id }), className: cn('rounded-md border px-2.5 py-1 text-sm transition-colors', selected && !isCorrect && !isWrong && 'border-black bg-black text-white', !selected && !isCorrect && !isWrong && 'border-gray-300 bg-white text-gray-900', isCorrect && 'border-emerald-500 bg-emerald-50 text-emerald-900', isWrong && 'border-red-500 bg-red-50 text-red-900', (!canEdit || item.isExample) && 'cursor-default'), children: option.text || option.id })] }, option.id));
|
|
32
|
+
}) }), _jsx("span", { children: parts.slice(1).join('{0}') })] }), _jsx("textarea", { value: current.text, disabled: !canEdit || item.isExample, onChange: (event) => setAnswer({ text: event.target.value }), placeholder: "Answer for you", rows: 3, className: "w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm text-slate-800 outline-none focus:border-slate-400 disabled:bg-slate-50" })] })] }, `${item.questionNumber}-${itemIndex}`));
|
|
33
33
|
}), isReviewMode && questionData.explanation && (_jsxs("div", { className: "flex items-start gap-2 rounded-xl border border-amber-100 bg-amber-50 px-4 py-3 text-sm text-amber-800", children: [_jsx(Lightbulb, { className: "mt-0.5 h-4 w-4 flex-shrink-0" }), questionData.explanation] }))] }));
|
|
34
34
|
}
|
package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { Button } from '../../../../components/ui/button';
|
|
5
5
|
import { Label } from '../../../../components/ui/label';
|
|
6
6
|
import { Input } from '../../../../components/ui/input';
|
|
7
7
|
import { PointsInput } from '../../../../components/ui/points-input';
|
|
8
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
9
8
|
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
10
9
|
import { Switch } from '../../../../components/ui/switch';
|
|
11
10
|
import { useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
12
|
-
import {
|
|
11
|
+
import { ListChecks, Plus, Star, Trash2 } from 'lucide-react';
|
|
12
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
13
13
|
import { ChooseThenAnswerGroupClient } from './ChooseThenAnswerGroupClient';
|
|
14
14
|
import { createEmptyItem, createEmptyOption, sumGradablePoints, } from './map-choose-then-answer-group-data';
|
|
15
15
|
import { CHOOSE_THEN_ANSWER_GROUP_DEFAULT_INSTRUCTION } from '../../_shared/types/choose-then-answer-group.type';
|
|
@@ -168,7 +168,10 @@ export function ChooseThenAnswerGroupCreator({ initialData, onChange, externalEr
|
|
|
168
168
|
})));
|
|
169
169
|
};
|
|
170
170
|
const displayErrors = [...errors, ...(externalErrors || [])];
|
|
171
|
-
|
|
171
|
+
if (isClientMode) {
|
|
172
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(ChooseThenAnswerGroupClient, { questionData: buildPayload(), isReviewMode: true })] }));
|
|
173
|
+
}
|
|
174
|
+
return (_jsxs("div", { className: "space-y-3", children: [displayErrors.length > 0 && (_jsx("ul", { className: "list-disc space-y-1 rounded-md border border-red-200 bg-red-50 p-2 pl-6 text-xs text-red-700", children: displayErrors.map((error) => (_jsx("li", { children: error }, error))) })), _jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(ListChecks, { className: "h-3.5 w-3.5" }), title: "Ch\u1ECDn r\u1ED3i tr\u1EA3 l\u1EDDi (Nh\u00F3m)", preview: { onClick: () => setIsClientMode(true) } }), children: _jsxs("div", { className: "grid gap-2.5 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "cta-title", className: "text-sm font-semibold text-gray-800", children: "Ti\u00EAu \u0111\u1EC1 (optional)" }), _jsx(Input, { id: "cta-title", value: title, onChange: (event) => setTitle(event.target.value), placeholder: "GRAMMAR", className: "h-10 border-gray-200 bg-white" })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "cta-instruction", className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(Input, { id: "cta-instruction", value: instruction, onChange: (event) => setInstruction(event.target.value), placeholder: CHOOSE_THEN_ANSWER_GROUP_DEFAULT_INSTRUCTION, className: "h-10 border-gray-200 bg-white" })] })] }) }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-sm", children: [_jsx(CardHeader, { className: "px-3 py-2", children: _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs(CardTitle, { className: "text-sm font-semibold text-gray-800", children: ["Danh s\u00E1ch c\u00E2u (", items.length, ")"] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addItem, className: "h-8 gap-1.5 px-2.5 text-xs", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), " Th\u00EAm c\u00E2u"] })] }) }), _jsxs(CardContent, { className: "space-y-2.5 px-3 pb-3", children: [items.map((item, index) => (_jsxs("div", { className: "space-y-2.5 rounded-md border border-gray-200 bg-white px-3 py-2.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", index + 1] }), items.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeItem(index), className: "h-7 w-7 text-red-500 hover:bg-red-50", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }), _jsx(Input, { value: item.question, onChange: (event) => updateItem(index, { question: event.target.value }), placeholder: "Did you eat {0} for lunch today?", className: "h-10 border-gray-200 bg-white" }), _jsxs("p", { className: "text-xs text-slate-500", children: ["D\u00F9ng ", '{0}', " cho ch\u1ED7 hi\u1EC7n chip ch\u1ECDn."] }), item.options.map((option, optionIndex) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { value: option.text, onChange: (event) => updateOption(index, optionIndex, event.target.value), placeholder: `Lựa chọn ${optionIndex + 1}`, className: "h-10 border-gray-200 bg-white" }), item.options.length > 2 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeOption(index, optionIndex), className: "h-7 w-7 text-red-500 hover:bg-red-50", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }, option.id))), _jsxs(Button, { type: "button", size: "sm", variant: "outline", onClick: () => addOption(index), className: "h-8 gap-1.5 px-2.5 text-xs", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), " Th\u00EAm l\u1EF1a ch\u1ECDn"] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("select", { className: "h-10 rounded-md border border-input bg-background px-3 text-sm", value: item.optionId, onChange: (event) => updateItem(index, { optionId: event.target.value }), children: item.options.map((option) => (_jsx("option", { value: option.id, children: option.text || option.id }, option.id))) }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Switch, { id: `cta-example-${index}`, checked: item.isExample === true, onCheckedChange: (checked) => updateItem(index, { isExample: checked }) }), _jsxs(Label, { htmlFor: `cta-example-${index}`, className: "flex cursor-pointer items-center gap-1 text-xs", children: [_jsx(Star, { className: "h-3 w-3 text-amber-500" }), "Example"] })] }), !item.isExample && (_jsx(PointsInput, { value: item.points, allowZero: true, onChange: (event) => updateItem(index, {
|
|
172
175
|
points: Number.parseInt(event.target.value, 10) || 1,
|
|
173
|
-
}) }))] })] }
|
|
176
|
+
}) }))] })] }, `${item.questionNumber}-${index}`))), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) })] })] })] }));
|
|
174
177
|
}
|