@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
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useRef, useEffect, useState } from 'react';
|
|
4
|
-
import { useParams } from 'next/navigation';
|
|
5
|
-
import { Button } from '../../../../components/ui/button';
|
|
6
|
-
import { Label } from '../../../../components/ui/label';
|
|
7
|
-
import { Input } from '../../../../components/ui/input';
|
|
8
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
9
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
10
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
11
|
-
import { useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
12
|
-
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
13
|
-
import { usePresignedFileUrl } from '../../../../shared/lib/hooks/usePresignedFileUrl';
|
|
14
|
-
import { Mic, Type, Volume2, BookOpen, MessageSquareText, Info, Plus, Trash2, Loader2, Users, UserCheck, } from 'lucide-react';
|
|
15
|
-
import { toast } from 'sonner';
|
|
16
|
-
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
17
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
18
|
-
import { z } from 'zod';
|
|
19
|
-
// ─── Validation Schema ─────────────────────────────────────────────────────
|
|
20
|
-
const gnSpeakingInterviewFormSchema = z.object({
|
|
21
|
-
inputType: z.enum(['AUDIO', 'TEXT']),
|
|
22
|
-
audioUrl: z.string().optional(),
|
|
23
|
-
imageUrl: z.string().optional(),
|
|
24
|
-
questionText: z.string().optional(),
|
|
25
|
-
targetCandidate: z.enum(['VIRTUAL', 'REAL']),
|
|
26
|
-
virtualAnswerText: z.string().optional(),
|
|
27
|
-
virtualAnswerAudioUrl: z.string().optional(),
|
|
28
|
-
isExtendedResponse: z.boolean().optional(),
|
|
29
|
-
sampleAnswers: z.array(z.string()).optional(),
|
|
30
|
-
explanation: z.string().optional(),
|
|
31
|
-
points: z.number().min(0).default(1),
|
|
32
|
-
});
|
|
33
|
-
// ─── Main Creator Component ─────────────────────────────────────────────────
|
|
34
|
-
export function GNSpeakingInterviewCreator({ initialData, onSave, onChange, externalErrors, validationRef, }) {
|
|
35
|
-
const apiData = initialData;
|
|
36
|
-
const params = useParams();
|
|
37
|
-
const examRouteId = params?.examId || params?.id;
|
|
38
|
-
const initSampleAnswers = (() => {
|
|
39
|
-
const newArr = apiData?.expectedAnswers || apiData?.correctAnswer?.expectedAnswers;
|
|
40
|
-
if (Array.isArray(newArr) && newArr.length > 0)
|
|
41
|
-
return newArr;
|
|
42
|
-
const arr = apiData?.sampleAnswers || apiData?.content?.sampleAnswers;
|
|
43
|
-
if (Array.isArray(arr) && arr.length > 0)
|
|
44
|
-
return arr;
|
|
45
|
-
const single = apiData?.sampleAnswer || apiData?.correctAnswer?.sampleAnswer;
|
|
46
|
-
if (single)
|
|
47
|
-
return [single];
|
|
48
|
-
return [''];
|
|
49
|
-
})();
|
|
50
|
-
const form = useReactHookForm({
|
|
51
|
-
defaultValues: {
|
|
52
|
-
inputType: apiData?.inputType || apiData?.content?.inputType || 'TEXT',
|
|
53
|
-
audioUrl: apiData?.audioUrl || apiData?.content?.audioUrl || '',
|
|
54
|
-
imageUrl: apiData?.imageUrl || apiData?.content?.imageUrl || '',
|
|
55
|
-
questionText: apiData?.questionText || apiData?.content?.questionText || '',
|
|
56
|
-
targetCandidate: apiData?.targetCandidate || apiData?.content?.targetCandidate || 'REAL',
|
|
57
|
-
virtualAnswerText: apiData?.virtualAnswerText ||
|
|
58
|
-
apiData?.content?.virtualAnswer?.answerText ||
|
|
59
|
-
'',
|
|
60
|
-
virtualAnswerAudioUrl: apiData?.virtualAnswerAudioUrl ||
|
|
61
|
-
apiData?.content?.virtualAnswer?.audioUrl ||
|
|
62
|
-
'',
|
|
63
|
-
isExtendedResponse: apiData?.isExtendedResponse || apiData?.content?.isExtendedResponse || false,
|
|
64
|
-
sampleAnswers: initSampleAnswers,
|
|
65
|
-
explanation: apiData?.explanation || '',
|
|
66
|
-
points: apiData?.points ?? 1,
|
|
67
|
-
},
|
|
68
|
-
zodSchema: gnSpeakingInterviewFormSchema,
|
|
69
|
-
mode: 'onBlur',
|
|
70
|
-
});
|
|
71
|
-
const previousInitialDataRef = useRef(initialData);
|
|
72
|
-
const isInitializingRef = useRef(false);
|
|
73
|
-
const onChangeRef = useRef(onChange);
|
|
74
|
-
const previousPayloadRef = useRef('');
|
|
75
|
-
const [isGeneratingAudio, setIsGeneratingAudio] = useState(false);
|
|
76
|
-
const [isGeneratingVirtualAudio, setIsGeneratingVirtualAudio] = useState(false);
|
|
77
|
-
const [previewAudioUrl, setPreviewAudioUrl] = useState(null);
|
|
78
|
-
const [virtualPreviewAudioUrl, setVirtualPreviewAudioUrl] = useState(null);
|
|
79
|
-
useEffect(() => {
|
|
80
|
-
onChangeRef.current = onChange;
|
|
81
|
-
}, [onChange]);
|
|
82
|
-
const debouncedOnChange = useDebouncedCallback((data) => {
|
|
83
|
-
onChangeRef.current?.(data);
|
|
84
|
-
}, 300);
|
|
85
|
-
const watchedInputType = form.watch('inputType');
|
|
86
|
-
const watchedAudioUrl = form.watch('audioUrl');
|
|
87
|
-
const watchedImageUrl = form.watch('imageUrl');
|
|
88
|
-
const { previewUrl: imagePreviewUrl, isLoading: isImagePreviewLoading } = usePresignedFileUrl(watchedImageUrl);
|
|
89
|
-
const watchedQuestionText = form.watch('questionText');
|
|
90
|
-
const watchedTargetCandidate = form.watch('targetCandidate');
|
|
91
|
-
const watchedVirtualAnswerText = form.watch('virtualAnswerText');
|
|
92
|
-
const watchedVirtualAnswerAudioUrl = form.watch('virtualAnswerAudioUrl');
|
|
93
|
-
const watchedIsExtendedResponse = form.watch('isExtendedResponse');
|
|
94
|
-
const watchedSampleAnswers = form.watch('sampleAnswers') || [''];
|
|
95
|
-
const watchedExplanation = form.watch('explanation');
|
|
96
|
-
const watchedPoints = form.watch('points');
|
|
97
|
-
// For the demo, we'll treat 'A' as virtual by default
|
|
98
|
-
// The actual virtualCandidate is set at group level, but in the creator we show
|
|
99
|
-
// conditional UI based on targetCandidate
|
|
100
|
-
const isVirtualQuestion = watchedTargetCandidate === 'VIRTUAL'; // VIRTUAL = virtual by default
|
|
101
|
-
// Auto-set points for Virtual candidate to 0, and revert to 1 if changed back
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
if (isInitializingRef.current)
|
|
104
|
-
return;
|
|
105
|
-
if (isVirtualQuestion) {
|
|
106
|
-
if (watchedPoints !== 0) {
|
|
107
|
-
form.setValue('points', 0, { shouldValidate: true });
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
if (watchedPoints === 0) {
|
|
112
|
-
form.setValue('points', 1, { shouldValidate: true });
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}, [isVirtualQuestion, watchedPoints, form]);
|
|
116
|
-
// Expose validation ref
|
|
117
|
-
useEffect(() => {
|
|
118
|
-
if (validationRef) {
|
|
119
|
-
validationRef.current = {
|
|
120
|
-
triggerValidation: async () => await form.trigger(),
|
|
121
|
-
getFormData: () => form.getValues(),
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
}, [validationRef, form]);
|
|
125
|
-
// Reset form when initialData changes externally
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
const apiD = initialData;
|
|
128
|
-
const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
|
|
129
|
-
if (!initialDataChanged)
|
|
130
|
-
return;
|
|
131
|
-
previousInitialDataRef.current = initialData;
|
|
132
|
-
const resetSampleAnswers = (() => {
|
|
133
|
-
const newArr = apiD?.expectedAnswers || apiD?.correctAnswer?.expectedAnswers;
|
|
134
|
-
if (Array.isArray(newArr) && newArr.length > 0)
|
|
135
|
-
return newArr;
|
|
136
|
-
const arr = apiD?.sampleAnswers || apiD?.content?.sampleAnswers;
|
|
137
|
-
if (Array.isArray(arr) && arr.length > 0)
|
|
138
|
-
return arr;
|
|
139
|
-
return [''];
|
|
140
|
-
})();
|
|
141
|
-
isInitializingRef.current = true;
|
|
142
|
-
form.reset({
|
|
143
|
-
inputType: apiD?.inputType || apiD?.content?.inputType || 'TEXT',
|
|
144
|
-
audioUrl: apiD?.audioUrl || apiD?.content?.audioUrl || '',
|
|
145
|
-
imageUrl: apiD?.imageUrl || apiD?.content?.imageUrl || '',
|
|
146
|
-
questionText: apiD?.questionText || apiD?.content?.questionText || '',
|
|
147
|
-
targetCandidate: apiD?.targetCandidate || apiD?.content?.targetCandidate || 'REAL',
|
|
148
|
-
virtualAnswerText: apiD?.virtualAnswerText || apiD?.content?.virtualAnswer?.answerText || '',
|
|
149
|
-
virtualAnswerAudioUrl: apiD?.virtualAnswerAudioUrl || apiD?.content?.virtualAnswer?.audioUrl || '',
|
|
150
|
-
isExtendedResponse: apiD?.isExtendedResponse || apiD?.content?.isExtendedResponse || false,
|
|
151
|
-
sampleAnswers: resetSampleAnswers,
|
|
152
|
-
explanation: apiD?.explanation || '',
|
|
153
|
-
points: apiD?.points ?? 1,
|
|
154
|
-
});
|
|
155
|
-
setTimeout(() => {
|
|
156
|
-
isInitializingRef.current = false;
|
|
157
|
-
}, 150);
|
|
158
|
-
}, [initialData, form]);
|
|
159
|
-
// Debounced onChange
|
|
160
|
-
useEffect(() => {
|
|
161
|
-
if (isInitializingRef.current)
|
|
162
|
-
return;
|
|
163
|
-
const payload = {
|
|
164
|
-
inputType: watchedInputType,
|
|
165
|
-
audioUrl: watchedAudioUrl,
|
|
166
|
-
imageUrl: watchedImageUrl,
|
|
167
|
-
questionText: watchedQuestionText,
|
|
168
|
-
targetCandidate: watchedTargetCandidate,
|
|
169
|
-
virtualAnswerText: watchedVirtualAnswerText,
|
|
170
|
-
virtualAnswerAudioUrl: watchedVirtualAnswerAudioUrl,
|
|
171
|
-
isExtendedResponse: watchedIsExtendedResponse,
|
|
172
|
-
sampleAnswers: watchedSampleAnswers,
|
|
173
|
-
explanation: watchedExplanation,
|
|
174
|
-
points: watchedPoints,
|
|
175
|
-
};
|
|
176
|
-
const payloadString = JSON.stringify(payload);
|
|
177
|
-
if (previousPayloadRef.current !== payloadString) {
|
|
178
|
-
previousPayloadRef.current = payloadString;
|
|
179
|
-
debouncedOnChange(payload);
|
|
180
|
-
}
|
|
181
|
-
}, [
|
|
182
|
-
watchedInputType, watchedAudioUrl, watchedImageUrl, watchedQuestionText, watchedTargetCandidate,
|
|
183
|
-
watchedVirtualAnswerText, watchedVirtualAnswerAudioUrl, watchedIsExtendedResponse,
|
|
184
|
-
watchedSampleAnswers, watchedExplanation, watchedPoints, debouncedOnChange,
|
|
185
|
-
]);
|
|
186
|
-
// TTS handlers
|
|
187
|
-
const handleGenerateAudio = async () => {
|
|
188
|
-
const text = form.getValues('questionText');
|
|
189
|
-
if (!text?.trim()) {
|
|
190
|
-
toast.error('Vui lòng nhập nội dung câu hỏi trước khi tạo audio!');
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
try {
|
|
194
|
-
setIsGeneratingAudio(true);
|
|
195
|
-
toast.error('TTS chưa được tích hợp trong component SDK');
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
catch (error) {
|
|
199
|
-
toast.error(error?.response?.data?.message || 'Lỗi khi tạo audio');
|
|
200
|
-
}
|
|
201
|
-
finally {
|
|
202
|
-
setIsGeneratingAudio(false);
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
const handleGenerateVirtualAudio = async () => {
|
|
206
|
-
const text = form.getValues('virtualAnswerText');
|
|
207
|
-
if (!text?.trim()) {
|
|
208
|
-
toast.error('Vui lòng nhập câu trả lời HS ảo trước khi tạo audio!');
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
try {
|
|
212
|
-
setIsGeneratingVirtualAudio(true);
|
|
213
|
-
toast.error('TTS chưa được tích hợp trong component SDK');
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
catch (error) {
|
|
217
|
-
toast.error(error?.response?.data?.message || 'Lỗi khi tạo audio');
|
|
218
|
-
}
|
|
219
|
-
finally {
|
|
220
|
-
setIsGeneratingVirtualAudio(false);
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
// Dynamic expected answers
|
|
224
|
-
const handleAddSampleAnswer = () => {
|
|
225
|
-
form.setValue('sampleAnswers', [...watchedSampleAnswers, ''], { shouldValidate: false });
|
|
226
|
-
};
|
|
227
|
-
const handleRemoveSampleAnswer = (index) => {
|
|
228
|
-
const current = [...watchedSampleAnswers];
|
|
229
|
-
current.splice(index, 1);
|
|
230
|
-
if (current.length === 0)
|
|
231
|
-
current.push('');
|
|
232
|
-
form.setValue('sampleAnswers', current, { shouldValidate: true });
|
|
233
|
-
};
|
|
234
|
-
const handleSampleAnswerChange = (index, value) => {
|
|
235
|
-
const current = [...watchedSampleAnswers];
|
|
236
|
-
current[index] = value;
|
|
237
|
-
form.setValue('sampleAnswers', current, { shouldValidate: true });
|
|
238
|
-
};
|
|
239
|
-
return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "blue", steps: [
|
|
240
|
-
{ text: 'Chọn đối tượng trả lời (Thí sinh ảo / Thí sinh thật).' },
|
|
241
|
-
{ text: 'Nhập câu hỏi của Giám khảo (Examiner Prompt) bằng Audio hoặc Text.', highlight: true },
|
|
242
|
-
{ text: 'Đối với Thí sinh ảo: cung cấp câu trả lời mô phỏng.' },
|
|
243
|
-
{ text: 'Đối với Thí sinh thật: thêm các đáp án mẫu (Sample Answers) để AI tham chiếu chấm điểm.' },
|
|
244
|
-
], tips: [
|
|
245
|
-
'Câu của Thí sinh ảo sẽ tự động phát và điểm số bằng 0.',
|
|
246
|
-
'Nên sử dụng TTS (Text-to-Speech) để tiết kiệm thời gian chuẩn bị audio.',
|
|
247
|
-
] }), _jsxs(Card, { className: "overflow-hidden border border-slate-200 bg-white shadow-sm", children: [_jsx(CardHeader, { className: "border-b border-slate-100 bg-slate-50/50 pb-4", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-blue-100 text-blue-700", children: _jsx(Users, { className: "h-4 w-4" }) }), _jsx(CardTitle, { className: "text-lg font-semibold text-slate-800", children: "C\u1EA5u h\u00ECnh C\u00E2u h\u1ECFi & \u0110\u1ED1i t\u01B0\u1EE3ng" })] }) }), _jsxs(CardContent, { className: "p-0", children: [_jsxs("div", { className: "p-6 space-y-8", children: [_jsxs("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [_jsx("div", { className: "space-y-1", children: _jsx("div", { className: "w-full xl:w-2/3 animate-in fade-in zoom-in-95 duration-200", children: _jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), error: form.hasError('points') ? form.getFieldError('points') : undefined, disabled: isVirtualQuestion, allowZero: true }) }) }), _jsxs("div", { className: "space-y-3", children: [_jsxs(Label, { className: "text-sm font-semibold text-slate-700 flex items-center gap-2", children: [_jsx(UserCheck, { className: "h-4 w-4" }), "\u0110\u1ED1i t\u01B0\u1EE3ng tr\u1EA3 l\u1EDDi"] }), _jsxs("div", { className: "flex w-fit rounded-lg border border-slate-200 bg-slate-100 p-1", children: [_jsx("button", { type: "button", onClick: () => form.setValue('targetCandidate', 'VIRTUAL', { shouldValidate: true }), className: `flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all ${isVirtualQuestion
|
|
248
|
-
? 'bg-white text-blue-700 shadow-sm'
|
|
249
|
-
: 'text-slate-500 hover:bg-slate-200/50 hover:text-slate-700'}`, children: "\uD83E\uDD16 Th\u00ED sinh \u1EA3o (Virtual)" }), _jsx("button", { type: "button", onClick: () => form.setValue('targetCandidate', 'REAL', { shouldValidate: true }), className: `flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all ${!isVirtualQuestion
|
|
250
|
-
? 'bg-white text-emerald-700 shadow-sm'
|
|
251
|
-
: 'text-slate-500 hover:bg-slate-200/50 hover:text-slate-700'}`, children: "\uD83D\uDC66 Th\u00ED sinh th\u1EADt (Real)" })] })] })] }), _jsxs("div", { className: `flex items-start gap-3 rounded-lg border px-4 py-3 text-sm transition-colors duration-300 ${isVirtualQuestion
|
|
252
|
-
? 'border-blue-100 bg-blue-50/50 text-blue-800'
|
|
253
|
-
: 'border-emerald-100 bg-emerald-50/50 text-emerald-800'}`, children: [_jsx(Info, { className: `mt-0.5 h-4 w-4 shrink-0 ${isVirtualQuestion ? 'text-blue-500' : 'text-emerald-500'}` }), _jsx("p", { children: isVirtualQuestion ? (_jsxs("span", { children: [_jsx("strong", { children: "C\u1EA3nh b\u00E1o:" }), " C\u00E2u n\u00E0y d\u00E0nh cho ", _jsx("strong", { children: "Th\u00ED sinh \u1EA3o" }), ". H\u1EC7 th\u1ED1ng s\u1EBD b\u1ECF qua b\u01B0\u1EDBc thu \u00E2m v\u00E0 t\u1EF1 \u0111\u1ED9ng ph\u00E1t audio m\u00F4 ph\u1ECFng. Khi th\u00ED sinh thi, c\u00E2u n\u00E0y mang t\u00EDnh ch\u1EA5t giao ti\u1EBFp (\u0110i\u1EC3m m\u1EB7c \u0111\u1ECBnh = 0)."] })) : (_jsxs("span", { children: [_jsx("strong", { children: "L\u01B0u \u00FD:" }), " C\u00E2u n\u00E0y d\u00E0nh cho ", _jsx("strong", { children: "Th\u00ED sinh th\u1EADt" }), " l\u00E0m b\u00E0i. AI s\u1EBD thu \u00E2m v\u00E0 t\u1EF1 \u0111\u1ED9ng ch\u1EA5m \u0111i\u1EC3m d\u1EF1a b\u1EB1ng c\u00E1ch so s\u00E1nh v\u1EDBi c\u00E1c ", _jsx("strong", { children: "\u0110\u00E1p \u00E1n m\u1EABu" }), " b\u00EAn d\u01B0\u1EDBi."] })) })] }), _jsx("hr", { className: "border-slate-100" }), _jsxs("div", { className: "space-y-4", children: [_jsx(Label, { className: "block text-sm font-semibold text-slate-700", children: "C\u00E2u h\u1ECFi c\u1EE7a Gi\u00E1m kh\u1EA3o (Examiner Prompt)" }), _jsxs("div", { className: "flex w-fit rounded-lg border border-slate-200 bg-slate-100 p-1 mb-4", children: [_jsxs("button", { type: "button", onClick: () => form.setValue('inputType', 'AUDIO', { shouldValidate: true }), className: `flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all ${watchedInputType === 'AUDIO'
|
|
254
|
-
? 'bg-white text-slate-800 shadow-sm'
|
|
255
|
-
: 'text-slate-500 hover:bg-slate-200/50 hover:text-slate-700'}`, children: [_jsx(Mic, { className: "h-3.5 w-3.5" }), " Upload Audio"] }), _jsxs("button", { type: "button", onClick: () => form.setValue('inputType', 'TEXT', { shouldValidate: true }), className: `flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all ${watchedInputType === 'TEXT'
|
|
256
|
-
? 'bg-white text-slate-800 shadow-sm'
|
|
257
|
-
: 'text-slate-500 hover:bg-slate-200/50 hover:text-slate-700'}`, children: [_jsx(Type, { className: "h-3.5 w-3.5" }), " Nh\u1EADp v\u0103n b\u1EA3n (TTS)"] })] }), _jsx("div", { className: "max-w-3xl", children: watchedInputType === 'AUDIO' ? (_jsx(FileUpload, { id: "audioUrl", label: "", accept: "audio/*", value: watchedAudioUrl || '', onChange: (url) => form.setValue('audioUrl', url, { shouldValidate: true }), maxSize: 20, placeholder: "T\u1EA3i l\u00EAn file audio c\u00E2u h\u1ECFi (MP3, WAV...)", autoUpload: true, prefix: "speaking/gn-interview" })) : (_jsxs("div", { className: "space-y-3", children: [_jsx(Input, { ...form.register('questionText'), placeholder: "VD: Do you work or are you a student?", className: "border-slate-200 focus:border-blue-400 focus:ring-blue-100" }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs(Button, { type: "button", variant: "default", size: "sm", onClick: handleGenerateAudio, disabled: isGeneratingAudio || !watchedQuestionText, className: "gap-2 rounded-none bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-bold shadow-md shadow-blue-500/30 hover:shadow-lg hover:shadow-blue-500/40 hover:-translate-y-0.5 transition-all", children: [isGeneratingAudio ? (_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(Volume2, { className: "h-3.5 w-3.5" })), "T\u1EA1o Audio"] }), previewAudioUrl && (_jsx("audio", { controls: true, src: previewAudioUrl, className: "h-8 max-w-[250px]" }))] })] })) })] }), _jsx("hr", { className: "border-slate-100" }), _jsxs("div", { className: "space-y-4", children: [_jsx(Label, { className: "block text-sm font-semibold text-slate-700", children: "H\u00ECnh \u1EA3nh (Tu\u1EF3 ch\u1ECDn)" }), _jsxs("div", { className: "max-w-3xl", children: [_jsx(FileUpload, { id: "imageUrl", label: "", accept: "image/*", value: watchedImageUrl || '', onChange: (url) => form.setValue('imageUrl', url, { shouldValidate: true }), maxSize: 5, placeholder: "T\u1EA3i l\u00EAn h\u00ECnh \u1EA3nh tham kh\u1EA3o (JPG, PNG...)", autoUpload: true, prefix: "speaking/gn-interview" }), watchedImageUrl && (_jsx("div", { className: "mt-4", children: isImagePreviewLoading ? (_jsx("div", { className: "flex h-32 w-full max-w-[240px] items-center justify-center rounded-lg border border-slate-200 bg-slate-50", children: _jsx(Loader2, { className: "h-5 w-5 animate-spin text-slate-400" }) })) : imagePreviewUrl ? (_jsx("div", { className: "relative inline-block overflow-hidden rounded-lg border border-slate-200 shadow-sm bg-slate-50 p-1", children: _jsx("img", { src: imagePreviewUrl, alt: "Preview", className: "max-h-48 object-contain rounded" }) })) : null }))] })] })] }), _jsx("div", { className: `border-t transition-colors duration-500 ${isVirtualQuestion ? 'border-blue-100 bg-blue-50/40' : 'border-emerald-100 bg-emerald-50/40'}`, children: _jsx("div", { className: "p-6", children: isVirtualQuestion ? (
|
|
258
|
-
/* Virtual Candidate Settings */
|
|
259
|
-
_jsxs("div", { className: "space-y-5 animate-in fade-in slide-in-from-bottom-2 duration-300", children: [_jsxs("h4", { className: "flex items-center gap-2 text-sm font-semibold text-blue-900", children: [_jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded bg-blue-200/50 text-xs text-blue-700", children: "\uD83E\uDD16" }), "C\u1EA5u h\u00ECnh c\u00E2u tr\u1EA3 l\u1EDDi m\u00F4 ph\u1ECFng"] }), _jsxs("div", { className: "space-y-4 max-w-3xl", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { className: "text-sm font-medium text-slate-700", children: "N\u1ED9i dung c\u00E2u tr\u1EA3 l\u1EDDi (Text)" }), _jsx(Textarea, { value: watchedVirtualAnswerText || '', onChange: (e) => form.setValue('virtualAnswerText', e.target.value, { shouldValidate: true }), placeholder: "VD: My name is Lan. I am a student.", rows: 2, className: "resize-none border-slate-200 focus:border-blue-400 focus:ring-blue-100" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { className: "text-sm font-medium text-slate-700", children: "Audio c\u00E2u tr\u1EA3 l\u1EDDi" }), _jsx(FileUpload, { id: "virtualAnswerAudioUrl", label: "", accept: "audio/*", value: watchedVirtualAnswerAudioUrl || '', onChange: (url) => form.setValue('virtualAnswerAudioUrl', url, { shouldValidate: true }), maxSize: 20, placeholder: "C\u00F3 th\u1EC3 upload audio ho\u1EB7c t\u1EA1o t\u1EF1 \u0111\u1ED9ng b\u1EB1ng TTS", autoUpload: true, prefix: "speaking/gn-interview/virtual" }), _jsxs("div", { className: "flex items-center gap-4 mt-2", children: [_jsxs(Button, { type: "button", variant: "default", size: "sm", onClick: handleGenerateVirtualAudio, disabled: isGeneratingVirtualAudio || !watchedVirtualAnswerText, className: "gap-2 rounded-none bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-bold shadow-md shadow-blue-500/30 hover:shadow-lg hover:shadow-blue-500/40 hover:-translate-y-0.5 transition-all", children: [isGeneratingVirtualAudio ? (_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(Volume2, { className: "h-3.5 w-3.5" })), "T\u1EA1o Audio t\u1EEB Text"] }), virtualPreviewAudioUrl && (_jsx("audio", { controls: true, src: virtualPreviewAudioUrl, className: "h-8 max-w-[250px]" }))] })] })] })] })) : (
|
|
260
|
-
/* Real Candidate Sample Answers */
|
|
261
|
-
_jsxs("div", { className: "space-y-5 animate-in fade-in slide-in-from-bottom-2 duration-300", children: [_jsx("div", { className: "flex items-center gap-2", children: _jsxs("h4", { className: "flex items-center gap-2 text-sm font-semibold text-emerald-900", children: [_jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded bg-emerald-200/50 text-emerald-700", children: _jsx(MessageSquareText, { className: "h-3.5 w-3.5" }) }), "\u0110\u00E1p \u00E1n m\u1EABu tham chi\u1EBFu (Sample Answers)"] }) }), _jsxs("div", { className: "space-y-3 max-w-4xl", children: [watchedSampleAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "mt-1.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-emerald-100 text-xs font-semibold text-emerald-700", children: index + 1 }), _jsx(Textarea, { value: answer || '', onChange: (e) => handleSampleAnswerChange(index, e.target.value), placeholder: `VD: ${index === 0 ? 'I am a student. I study at a university.' : 'No, I currently work.'}`, rows: 2, className: "flex-1 resize-none border-slate-200 bg-white focus:border-emerald-400 focus:ring-emerald-100" }), watchedSampleAnswers.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "mt-1 h-8 w-8 shrink-0 text-slate-400 hover:bg-red-50 hover:text-red-600", onClick: () => handleRemoveSampleAnswer(index), children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }, index))), _jsx("div", { className: "pt-2", children: _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "gap-1.5 border-emerald-200 bg-emerald-50/50 text-emerald-700 hover:bg-emerald-100 hover:text-emerald-800", onClick: handleAddSampleAnswer, children: [_jsx(Plus, { className: "h-4 w-4" }), " Th\u00EAm \u0111\u00E1p \u00E1n m\u1EABu"] }) })] })] })) }) }), externalErrors && externalErrors.length > 0 && (_jsx("div", { className: "border-t border-red-100 bg-red-50 p-6", children: _jsx("div", { className: "flex items-start gap-3", children: _jsxs("div", { className: "text-sm", children: [_jsx("p", { className: "font-semibold text-red-800", children: "C\u1EA7n ph\u1EA3i s\u1EEDa c\u00E1c l\u1ED7i sau:" }), _jsx("ul", { className: "mt-1 list-inside list-disc text-red-700", children: externalErrors.map((error, index) => (_jsx("li", { children: error }, index))) })] }) }) }))] })] }), _jsxs(Card, { className: "border border-slate-200 bg-slate-50/50 shadow-sm", children: [_jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-slate-200/50 text-slate-600", children: _jsx(BookOpen, { className: "h-3.5 w-3.5" }) }), _jsx(CardTitle, { className: "text-base font-semibold text-slate-800", children: "Gi\u1EA3i th\u00EDch chi ti\u1EBFt" })] }), _jsx("span", { className: "text-xs font-medium text-slate-500 uppercase tracking-wider", children: "T\u00F9y ch\u1ECDn" })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { value: watchedExplanation || '', onChange: (e) => form.setValue('explanation', e.target.value, { shouldValidate: true }), placeholder: "Ghi ch\u00FA th\u00EAm v\u1EC1 ti\u00EAu ch\u00ED ch\u1EA5m \u0111i\u1EC3m, l\u01B0u \u00FD cho gi\u00E1m kh\u1EA3o...", rows: 2, className: "resize-none border-slate-200 bg-white focus:border-slate-300 focus:ring-slate-100" }) })] })] }));
|
|
262
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { GNSpeakingInterviewCreator } from './GNSpeakingInterviewCreator';
|
|
2
|
-
export const gnSpeakingInterviewRegistration = {
|
|
3
|
-
component: GNSpeakingInterviewCreator,
|
|
4
|
-
transformInitialData: (initialData) => {
|
|
5
|
-
const content = initialData?.content;
|
|
6
|
-
const answer = initialData?.answer || initialData?.correctAnswer || initialData?.correct_answer;
|
|
7
|
-
const points = initialData?.total_points !== undefined && initialData?.total_points !== null
|
|
8
|
-
? parseFloat(initialData.total_points)
|
|
9
|
-
: (initialData?.points ?? content?.points ?? answer?.points ?? 1);
|
|
10
|
-
const explanation = initialData?.explanation || content?.explanation || answer?.explanation || '';
|
|
11
|
-
// Normalize expected answers
|
|
12
|
-
const normalizeSampleAnswers = () => {
|
|
13
|
-
const newArr = answer?.expectedAnswers;
|
|
14
|
-
if (Array.isArray(newArr) && newArr.length > 0)
|
|
15
|
-
return newArr;
|
|
16
|
-
const arr = answer?.sampleAnswers;
|
|
17
|
-
if (Array.isArray(arr) && arr.length > 0)
|
|
18
|
-
return arr;
|
|
19
|
-
const single = answer?.sampleAnswer;
|
|
20
|
-
if (single)
|
|
21
|
-
return [single];
|
|
22
|
-
return [''];
|
|
23
|
-
};
|
|
24
|
-
if (content && typeof content === 'object' && content.targetCandidate !== undefined) {
|
|
25
|
-
return {
|
|
26
|
-
inputType: content.inputType || 'TEXT',
|
|
27
|
-
audioUrl: content.audioUrl || '',
|
|
28
|
-
imageUrl: content.imageUrl || '',
|
|
29
|
-
questionText: content.questionText || '',
|
|
30
|
-
targetCandidate: content.targetCandidate || 'REAL',
|
|
31
|
-
virtualAnswerText: content.virtualAnswer?.answerText || '',
|
|
32
|
-
virtualAnswerAudioUrl: content.virtualAnswer?.audioUrl || '',
|
|
33
|
-
isExtendedResponse: content.isExtendedResponse || false,
|
|
34
|
-
sampleAnswers: normalizeSampleAnswers(),
|
|
35
|
-
explanation,
|
|
36
|
-
points,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
return {
|
|
40
|
-
inputType: answer?.inputType || content?.inputType || 'TEXT',
|
|
41
|
-
audioUrl: answer?.audioUrl || content?.audioUrl || '',
|
|
42
|
-
imageUrl: answer?.imageUrl || content?.imageUrl || '',
|
|
43
|
-
questionText: answer?.questionText || content?.questionText || '',
|
|
44
|
-
targetCandidate: answer?.targetCandidate || content?.targetCandidate || 'REAL',
|
|
45
|
-
virtualAnswerText: answer?.virtualAnswerText || content?.virtualAnswer?.answerText || '',
|
|
46
|
-
virtualAnswerAudioUrl: answer?.virtualAnswerAudioUrl || content?.virtualAnswer?.audioUrl || '',
|
|
47
|
-
isExtendedResponse: answer?.isExtendedResponse || content?.isExtendedResponse || false,
|
|
48
|
-
sampleAnswers: normalizeSampleAnswers(),
|
|
49
|
-
explanation,
|
|
50
|
-
points,
|
|
51
|
-
};
|
|
52
|
-
},
|
|
53
|
-
getExtraProps: (ctx) => ({
|
|
54
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
55
|
-
validationRef: ctx.validationRef,
|
|
56
|
-
}),
|
|
57
|
-
wrapOnSave: (data, ctx) => {
|
|
58
|
-
const expectedAnswers = (data.sampleAnswers || []).filter((s) => s && s.trim() !== '');
|
|
59
|
-
const isVirtual = data.targetCandidate === 'VIRTUAL'; // VIRTUAL = virtual by default
|
|
60
|
-
return {
|
|
61
|
-
type: ctx.questionType,
|
|
62
|
-
content: {
|
|
63
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null
|
|
64
|
-
? ctx.state.content
|
|
65
|
-
: {}),
|
|
66
|
-
inputType: data.inputType,
|
|
67
|
-
audioUrl: data.audioUrl || '',
|
|
68
|
-
imageUrl: data.imageUrl || '',
|
|
69
|
-
questionText: data.inputType === 'TEXT' ? data.questionText : undefined,
|
|
70
|
-
targetCandidate: data.targetCandidate || 'REAL',
|
|
71
|
-
virtualAnswer: isVirtual
|
|
72
|
-
? {
|
|
73
|
-
answerText: data.virtualAnswerText || '',
|
|
74
|
-
audioUrl: data.virtualAnswerAudioUrl || '',
|
|
75
|
-
}
|
|
76
|
-
: undefined,
|
|
77
|
-
isExtendedResponse: data.isExtendedResponse || false,
|
|
78
|
-
},
|
|
79
|
-
points: isVirtual ? 0 : (data?.points ?? ctx.state.points ?? 1),
|
|
80
|
-
level: ctx.state.level,
|
|
81
|
-
difficulty: ctx.state.difficulty,
|
|
82
|
-
skill: ctx.state.skill,
|
|
83
|
-
answer: {
|
|
84
|
-
expectedAnswers: isVirtual ? [] : expectedAnswers,
|
|
85
|
-
},
|
|
86
|
-
explanation: data?.explanation || '',
|
|
87
|
-
};
|
|
88
|
-
},
|
|
89
|
-
wrapOnChange: (data, ctx) => {
|
|
90
|
-
const expectedAnswers = data.sampleAnswers || [''];
|
|
91
|
-
const isVirtual = data.targetCandidate === 'VIRTUAL';
|
|
92
|
-
return {
|
|
93
|
-
type: ctx.questionType,
|
|
94
|
-
content: {
|
|
95
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null
|
|
96
|
-
? ctx.state.content
|
|
97
|
-
: {}),
|
|
98
|
-
inputType: data.inputType,
|
|
99
|
-
audioUrl: data.audioUrl || '',
|
|
100
|
-
imageUrl: data.imageUrl || '',
|
|
101
|
-
questionText: data.inputType === 'TEXT' ? data.questionText : undefined,
|
|
102
|
-
targetCandidate: data.targetCandidate || 'REAL',
|
|
103
|
-
virtualAnswer: isVirtual
|
|
104
|
-
? {
|
|
105
|
-
answerText: data.virtualAnswerText || '',
|
|
106
|
-
audioUrl: data.virtualAnswerAudioUrl || '',
|
|
107
|
-
}
|
|
108
|
-
: undefined,
|
|
109
|
-
isExtendedResponse: data.isExtendedResponse || false,
|
|
110
|
-
},
|
|
111
|
-
points: isVirtual ? 0 : (data?.points ?? ctx.state.points ?? 1),
|
|
112
|
-
level: ctx.state.level,
|
|
113
|
-
difficulty: ctx.state.difficulty,
|
|
114
|
-
skill: ctx.state.skill,
|
|
115
|
-
answer: {
|
|
116
|
-
expectedAnswers: isVirtual ? [] : expectedAnswers,
|
|
117
|
-
},
|
|
118
|
-
explanation: data?.explanation || '',
|
|
119
|
-
};
|
|
120
|
-
},
|
|
121
|
-
};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
export const transformGNSpeakingInterview = (question) => {
|
|
2
|
-
const contentData = question.content;
|
|
3
|
-
const answerData = question.answer;
|
|
4
|
-
const inputType = contentData?.inputType || 'TEXT';
|
|
5
|
-
const targetCandidate = contentData?.targetCandidate || 'REAL'; // VIRTUAL=Virtual, REAL=Real
|
|
6
|
-
const apiContent = {
|
|
7
|
-
inputType,
|
|
8
|
-
// Always include audioUrl — populated when AUDIO, empty string when TEXT
|
|
9
|
-
audioUrl: contentData?.audioUrl || '',
|
|
10
|
-
imageUrl: contentData?.imageUrl || '',
|
|
11
|
-
targetCandidate,
|
|
12
|
-
isExtendedResponse: contentData?.isExtendedResponse || false,
|
|
13
|
-
};
|
|
14
|
-
// Include questionText when inputType is TEXT
|
|
15
|
-
if (inputType === 'TEXT' && contentData?.questionText) {
|
|
16
|
-
apiContent.questionText = contentData.questionText;
|
|
17
|
-
}
|
|
18
|
-
// Include virtualAnswer when targetCandidate is 'VIRTUAL'
|
|
19
|
-
if (targetCandidate === 'VIRTUAL' && contentData?.virtualAnswer) {
|
|
20
|
-
apiContent.virtualAnswer = {
|
|
21
|
-
answerText: contentData.virtualAnswer.answerText || '',
|
|
22
|
-
audioUrl: contentData.virtualAnswer.audioUrl || '',
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
// Normalize: support expectedAnswers, sampleAnswers (array) and legacy sampleAnswer
|
|
26
|
-
const expectedAnswers = (() => {
|
|
27
|
-
// Virtual candidates do not have expected answers
|
|
28
|
-
if (targetCandidate === 'VIRTUAL')
|
|
29
|
-
return [];
|
|
30
|
-
const newArr = answerData?.expectedAnswers;
|
|
31
|
-
if (Array.isArray(newArr))
|
|
32
|
-
return newArr.filter((s) => s && s.trim() !== '');
|
|
33
|
-
const arr = answerData?.sampleAnswers;
|
|
34
|
-
if (Array.isArray(arr))
|
|
35
|
-
return arr.filter((s) => s && s.trim() !== '');
|
|
36
|
-
const single = answerData?.sampleAnswer;
|
|
37
|
-
if (single && single.trim() !== '')
|
|
38
|
-
return [single];
|
|
39
|
-
return [];
|
|
40
|
-
})();
|
|
41
|
-
const apiCorrectAnswer = {
|
|
42
|
-
expectedAnswers,
|
|
43
|
-
};
|
|
44
|
-
return {
|
|
45
|
-
apiContent,
|
|
46
|
-
apiCorrectAnswer,
|
|
47
|
-
};
|
|
48
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
interface LookPictureChooseCorrectAnswerClientProps {
|
|
2
|
-
questionData: {
|
|
3
|
-
subQuestions: Array<{
|
|
4
|
-
id: string;
|
|
5
|
-
imageUrl: string;
|
|
6
|
-
question?: string;
|
|
7
|
-
options: Array<{
|
|
8
|
-
id: string;
|
|
9
|
-
text: string;
|
|
10
|
-
isCorrect: boolean;
|
|
11
|
-
}>;
|
|
12
|
-
}>;
|
|
13
|
-
};
|
|
14
|
-
onSubmit?: (answers: Record<string, string>) => void;
|
|
15
|
-
isReviewMode?: boolean;
|
|
16
|
-
userAnswer?: Record<string, string>;
|
|
17
|
-
explanation?: string;
|
|
18
|
-
}
|
|
19
|
-
export declare function LookPictureChooseCorrectAnswerClient({ questionData, onSubmit, isReviewMode, userAnswer, explanation, }: LookPictureChooseCorrectAnswerClientProps): import("react").JSX.Element;
|
|
20
|
-
export {};
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useEffect } from 'react';
|
|
4
|
-
import { Check, X, Lightbulb } from 'lucide-react';
|
|
5
|
-
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
6
|
-
function SubQuestionClient({ subQuestion, index, userAnswerValue, isReviewMode, onAnswerChange, }) {
|
|
7
|
-
// Use hook at component level
|
|
8
|
-
const { previewUrl: imagePreviewUrl, isLoading } = usePresignedFileUrl(subQuestion.imageUrl || '');
|
|
9
|
-
// Find correct answer
|
|
10
|
-
const correctOption = subQuestion.options.find((opt) => opt.isCorrect);
|
|
11
|
-
const correctOptionId = correctOption?.id || '';
|
|
12
|
-
// Check if answer is correct
|
|
13
|
-
const isAnswerCorrect = userAnswerValue === correctOptionId;
|
|
14
|
-
const hasAnswered = userAnswerValue !== '';
|
|
15
|
-
return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-sm font-bold text-white shadow-sm", children: index + 1 }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nh\u00ECn tranh, ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: `flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold ${isAnswerCorrect
|
|
16
|
-
? 'bg-green-100 text-green-700'
|
|
17
|
-
: 'bg-red-100 text-red-700'}`, children: [isAnswerCorrect ? _jsx(Check, { className: "h-3.5 w-3.5" }) : _jsx(X, { className: "h-3.5 w-3.5" }), isAnswerCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "p-5 space-y-4", children: [(imagePreviewUrl || subQuestion.imageUrl) && (_jsxs("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gradient-to-br from-slate-50 to-gray-100", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-1.5", children: _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-2 w-2 rounded-full bg-red-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-green-400" }), _jsx("span", { className: "ml-2 text-xs font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh" })] }) }), _jsx("div", { className: "flex items-center justify-center p-4", children: isLoading ? (_jsx("div", { className: "flex h-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" }) })) : (
|
|
18
|
-
/* eslint-disable-next-line @next/next/no-img-element */
|
|
19
|
-
_jsx("img", { src: imagePreviewUrl || subQuestion.imageUrl, alt: "Question illustration", className: "max-h-56 w-auto object-contain", onError: (e) => {
|
|
20
|
-
const target = e.target;
|
|
21
|
-
target.onerror = null;
|
|
22
|
-
target.src = '/images/placeholder-image.svg';
|
|
23
|
-
} })) })] })), _jsx("div", { className: "space-y-2", children: subQuestion.options.map((option, optIndex) => {
|
|
24
|
-
const isSelected = userAnswerValue === option.id;
|
|
25
|
-
const isCorrectAnswer = option.isCorrect;
|
|
26
|
-
const letter = String.fromCharCode(65 + optIndex);
|
|
27
|
-
return _jsxs("button", { type: "button", onClick: () => !isReviewMode && onAnswerChange(option.id), disabled: isReviewMode, className: `group flex w-full items-center gap-3 rounded-lg border-2 p-3 text-left transition-all ${isReviewMode
|
|
28
|
-
? isCorrectAnswer
|
|
29
|
-
? 'border-green-400 bg-green-50'
|
|
30
|
-
: isSelected
|
|
31
|
-
? 'border-red-400 bg-red-50'
|
|
32
|
-
: 'border-gray-200 bg-gray-50'
|
|
33
|
-
: isSelected
|
|
34
|
-
? 'border-blue-500 bg-blue-50 shadow-sm'
|
|
35
|
-
: 'border-gray-200 bg-white hover:border-gray-300 hover:bg-gray-50 hover:shadow-sm'}`, children: [_jsx("div", { className: `flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg text-sm font-bold transition-all ${isReviewMode
|
|
36
|
-
? isCorrectAnswer
|
|
37
|
-
? 'bg-green-500 text-white'
|
|
38
|
-
: isSelected
|
|
39
|
-
? 'bg-red-500 text-white'
|
|
40
|
-
: 'bg-gray-200 text-gray-600'
|
|
41
|
-
: isSelected
|
|
42
|
-
? 'bg-blue-500 text-white'
|
|
43
|
-
: 'bg-gray-100 text-gray-600 group-hover:bg-gray-200'}`, children: letter }), _jsx("span", { className: `flex-1 text-sm font-medium ${isReviewMode && isCorrectAnswer
|
|
44
|
-
? 'text-green-800'
|
|
45
|
-
: isReviewMode && isSelected && !isCorrectAnswer
|
|
46
|
-
? 'text-red-800'
|
|
47
|
-
: 'text-gray-700'}`, children: option.text }), isReviewMode && (isCorrectAnswer || isSelected) && (_jsx("div", { className: `flex h-6 w-6 items-center justify-center rounded-full ${isCorrectAnswer ? 'bg-green-500' : 'bg-red-500'}`, children: isCorrectAnswer
|
|
48
|
-
? _jsx(Check, { className: "h-4 w-4 text-white" })
|
|
49
|
-
: _jsx(X, { className: "h-4 w-4 text-white" }) }))] }, option.id);
|
|
50
|
-
}) }), isReviewMode && hasAnswered && !isAnswerCorrect && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-green-50 p-3 border border-green-200", children: [_jsx(Check, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: correctOption?.text })] })] }))] })] }));
|
|
51
|
-
}
|
|
52
|
-
export function LookPictureChooseCorrectAnswerClient({ questionData, onSubmit, isReviewMode = false, userAnswer = {}, explanation, }) {
|
|
53
|
-
// State for answers: { subQuestionId: selectedOptionId }
|
|
54
|
-
const [answers, setAnswers] = useState(userAnswer);
|
|
55
|
-
// Sync userAnswer when it changes
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
setAnswers(userAnswer);
|
|
58
|
-
}, [userAnswer]);
|
|
59
|
-
// Handle answer change for a sub-question
|
|
60
|
-
const handleAnswerChange = (subQuestionId, optionId) => {
|
|
61
|
-
if (isReviewMode)
|
|
62
|
-
return;
|
|
63
|
-
const newAnswers = {
|
|
64
|
-
...answers,
|
|
65
|
-
[subQuestionId]: optionId,
|
|
66
|
-
};
|
|
67
|
-
setAnswers(newAnswers);
|
|
68
|
-
// Auto submit if callback provided
|
|
69
|
-
onSubmit?.(newAnswers);
|
|
70
|
-
};
|
|
71
|
-
return (_jsxs("div", { className: "space-y-6", children: [questionData.subQuestions.map((subQuestion, index) => (_jsx(SubQuestionClient, { subQuestion: subQuestion, index: index, userAnswerValue: answers[subQuestion.id] || '', isReviewMode: isReviewMode, onAnswerChange: (value) => handleAnswerChange(subQuestion.id, value) }, subQuestion.id))), isReviewMode && explanation && (_jsx("div", { className: "rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-semibold text-blue-700 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-gray-700 leading-relaxed", children: explanation })] })] }) }))] }));
|
|
72
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
interface LookPictureChooseCorrectAnswerCreatorProps {
|
|
2
|
-
initialData?: any;
|
|
3
|
-
onSave?: (data: any) => void;
|
|
4
|
-
onCancel?: () => void;
|
|
5
|
-
onChange?: (data: any) => void;
|
|
6
|
-
onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void;
|
|
7
|
-
optionCount?: number;
|
|
8
|
-
validationRef?: React.MutableRefObject<{
|
|
9
|
-
triggerValidation: () => Promise<boolean>;
|
|
10
|
-
getFormData?: () => any;
|
|
11
|
-
} | null>;
|
|
12
|
-
}
|
|
13
|
-
export declare function LookPictureChooseCorrectAnswerCreator(props: LookPictureChooseCorrectAnswerCreatorProps): import("react").JSX.Element;
|
|
14
|
-
export declare const LookPictureChooseCorrectAnswer: typeof LookPictureChooseCorrectAnswerCreator;
|
|
15
|
-
export {};
|