@tinyweb_dev/oe-exam-sdk 1.0.5 → 1.0.7
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/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.js +2 -2
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +1 -1
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +44 -28
- 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.js +1 -1
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +13 -11
- 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/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 +0 -19
- 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/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
|
@@ -4,12 +4,10 @@ import { useState, useRef, useEffect, useCallback } 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
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
8
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
9
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
10
7
|
import { RadioGroup, RadioGroupItem } from '../../../../components/ui/radio-group';
|
|
11
|
-
import {
|
|
8
|
+
import { Plus, Trash2, Image as ImageIcon, Volume2, Lightbulb, MousePointerClick, GripVertical, ListChecks, } from 'lucide-react';
|
|
12
9
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
10
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
13
11
|
import { usePresignedFileUrl, useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
14
12
|
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
15
13
|
import { listenAndChooseObjectsInSceneFormSchema, } from '../../../../shared/lib/validation/listen-and-choose-objects-in-scene.validation';
|
|
@@ -17,7 +15,6 @@ import { ListenAndChooseObjectsInSceneClient } from './ListenAndChooseObjectsInS
|
|
|
17
15
|
import { useSceneQuestionGroup } from '../../_shared/hooks/useSceneQuestionGroup';
|
|
18
16
|
import { generateObjectId } from '../../_shared/utils/id-generator';
|
|
19
17
|
import { useExamQuestionStore } from '../../../../shared/lib/stores/examQuestionStore';
|
|
20
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
21
18
|
/**
|
|
22
19
|
* Scene Object Editor - An individual object node editor row
|
|
23
20
|
*/
|
|
@@ -347,7 +344,7 @@ function ListenAndChooseObjectsInSceneCreatorContent({ initialData, onSave, onSa
|
|
|
347
344
|
// ============ RENDER ============
|
|
348
345
|
// Preview mode
|
|
349
346
|
if (isClientMode) {
|
|
350
|
-
return (_jsxs("div", { className: "space-y-
|
|
347
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(ListenAndChooseObjectsInSceneClient, { questionData: {
|
|
351
348
|
audioUrl: audioPreviewUrl || audioUrl,
|
|
352
349
|
backgroundImageUrl: backgroundPreviewUrl || backgroundImageUrl,
|
|
353
350
|
objectNodes,
|
|
@@ -355,31 +352,27 @@ function ListenAndChooseObjectsInSceneCreatorContent({ initialData, onSave, onSa
|
|
|
355
352
|
}
|
|
356
353
|
const formErrors = form.formState.errors;
|
|
357
354
|
// Creator mode
|
|
358
|
-
return (_jsxs("div", { className: "space-y-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
{ text:
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
form.clearErrors(); // Xóa lỗi validation khi người dùng đã chọn đáp án
|
|
380
|
-
}, className: "grid gap-3 sm:grid-cols-2 md:grid-cols-3", children: objectNodes.map((node, index) => (_jsxs("label", { className: `flex cursor-pointer items-start gap-3 rounded-lg border-2 p-3 transition-all ${correctObjectId === node.id
|
|
381
|
-
? 'border-green-500 bg-green-50 ring-1 ring-green-500'
|
|
382
|
-
: 'border-gray-200 hover:border-green-300 hover:bg-green-50/50'}`, children: [_jsx(RadioGroupItem, { value: node.id, id: `correct-${node.id}`, className: "mt-1 flex-shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("p", { className: "font-medium text-gray-900 line-clamp-1 break-all", children: [index + 1, ". ", node.label || '(Chưa đặt tên)'] }), node.imageUrl ? (_jsx("div", { className: "mt-2 h-12 w-full max-w-16 rounded overflow-hidden bg-white border border-gray-200", children: _jsx(PresignedImage, { url: node.imageUrl, alt: node.label }) })) : (_jsx("div", { className: "mt-2 text-xs text-gray-400 italic", children: "Ch\u01B0a c\u00F3 h\u00ECnh" }))] })] }, node.id))) }))] }), formErrors.correctObjectId && (_jsx("div", { className: "rounded-lg border border-red-200 bg-red-50 p-3 mt-3", children: _jsxs("p", { className: "text-sm text-red-700", children: ["\u26A0\uFE0F ", formErrors.correctObjectId.message] }) }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-amber-400 to-orange-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-sm text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch cho \u0111\u00E1p \u00E1n \u0111\u00FAng \u2014 hi\u1EC3n th\u1ECB sau khi h\u1ECDc sinh ho\u00E0n th\u00E0nh c\u00E2u h\u1ECFi" })] })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { value: explanation, onChange: (e) => setExplanation(e.target.value), placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0... v\u00EC...", rows: 3, className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none" }) })] })] }));
|
|
355
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(ListChecks, { className: "h-3.5 w-3.5" }), title: "Nh\u00F3m \u0111\u00E1p \u00E1n", extraActions: _jsxs("span", { className: "inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600", children: ["Nh\u00F3m ", currentGroupNumber] }), points: {
|
|
356
|
+
value: points,
|
|
357
|
+
onValueChange: setPoints,
|
|
358
|
+
}, preview: {
|
|
359
|
+
onClick: () => setIsClientMode(true),
|
|
360
|
+
disabled: !backgroundImageUrl || objectNodes.length < 2,
|
|
361
|
+
} }), contentClassName: "hidden", children: null }), _jsxs(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: "\u1EA2nh n\u1EC1n & \u0110\u1ED1i t\u01B0\u1EE3ng trong Scene" })] }), children: [_jsx("div", { className: `rounded-xl border-2 p-5 bg-gradient-to-r from-blue-50 to-indigo-50 border-blue-200`, children: _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-blue-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-blue-600" }), "\u1EA2nh n\u1EC1n (Scene) ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: "scene-background", label: "", accept: "image/*", value: backgroundImageUrl, onChange: (url) => {
|
|
362
|
+
setBackgroundImageUrl(url);
|
|
363
|
+
form.clearErrors();
|
|
364
|
+
}, onPresignedUrlChange: setBackgroundPreviewUrl, maxSize: 10, placeholder: "Upload h\u00ECnh \u1EA3nh n\u1EC1n c\u1EA3nh", autoUpload: true, prefix: "questions/scenes" }), formErrors.backgroundImageUrl && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: formErrors.backgroundImageUrl.message })), backgroundPreviewUrl && (_jsxs("div", { className: "mt-2 overflow-hidden rounded-lg border border-gray-200 bg-gray-50 isolate", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-1.5", children: _jsx("span", { className: "text-xs font-medium text-gray-500", children: "\u1EA2nh n\u1EC1n - K\u00E9o th\u1EA3 \u0111\u1ED1i t\u01B0\u1EE3ng \u0111\u1EC3 \u0111\u1ECBnh v\u1ECB" }) }), _jsx("div", { className: "p-2", children: _jsx(ScenePreviewWithDrag, { backgroundUrl: backgroundPreviewUrl, objectNodes: objectNodes, correctObjectId: correctObjectId, onNodeDrag: handleNodeDrag, onNodeResize: handleNodeResize }) })] }))] }) }), _jsxs("div", { className: "pt-4 border-t border-gray-100", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", 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-indigo-100", children: _jsx(MousePointerClick, { className: "h-3.5 w-3.5 text-indigo-600" }) }), _jsxs("h3", { className: "text-lg font-semibold text-gray-800", children: ["\u0110\u1ED1i t\u01B0\u1EE3ng (", objectNodes.length, " items)"] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: addObjectNode, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u1ED1i t\u01B0\u1EE3ng"] })] }), _jsx("div", { className: "space-y-3", children: objectNodes.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center gap-3 rounded-xl border-2 border-dashed border-gray-200 bg-gray-50/50 py-10", children: [_jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-gray-100", children: _jsx(ImageIcon, { className: "h-6 w-6 text-gray-400" }) }), _jsx("p", { className: "text-sm text-gray-500", children: "Ch\u01B0a c\u00F3 \u0111\u1ED1i t\u01B0\u1EE3ng n\u00E0o. Nh\u1EA5n \"Th\u00EAm \u0111\u1ED1i t\u01B0\u1EE3ng\" \u0111\u1EC3 t\u1EA1o c\u00E1c \u0111\u1ED1i t\u01B0\u1EE3ng chung cho nh\u00F3m." })] })) : (objectNodes.map((node, index) => (_jsx(SceneObjectEditor, { node: node, index: index, isCorrect: false, onUpdate: updateObjectNode, onRemove: removeObjectNode, onSelectCorrect: () => { }, errors: {
|
|
365
|
+
label: formErrors.objectNodes?.[index]?.label?.message,
|
|
366
|
+
imageUrl: formErrors.objectNodes?.[index]?.imageUrl?.message,
|
|
367
|
+
} }, node.id)))) })] })] }), _jsxs(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100", children: _jsx(Volume2, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Th\u00F4ng tin c\u00E2u h\u1ECFi" })] }), children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(Volume2, { className: "h-4 w-4 text-indigo-500" }), "Audio c\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: "scene-audio", label: "", accept: "audio/*", value: audioUrl, onChange: (url) => {
|
|
368
|
+
setAudioUrl(url);
|
|
369
|
+
form.clearErrors();
|
|
370
|
+
}, onPresignedUrlChange: setAudioPreviewUrl, maxSize: 10, placeholder: "Upload file audio c\u00E2u h\u1ECFi", autoUpload: true, prefix: "questions/audio", examId: examId || undefined, partId: partId || undefined }), formErrors.audioUrl && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: formErrors.audioUrl.message })), audioPreviewUrl && (_jsx("audio", { controls: true, className: "mt-2 w-full", src: audioPreviewUrl, children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." }))] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-green-100", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5 text-green-600" }) }), _jsxs(Label, { className: "text-base font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng c\u1EE7a c\u00E2u h\u1ECFi n\u00E0y ", _jsx("span", { className: "text-red-500", children: "*" })] })] }), objectNodes.length === 0 ? (_jsx("p", { className: "text-sm text-amber-600 italic", children: "Vui l\u00F2ng th\u00EAm \u0111\u1ED1i t\u01B0\u1EE3ng \u1EDF b\u00EAn tr\u00EAn tr\u01B0\u1EDBc khi ch\u1ECDn \u0111\u00E1p \u00E1n." })) : (_jsx(RadioGroup, { value: correctObjectId, onValueChange: (val) => {
|
|
371
|
+
setCorrectObjectId(val);
|
|
372
|
+
form.clearErrors(); // Xóa lỗi validation khi người dùng đã chọn đáp án
|
|
373
|
+
}, className: "grid gap-3 sm:grid-cols-2 md:grid-cols-3", children: objectNodes.map((node, index) => (_jsxs("label", { className: `flex cursor-pointer items-start gap-3 rounded-lg border-2 p-3 transition-all ${correctObjectId === node.id
|
|
374
|
+
? 'border-green-500 bg-green-50 ring-1 ring-green-500'
|
|
375
|
+
: 'border-gray-200 hover:border-green-300 hover:bg-green-50/50'}`, children: [_jsx(RadioGroupItem, { value: node.id, id: `correct-${node.id}`, className: "mt-1 flex-shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("p", { className: "font-medium text-gray-900 line-clamp-1 break-all", children: [index + 1, ". ", node.label || '(Chưa đặt tên)'] }), node.imageUrl ? (_jsx("div", { className: "mt-2 h-12 w-full max-w-16 rounded overflow-hidden bg-white border border-gray-200", children: _jsx(PresignedImage, { url: node.imageUrl, alt: node.label }) })) : (_jsx("div", { className: "mt-2 text-xs text-gray-400 italic", children: "Ch\u01B0a c\u00F3 h\u00ECnh" }))] })] }, node.id))) }))] }), formErrors.correctObjectId && (_jsx("div", { className: "rounded-lg border border-red-200 bg-red-50 p-3 mt-3", children: _jsxs("p", { className: "text-sm text-red-700", children: ["\u26A0\uFE0F ", formErrors.correctObjectId.message] }) }))] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(explanation) })] }));
|
|
383
376
|
}
|
|
384
377
|
export function ListenAndChooseObjectsInSceneCreator(props) {
|
|
385
378
|
return _jsx(ListenAndChooseObjectsInSceneCreatorContent, { ...props });
|
|
@@ -76,9 +76,9 @@ export function ListenAndDragObjectsIntoSceneClient({ questionData, onSubmit, is
|
|
|
76
76
|
selectedDropZoneId === correctAnswer.dropZoneId;
|
|
77
77
|
const selectedObject = questionData.objectNodes.find((n) => n.id === selectedObjectId);
|
|
78
78
|
const selectedDropZone = questionData.dropZones.find((z) => z.id === selectedDropZoneId);
|
|
79
|
-
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-
|
|
79
|
+
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gray-50 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100 text-gray-600", children: _jsx(CircleHelp, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 k\u00E9o th\u1EA3 \u0111\u1ED1i t\u01B0\u1EE3ng v\u00E0o c\u1EA3nh" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAnswerCorrect
|
|
80
80
|
? 'bg-green-100 text-green-700'
|
|
81
|
-
: '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: "space-y-
|
|
81
|
+
: '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: "space-y-3 p-3", children: [audioUrl && (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-orange-100 bg-gradient-to-r from-orange-50 to-amber-50 p-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-orange-500 text-white", children: _jsx(Volume2, { className: "h-4 w-4" }) }), _jsx("audio", { controls: true, className: "h-8 w-full", src: audioUrl, children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." })] })), _jsxs("div", { className: "flex gap-4", children: [_jsx("div", { className: "flex w-24 flex-shrink-0 flex-col gap-2", children: questionData.objectNodes.slice(0, Math.ceil(questionData.objectNodes.length / 2)).map((node) => {
|
|
82
82
|
const isSelected = selectedObjectId === node.id;
|
|
83
83
|
const isDragging = draggedObjectId === node.id;
|
|
84
84
|
const isCorrectObj = isReviewMode && correctAnswer?.objectId === node.id;
|
|
@@ -4,11 +4,9 @@ import { useState, useRef, useEffect, useCallback } 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
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
8
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
9
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
10
7
|
import { RadioGroup, RadioGroupItem } from '../../../../components/ui/radio-group';
|
|
11
|
-
import {
|
|
8
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
9
|
+
import { Plus, Trash2, Image as ImageIcon, Volume2, Lightbulb, GripVertical, Target, Move, ChevronDown, ChevronUp, MapPin, ArrowRight, AlertTriangle, ListChecks, } from 'lucide-react';
|
|
12
10
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
13
11
|
import { usePresignedFileUrl, useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
14
12
|
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
@@ -16,7 +14,6 @@ import { listenAndDragObjectsIntoSceneFormSchema, } from '../../../../shared/lib
|
|
|
16
14
|
import { ListenAndDragObjectsIntoSceneClient } from './ListenAndDragObjectsIntoSceneClient';
|
|
17
15
|
import { useSceneQuestionGroup } from '../../_shared/hooks/useSceneQuestionGroup';
|
|
18
16
|
import { generateObjectId } from '../../_shared/utils/id-generator';
|
|
19
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
20
17
|
import { useExamQuestionStore } from '../../../../shared/lib/stores/examQuestionStore';
|
|
21
18
|
// Drop zone color palette for visual correlation between editor and preview
|
|
22
19
|
const DROP_ZONE_COLORS = [
|
|
@@ -417,7 +414,7 @@ function ListenAndDragObjectsIntoSceneCreatorContent({ initialData, onSave, onSa
|
|
|
417
414
|
// ============ RENDER ============
|
|
418
415
|
// Preview mode
|
|
419
416
|
if (isClientMode) {
|
|
420
|
-
return (_jsxs("div", { className: "space-y-
|
|
417
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(ListenAndDragObjectsIntoSceneClient, { questionData: {
|
|
421
418
|
audioUrl: audioPreviewUrl || audioUrl,
|
|
422
419
|
backgroundImageUrl: backgroundPreviewUrl || backgroundImageUrl,
|
|
423
420
|
objectNodes,
|
|
@@ -425,32 +422,32 @@ function ListenAndDragObjectsIntoSceneCreatorContent({ initialData, onSave, onSa
|
|
|
425
422
|
}, isReviewMode: true, userAnswer: correctObjectId && correctDropZoneId ? { objectId: correctObjectId, dropZoneId: correctDropZoneId } : undefined, correctAnswer: correctObjectId && correctDropZoneId ? { objectId: correctObjectId, dropZoneId: correctDropZoneId } : undefined, explanation: explanation })] }));
|
|
426
423
|
}
|
|
427
424
|
// Creator mode
|
|
428
|
-
return (_jsxs("div", { className: "space-y-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
425
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(ListChecks, { className: "h-3.5 w-3.5" }), title: "Nh\u00F3m \u0111\u00E1p \u00E1n", extraActions: _jsxs("span", { className: "inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600", children: ["Nh\u00F3m ", currentGroupNumber] }), points: {
|
|
426
|
+
value: points ?? 1,
|
|
427
|
+
onValueChange: setPoints,
|
|
428
|
+
}, preview: {
|
|
429
|
+
onClick: () => setIsClientMode(true),
|
|
430
|
+
disabled: !backgroundImageUrl || objectNodes.length < 2 || dropZones.length < 1,
|
|
431
|
+
} }), contentClassName: "hidden", children: null }), _jsxs(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: "\u1EA2nh n\u1EC1n & \u0110\u1ED1i t\u01B0\u1EE3ng k\u00E9o th\u1EA3" })] }), _jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setIsGroupDataCollapsed(!isGroupDataCollapsed), className: "gap-1 text-gray-500 hover:text-gray-700", children: [isGroupDataCollapsed ? _jsx(ChevronDown, { className: "h-4 w-4" }) : _jsx(ChevronUp, { className: "h-4 w-4" }), isGroupDataCollapsed ? 'Mở rộng' : 'Thu gọn'] })] }), contentClassName: isGroupDataCollapsed ? 'hidden' : undefined, children: [_jsx("div", { className: "rounded-xl border-2 p-5 bg-gradient-to-r from-orange-50 to-amber-50 border-orange-200", children: _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-orange-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-orange-600" }), "\u1EA2nh n\u1EC1n (Scene) ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: "drag-scene-background", label: "", accept: "image/*", value: backgroundImageUrl, onChange: (url) => {
|
|
432
|
+
setBackgroundImageUrl(url);
|
|
433
|
+
form.clearErrors();
|
|
434
|
+
}, onPresignedUrlChange: setBackgroundPreviewUrl, maxSize: 10, placeholder: "Upload h\u00ECnh \u1EA3nh n\u1EC1n h\u00ECnh \u1EA3nh n\u1EC1n", autoUpload: true, prefix: "questions/scenes" }), form.formState.errors.backgroundImageUrl && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.backgroundImageUrl.message }))] }) }), backgroundPreviewUrl && (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-gray-50 overflow-hidden isolate", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 px-4 py-2.5 bg-white", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-blue-600" }), _jsx("span", { className: "text-sm font-semibold text-gray-700", children: "H\u00ECnh \u1EA3nh n\u1EC1n" }), _jsx(SharedGroupBadge, { groupNumber: currentGroupNumber })] }), _jsx("div", { className: "text-xs text-gray-500 font-medium", children: "K\u00E9o th\u1EA3 v\u1ECB tr\u00ED \u0111\u1EC3 \u0111\u1ECBnh v\u1ECB tr\u00EAn \u1EA3nh n\u1EC1n" })] }), _jsx("div", { className: "p-3 w-full", children: _jsx(DropZonePreviewWithDrag, { backgroundUrl: backgroundPreviewUrl, dropZones: dropZones, onZoneDrag: handleZoneDrag, onZoneResize: handleZoneResize, activeZoneId: activeDropZoneId, onSelectZone: setActiveDropZoneId }) })] })), !backgroundPreviewUrl && (_jsx("div", { className: "rounded-xl border border-gray-200 bg-gray-50/50 p-6", children: _jsxs("div", { className: "flex flex-col items-center gap-2 text-center", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-gray-100", children: _jsx(Target, { className: "h-5 w-5 text-gray-400" }) }), _jsx("p", { className: "text-sm text-gray-500", children: "Upload \u1EA3nh n\u1EC1n tr\u01B0\u1EDBc \u0111\u1EC3 thi\u1EBFt l\u1EADp v\u1ECB tr\u00ED tr\u00EAn h\u00ECnh \u1EA3nh n\u1EC1n." })] }) })), backgroundPreviewUrl && (_jsxs("div", { className: "pt-4 border-t border-gray-100", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", 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-blue-100", children: _jsx(Target, { className: "h-3.5 w-3.5 text-blue-600" }) }), _jsxs("h3", { className: "text-base font-semibold text-gray-800", children: ["V\u1ECB tr\u00ED (", dropZones.length, " items)"] }), _jsx(SharedGroupBadge, { groupNumber: currentGroupNumber })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: addDropZone, className: "gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 hover:text-blue-700", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm v\u1ECB tr\u00ED"] })] }), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: dropZones.length === 0 ? (_jsxs("div", { className: "col-span-full flex flex-col items-center gap-3 rounded-xl border-2 border-dashed border-gray-200 bg-gray-50/50 py-10", children: [_jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-gray-100", children: _jsx(Target, { className: "h-6 w-6 text-gray-400" }) }), _jsx("p", { className: "text-sm text-gray-500", children: "Ch\u01B0a c\u00F3 v\u1ECB tr\u00ED n\u00E0o. Nh\u1EA5n \u201CTh\u00EAm v\u1ECB tr\u00ED\u201D \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u." })] })) : (dropZones.map((zone, index) => (_jsx(DropZoneEditor, { zone: zone, index: index, onUpdate: updateDropZone, onRemove: removeDropZone, isActive: activeDropZoneId === zone.id, onSelect: setActiveDropZoneId }, zone.id)))) }), form.formState.errors.dropZones && (_jsx("div", { className: "col-span-full mt-2 rounded-lg border border-red-200 bg-red-50 p-3", children: _jsx("p", { className: "text-sm text-red-500", children: form.formState.errors.dropZones.message || "Vui lòng nhập đầy đủ thông tin cho các vị trí." }) }))] })), _jsxs("div", { className: "pt-4 border-t border-gray-100", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", 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-amber-100", children: _jsx(Move, { className: "h-3.5 w-3.5 text-amber-600" }) }), _jsxs("h3", { className: "text-base font-semibold text-gray-800", children: ["\u0110\u1ED1i t\u01B0\u1EE3ng (", objectNodes.length, " items)"] }), _jsx(SharedGroupBadge, { groupNumber: currentGroupNumber })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: addObjectNode, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u1ED1i t\u01B0\u1EE3ng"] })] }), _jsxs("div", { className: "space-y-3", children: [objectNodes.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center gap-3 rounded-xl border-2 border-dashed border-gray-200 bg-gray-50/50 py-10", children: [_jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-gray-100", children: _jsx(ImageIcon, { className: "h-6 w-6 text-gray-400" }) }), _jsx("p", { className: "text-sm text-gray-500", children: "Ch\u01B0a c\u00F3 \u0111\u1ED1i t\u01B0\u1EE3ng n\u00E0o. Nh\u1EA5n \u201CTh\u00EAm \u0111\u1ED1i t\u01B0\u1EE3ng\u201D \u0111\u1EC3 t\u1EA1o c\u00E1c \u0111\u1ED1i t\u01B0\u1EE3ng \u1EDF sidebar." })] })) : (objectNodes.map((node, index) => (_jsx(DragObjectEditor, { node: node, index: index, onUpdate: updateObjectNode, onRemove: removeObjectNode }, node.id)))), form.formState.errors.objectNodes && (_jsx("p", { className: "mt-2 text-sm text-red-500", children: form.formState.errors.objectNodes.message || "Vui lòng nhập đầy đủ thông tin cho các đối tượng." }))] })] })] }), _jsxs(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100", children: _jsx(Volume2, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Th\u00F4ng tin c\u00E2u h\u1ECFi" })] }), children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(Volume2, { className: "h-4 w-4 text-indigo-500" }), "Audio c\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: "drag-scene-audio", label: "", accept: "audio/*", value: audioUrl, onChange: (url) => {
|
|
435
|
+
setAudioUrl(url);
|
|
436
|
+
form.clearErrors();
|
|
437
|
+
}, onPresignedUrlChange: setAudioPreviewUrl, maxSize: 10, placeholder: "Upload file audio c\u00E2u h\u1ECFi", autoUpload: true, prefix: "questions/audio", examId: examId || undefined, partId: partId || undefined }), form.formState.errors.audioUrl && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.audioUrl.message })), audioPreviewUrl && (_jsx("audio", { controls: true, className: "mt-2 w-full", src: audioPreviewUrl, children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." }))] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-green-100", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5 text-green-600" }) }), _jsxs(Label, { className: "text-base font-semibold text-gray-800", children: ["\u0110\u1ED1i t\u01B0\u1EE3ng \u0111\u00FAng ", _jsx("span", { className: "text-red-500", children: "*" })] })] }), objectNodes.length === 0 ? (_jsx("p", { className: "text-sm text-amber-600 italic", children: "Vui l\u00F2ng th\u00EAm \u0111\u1ED1i t\u01B0\u1EE3ng \u1EDF ph\u1EA7n d\u1EEF li\u1EC7u nh\u00F3m tr\u01B0\u1EDBc khi ch\u1ECDn \u0111\u00E1p \u00E1n." })) : (_jsx(RadioGroup, { value: correctObjectId, onValueChange: (val) => {
|
|
438
|
+
setCorrectObjectId(val);
|
|
439
|
+
form.clearErrors();
|
|
440
|
+
}, className: "grid gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4", children: objectNodes.map((node, index) => (_jsxs("label", { className: `flex cursor-pointer items-center gap-3 rounded-lg border-2 p-3 transition-all ${correctObjectId === node.id
|
|
441
|
+
? 'border-green-500 bg-green-50 ring-1 ring-green-500 shadow-sm'
|
|
442
|
+
: 'border-gray-200 hover:border-green-300 hover:bg-green-50/50'}`, children: [_jsx(RadioGroupItem, { value: node.id, id: `correct-obj-${node.id}`, className: "flex-shrink-0" }), _jsx("div", { className: "h-12 w-12 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 bg-white", children: node.imageUrl ? (_jsx(PresignedImage, { url: node.imageUrl, alt: node.label })) : (_jsx("div", { className: "flex h-full w-full items-center justify-center", children: _jsx(ImageIcon, { className: "h-4 w-4 text-gray-300" }) })) }), _jsx("div", { className: "min-w-0 flex-1", children: _jsxs("p", { className: "font-medium text-gray-900 text-sm line-clamp-2 break-all", children: [index + 1, ". ", node.label || '(Chưa đặt tên)'] }) })] }, node.id))) })), form.formState.errors.correctObjectId && (_jsx("div", { className: "rounded-lg border border-red-200 bg-red-50 p-3 mt-2", children: _jsxs("p", { className: "text-sm text-red-700", children: ["\u26A0\uFE0F ", form.formState.errors.correctObjectId.message] }) }))] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-blue-100", children: _jsx(Target, { className: "h-3.5 w-3.5 text-blue-600" }) }), _jsxs(Label, { className: "text-base font-semibold text-gray-800", children: ["V\u1ECB tr\u00ED \u0111\u00FAng ", _jsx("span", { className: "text-red-500", children: "*" })] })] }), dropZones.length === 0 ? (_jsx("p", { className: "text-sm text-amber-600 italic", children: "Vui l\u00F2ng th\u00EAm v\u1ECB tr\u00ED \u1EDF ph\u1EA7n d\u1EEF li\u1EC7u nh\u00F3m tr\u01B0\u1EDBc khi ch\u1ECDn \u0111\u00E1p \u00E1n." })) : (_jsx(RadioGroup, { value: correctDropZoneId, onValueChange: (val) => {
|
|
443
|
+
setCorrectDropZoneId(val);
|
|
444
|
+
form.clearErrors();
|
|
445
|
+
}, className: "grid gap-3 sm:grid-cols-2 md:grid-cols-3", children: dropZones.map((zone, index) => {
|
|
446
|
+
const color = getZoneColor(index);
|
|
447
|
+
return (_jsxs("label", { className: `flex cursor-pointer items-center gap-3 rounded-lg border-2 p-3 transition-all ${correctDropZoneId === zone.id
|
|
448
|
+
? 'border-blue-500 bg-blue-50 ring-1 ring-blue-500 shadow-sm'
|
|
449
|
+
: 'border-gray-200 hover:border-blue-300 hover:bg-blue-50/50'}`, children: [_jsx(RadioGroupItem, { value: zone.id, id: `correct-dz-${zone.id}`, className: "flex-shrink-0" }), _jsx("div", { className: `flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md ${color.dot} text-white shadow-sm`, children: _jsx(MapPin, { className: "h-4 w-4" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("p", { className: "font-medium text-gray-900 text-sm line-clamp-1 break-all", children: [index + 1, ". ", zone.label || '(Chưa đặt nhãn)'] }), _jsxs("p", { className: "text-[11px] text-gray-400 mt-0.5", children: ["V\u1ECB tr\u00ED: (", zone.x, "%, ", zone.y, "%) \u2014 ", zone.width, "\u00D7", zone.height, "%"] })] })] }, zone.id));
|
|
450
|
+
}) })), form.formState.errors.correctDropZoneId && (_jsx("div", { className: "rounded-lg border border-red-200 bg-red-50 p-3 mt-2", children: _jsxs("p", { className: "text-sm text-red-700", children: ["\u26A0\uFE0F ", form.formState.errors.correctDropZoneId.message] }) }))] }), selectedObject && selectedDropZone && (_jsxs("div", { className: "rounded-xl border-2 border-green-200 bg-gradient-to-r from-green-50 to-emerald-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-green-500 text-white", children: _jsx(Lightbulb, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-sm font-semibold text-green-800", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsxs("div", { className: "flex items-center gap-3 rounded-lg bg-white/80 p-3 border border-green-100", children: [_jsx("div", { className: "h-14 w-14 flex-shrink-0 overflow-hidden rounded-lg border-2 border-green-300 bg-white", children: selectedObject.imageUrl ? (_jsx(PresignedImage, { url: selectedObject.imageUrl, alt: selectedObject.label })) : (_jsx("div", { className: "flex h-full w-full items-center justify-center", children: _jsx(ImageIcon, { className: "h-5 w-5 text-gray-300" }) })) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "font-semibold text-gray-900 text-sm", children: selectedObject.label || '(Chưa đặt tên)' }), _jsx("p", { className: "text-xs text-gray-500", children: "\u0110\u1ED1i t\u01B0\u1EE3ng" })] }), _jsx(ArrowRight, { className: "h-5 w-5 text-green-500 flex-shrink-0 mx-1" }), _jsx("div", { className: `flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg ${selectedDropZoneIndex >= 0 ? getZoneColor(selectedDropZoneIndex).dot : 'bg-blue-500'} text-white shadow-sm`, children: _jsx(MapPin, { className: "h-5 w-5" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "font-semibold text-gray-900 text-sm", children: selectedDropZone.label || '(Chưa đặt nhãn)' }), _jsxs("p", { className: "text-xs text-gray-500", children: ["V\u1ECB tr\u00ED (", selectedDropZone.x, "%, ", selectedDropZone.y, "%)"] })] })] })] })), (!correctObjectId || !correctDropZoneId) && objectNodes.length > 0 && dropZones.length > 0 && (_jsxs("div", { className: "flex items-start gap-2.5 rounded-lg border border-amber-200 bg-amber-50 p-3", children: [_jsx(AlertTriangle, { className: "h-4 w-4 text-amber-500 flex-shrink-0 mt-0.5" }), _jsx("p", { className: "text-sm text-amber-700", children: "Vui l\u00F2ng ch\u1ECDn c\u1EA3 \u0111\u1ED1i t\u01B0\u1EE3ng \u0111\u00FAng v\u00E0 v\u1ECB tr\u00ED \u0111\u00FAng cho c\u00E2u h\u1ECFi n\u00E0y" })] }))] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(explanation) })] }));
|
|
454
451
|
}
|
|
455
452
|
export function ListenAndDragObjectsIntoSceneCreator(props) {
|
|
456
453
|
return _jsx(ListenAndDragObjectsIntoSceneCreatorContent, { ...props });
|
package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js
CHANGED
|
@@ -13,7 +13,7 @@ function QuestionAudioPlayer({ audioUrl }) {
|
|
|
13
13
|
// ─── Main Client Component ───────────────────────────────────────────────────
|
|
14
14
|
export function ListenAndSpeakAnswerClient({ questionData, isReviewMode = false, }) {
|
|
15
15
|
const { previewUrl: backgroundUrl } = usePresignedFileUrl(questionData.backgroundImageUrl || '');
|
|
16
|
-
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "flex items-center justify-between border-b border-gray-100 bg-
|
|
16
|
+
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gray-50 px-3 py-2", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100 text-gray-600", children: _jsx(Mic, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 tr\u1EA3 l\u1EDDi" })] }) }), _jsxs("div", { className: "p-3 space-y-3", children: [questionData.audioUrl ? (_jsx(QuestionAudioPlayer, { audioUrl: questionData.audioUrl })) : (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3", children: [_jsx(Volume2, { className: "h-4 w-4 text-gray-400" }), _jsx("span", { className: "text-xs text-gray-400", children: "Ch\u01B0a c\u00F3 audio c\u00E2u h\u1ECFi" })] })), backgroundUrl && questionData.arrowNode && (_jsxs("div", { className: "relative w-full max-w-3xl mx-auto rounded-xl overflow-hidden shadow-sm border border-gray-200 bg-gray-50", children: [_jsx("img", { src: backgroundUrl, alt: "Background", className: "w-full object-contain pointer-events-none" }), _jsx("div", { className: "absolute", style: {
|
|
17
17
|
left: `${questionData.arrowNode.x}%`,
|
|
18
18
|
top: `${questionData.arrowNode.y}%`,
|
|
19
19
|
width: `${questionData.arrowNode.width}%`,
|
package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js
CHANGED
|
@@ -4,18 +4,15 @@ import { useState, useRef, useEffect, useCallback } 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
|
-
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
7
|
import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
11
8
|
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
12
|
-
import {
|
|
9
|
+
import { Plus, Trash2, Mic, Volume2, ImageIcon, Locate, MessageSquareText, Info, ListChecks } from 'lucide-react';
|
|
13
10
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
14
11
|
import { useSceneQuestionGroup } from '../../_shared/hooks/useSceneQuestionGroup';
|
|
15
12
|
import { ListenAndSpeakAnswerClient } from './ListenAndSpeakAnswerClient';
|
|
16
13
|
import { ArrowIndicatorSVG } from './ArrowIndicator';
|
|
17
14
|
import { listenAndSpeakAnswerFormSchema, } from '../../../../shared/lib/validation/listen-and-speak-answer.validation';
|
|
18
|
-
import {
|
|
15
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
19
16
|
function ArrowPreviewWithDrag({ backgroundUrl, arrowNode, onNodeUpdate, }) {
|
|
20
17
|
const containerRef = useRef(null);
|
|
21
18
|
const [isDragging, setIsDragging] = useState(false);
|
|
@@ -262,30 +259,31 @@ export function ListenAndSpeakAnswerCreator({ initialData, onSave, onCancel, onC
|
|
|
262
259
|
backgroundImageUrl,
|
|
263
260
|
arrowNode: watchedArrowNode,
|
|
264
261
|
};
|
|
265
|
-
return (_jsxs("div", { className: "space-y-
|
|
262
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(ListenAndSpeakAnswerClient, { questionData: questionData, isReviewMode: true })] }));
|
|
266
263
|
}
|
|
267
|
-
return (_jsxs("div", { className: "space-y-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}, placeholder: `Ví dụ: I have a dog...`, className: "border-gray-200 focus:border-green-300 focus:ring-green-200" }), form.formState.errors?.expectedAnswers?.[index]?.message && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.expectedAnswers[index]?.message }))] }), watchedExpectedAnswers.length > 1 && (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => {
|
|
288
|
-
const newAnswers = watchedExpectedAnswers.filter((_, i) => i !== index);
|
|
264
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(ListChecks, { className: "h-3.5 w-3.5" }), title: "Nh\u00F3m \u0111\u00E1p \u00E1n", extraActions: _jsxs("span", { className: "inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600", children: ["Nh\u00F3m ", currentGroupNumber] }), points: {
|
|
265
|
+
value: watchedPoints,
|
|
266
|
+
onValueChange: (value) => form.setValue('points', value, { shouldValidate: true }),
|
|
267
|
+
error: form.hasError('points') ? form.getFieldError('points') : undefined,
|
|
268
|
+
}, preview: {
|
|
269
|
+
onClick: () => setIsClientMode(true),
|
|
270
|
+
disabled: !watchedAudioUrl || watchedExpectedAnswers.filter((a) => a.trim()).length === 0,
|
|
271
|
+
} }), contentClassName: "hidden", children: null }), _jsx(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: "H\u00ECnh \u1EA3nh n\u1EC1n & M\u0169i t\u00EAn ch\u1EC9 th\u1ECB" })] }), children: _jsxs("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-blue-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-blue-600" }), "H\u00ECnh \u1EA3nh n\u1EC1n"] }), _jsxs("div", { className: "mt-2", children: [_jsx(FileUpload, { id: "scene-background", label: "", accept: "image/*", value: backgroundImageUrl, onChange: (url) => {
|
|
272
|
+
setBackgroundImageUrl(url);
|
|
273
|
+
form.setValue('backgroundImageUrl', url, { shouldValidate: true });
|
|
274
|
+
}, maxSize: 10, placeholder: "Upload h\u00ECnh \u1EA3nh n\u1EC1n", autoUpload: true, prefix: "questions/scenes" }), form.hasError('backgroundImageUrl') && (_jsx("p", { className: "text-sm text-red-600 mt-1", children: form.getFieldError('backgroundImageUrl') }))] }), backgroundPreviewUrl && (_jsxs("div", { className: "mt-4 overflow-hidden rounded-lg border border-gray-200 bg-white", children: [_jsxs("div", { className: "border-b border-gray-100 px-3 py-2 bg-gray-50 flex items-center gap-2", children: [_jsx(Locate, { className: "h-4 w-4 text-blue-500" }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "K\u00E9o th\u1EA3 v\u00E0 \u0111i\u1EC1u ch\u1EC9nh k\u00EDch th\u01B0\u1EDBc m\u0169i t\u00EAn \u0111\u1EC3 ch\u1EC9 v\u00E0o \u0111\u1ED1i t\u01B0\u1EE3ng" })] }), _jsx("div", { className: "p-2", children: _jsx(ArrowPreviewWithDrag, { backgroundUrl: backgroundPreviewUrl, arrowNode: watchedArrowNode, onNodeUpdate: (updates) => {
|
|
275
|
+
form.setValue('arrowNode', { ...watchedArrowNode, ...updates }, { shouldValidate: true });
|
|
276
|
+
} }) })] }))] }) }), _jsxs(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100", children: _jsx(Mic, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Nghe v\u00E0 tr\u1EA3 l\u1EDDi" })] }), children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(Volume2, { className: "h-4 w-4 text-indigo-500" }), "Audio c\u00E2u h\u1ECFi"] }), _jsx(FileUpload, { id: "audioUrl", label: "", accept: "audio/*", value: watchedAudioUrl || '', onChange: (url) => {
|
|
277
|
+
form.setValue('audioUrl', url, { shouldValidate: true });
|
|
278
|
+
}, maxSize: 10, placeholder: "T\u1EA3i l\u00EAn file \u00E2m thanh (mp3, wav...)", autoUpload: true, prefix: "listen-and-speak" }), form.hasError('audioUrl') && (_jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('audioUrl') })), audioPreviewUrl && (_jsx("audio", { controls: true, className: "mt-2 w-full", src: audioPreviewUrl, children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." }))] }), externalErrors && externalErrors.length > 0 && (_jsx("div", { className: "flex items-start gap-3 rounded-xl border border-red-200 bg-red-50 p-4", 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(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100", children: _jsx(MessageSquareText, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsxs("p", { className: "text-sm font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n mong mu\u1ED1n (", watchedExpectedAnswers.filter((a) => a.trim()).length, ")"] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => {
|
|
279
|
+
const currentAnswers = form.getValues('expectedAnswers');
|
|
280
|
+
form.setValue('expectedAnswers', [...currentAnswers, '']);
|
|
281
|
+
}, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n"] })] }), children: [_jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2.5", children: [_jsx(Info, { className: "h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0" }), _jsxs("div", { className: "text-xs text-blue-700 space-y-1", children: [_jsx("p", { children: "Nh\u1EADp c\u00E1c c\u00E2u tr\u1EA3 l\u1EDDi mong mu\u1ED1n. C\u00F3 th\u1EC3 th\u00EAm nhi\u1EC1u \u0111\u00E1p \u00E1n n\u1EBFu c\u00F3 nhi\u1EC1u c\u00E1ch tr\u1EA3 l\u1EDDi \u0111\u00FAng." }), _jsxs("p", { children: [_jsx("span", { className: "font-semibold", children: "\u2022 Ch\u1EA5m th\u1EE7 c\u00F4ng:" }), " Gi\u00E1o vi\u00EAn d\u00F9ng c\u00E1c \u0111\u00E1p \u00E1n n\u00E0y l\u00E0m tham kh\u1EA3o \u0111\u1EC3 ch\u1EA5m \u0111i\u1EC3m."] }), _jsxs("p", { children: [_jsx("span", { className: "font-semibold", children: "\u2022 Ch\u1EA5m t\u1EF1 \u0111\u1ED9ng (AI):" }), " H\u1EC7 th\u1ED1ng d\u00F9ng AI so s\u00E1nh gi\u1ECDng n\u00F3i c\u1EE7a h\u1ECDc sinh v\u1EDBi c\u00E1c \u0111\u00E1p \u00E1n \u0111\u1EC3 t\u1EF1 \u0111\u1ED9ng ch\u1EA5m \u0111i\u1EC3m."] })] })] }), _jsxs("div", { className: "space-y-3", children: [watchedExpectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-start gap-3 group/answer", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 font-semibold shadow-sm text-sm", children: index + 1 }), _jsxs("div", { className: "flex-1", children: [_jsx(Input, { value: answer || '', onChange: (e) => {
|
|
282
|
+
const newAnswers = [...watchedExpectedAnswers];
|
|
283
|
+
newAnswers[index] = e.target.value;
|
|
289
284
|
form.setValue('expectedAnswers', newAnswers, { shouldValidate: true });
|
|
290
|
-
},
|
|
285
|
+
}, placeholder: `Ví dụ: I have a dog...`, className: "border-gray-200 focus:border-green-300 focus:ring-green-200" }), form.formState.errors?.expectedAnswers?.[index]?.message && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.expectedAnswers[index]?.message }))] }), watchedExpectedAnswers.length > 1 && (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => {
|
|
286
|
+
const newAnswers = watchedExpectedAnswers.filter((_, i) => i !== index);
|
|
287
|
+
form.setValue('expectedAnswers', newAnswers, { shouldValidate: true });
|
|
288
|
+
}, className: "text-red-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }, index))), form.formState.errors?.expectedAnswers && !Array.isArray(form.formState.errors.expectedAnswers) && (_jsx("p", { className: "text-sm text-red-500", children: form.formState.errors.expectedAnswers.message }))] })] }), _jsx(CompactExplanationToggle, { value: watchedExplanation || '', onValueChange: (value) => form.setValue('explanation', value, { shouldValidate: true }), defaultVisible: Boolean(watchedExplanation) })] }));
|
|
291
289
|
}
|
|
@@ -28,5 +28,5 @@ export function ListenAndSpeakCompareImagesClient({ questionData, isReviewMode =
|
|
|
28
28
|
// fall back to store values (exam-taking mode populates the store)
|
|
29
29
|
const candidateImageUrl = questionData.candidateImageUrl || storeCandidateImageUrl;
|
|
30
30
|
const examinerImageUrl = questionData.examinerImageUrl || storeExaminerImageUrl;
|
|
31
|
-
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "flex items-center justify-between border-b border-gray-100 bg-
|
|
31
|
+
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gray-50 px-3 py-2", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100 text-gray-600", children: _jsx(GitCompareArrows, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 n\u00F3i (So s\u00E1nh h\u00ECnh \u1EA3nh)" })] }) }), _jsxs("div", { className: "p-3 space-y-3", children: [audioUrl ? (_jsx(QuestionAudioPlayer, { audioUrl: audioUrl })) : (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3", children: [_jsx(Volume2, { className: "h-4 w-4 text-gray-400" }), _jsx("span", { className: "text-xs text-gray-400", children: "Ch\u01B0a c\u00F3 audio c\u00E2u h\u1ECFi" })] })), (candidateImageUrl || examinerImageUrl) && (_jsxs("div", { className: "space-y-4 bg-gradient-to-br from-amber-100 to-orange-100 p-5 rounded-2xl border border-amber-200 shadow-sm", children: [candidateImageUrl && (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "flex justify-center", children: _jsx("span", { className: "inline-block rounded-lg border-2 border-amber-400 bg-gradient-to-r from-gray-600 to-gray-700 px-8 py-2 text-sm font-bold uppercase tracking-widest text-white shadow-lg ring-1 ring-amber-300/50", children: "Candidate's Copy" }) }), _jsx("div", { className: "w-full rounded-xl overflow-hidden shadow-md bg-white border border-amber-200", children: _jsx(CompareImagePreview, { imageUrl: candidateImageUrl, label: "Candidate's Copy" }) })] })), examinerImageUrl && (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "flex justify-center", children: _jsx("span", { className: "inline-block rounded-lg border-2 border-orange-400 bg-gradient-to-r from-gray-600 to-gray-700 px-8 py-2 text-sm font-bold uppercase tracking-widest text-white shadow-lg ring-1 ring-orange-300/50", children: "Examiner's Copy" }) }), _jsx("div", { className: "w-full rounded-xl overflow-hidden shadow-md bg-white border border-amber-200", children: _jsx(CompareImagePreview, { imageUrl: examinerImageUrl, label: "Examiner's Copy" }) })] }))] })), _jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: "H\u1ECDc sinh s\u1EBD nh\u1EA5n n\u00FAt ghi \u00E2m v\u00E0 n\u00F3i c\u00E2u tr\u1EA3 l\u1EDDi t\u1EA1i \u0111\u00E2y" })] }), isReviewMode && expectedAnswers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsx("h4", { className: "text-sm font-semibold text-green-800", children: "C\u00E1c \u0111\u00E1p \u00E1n mong mu\u1ED1n" })] }), _jsx("div", { className: "space-y-2", children: expectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold", children: index + 1 }), _jsx("span", { className: "text-sm text-green-800", children: answer })] }, index))) })] })), isReviewMode && explanation && (_jsxs("div", { className: "rounded-xl border border-indigo-200 bg-indigo-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-indigo-600" }), _jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("p", { className: "text-sm text-indigo-700 leading-relaxed", children: explanation })] }))] })] })] }));
|
|
32
32
|
}
|