@tinyweb_dev/oe-exam-sdk 1.0.6 → 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
package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useRef, useEffect } from 'react';
|
|
3
|
+
import { useRef, useEffect, useState } from 'react';
|
|
4
4
|
import { Button } from '../../../../components/ui/button';
|
|
5
5
|
import { Label } from '../../../../components/ui/label';
|
|
6
6
|
import { Input } from '../../../../components/ui/input';
|
|
7
|
-
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 { Plus, Trash2, ImageIcon, MessageSquareText
|
|
9
|
+
import { Plus, Trash2, ImageIcon, MessageSquareText } from 'lucide-react';
|
|
13
10
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
14
|
-
import {
|
|
11
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
12
|
+
import { SpeakingDescribeImageClient } from './SpeakingDescribeImageClient';
|
|
15
13
|
import { speakingDescribeImageFormSchema, } from '../../../../shared/lib/validation/speaking-describe-image.validation';
|
|
16
14
|
export function SpeakingDescribeImageCreator({ initialData, onSave, onChange, externalErrors, validationRef, }) {
|
|
17
15
|
const apiData = initialData;
|
|
@@ -32,6 +30,7 @@ export function SpeakingDescribeImageCreator({ initialData, onSave, onChange, ex
|
|
|
32
30
|
const isInitializingRef = useRef(false);
|
|
33
31
|
const onChangeRef = useRef(onChange);
|
|
34
32
|
const previousPayloadRef = useRef('');
|
|
33
|
+
const [isClientMode, setIsClientMode] = useState(false);
|
|
35
34
|
useEffect(() => {
|
|
36
35
|
onChangeRef.current = onChange;
|
|
37
36
|
}, [onChange]);
|
|
@@ -140,22 +139,30 @@ export function SpeakingDescribeImageCreator({ initialData, onSave, onChange, ex
|
|
|
140
139
|
...data,
|
|
141
140
|
});
|
|
142
141
|
});
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
142
|
+
if (isClientMode) {
|
|
143
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(SpeakingDescribeImageClient, { questionData: {
|
|
144
|
+
question: watchedQuestion || '',
|
|
145
|
+
imageUrl: watchedImageUrl || '',
|
|
146
|
+
expectedAnswers: watchedExpectedAnswers || [],
|
|
147
|
+
explanation: watchedExplanation,
|
|
148
|
+
points: watchedPoints,
|
|
149
|
+
}, isReviewMode: true })] }));
|
|
150
|
+
}
|
|
151
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(ImageIcon, { className: "h-3.5 w-3.5" }), title: "N\u1ED9i dung c\u00E2u h\u1ECFi", points: {
|
|
152
|
+
value: watchedPoints,
|
|
153
|
+
onValueChange: (value) => form.setValue('points', value, { shouldValidate: true }),
|
|
154
|
+
error: form.hasError('points') ? form.getFieldError('points') : undefined,
|
|
155
|
+
}, preview: { onClick: () => setIsClientMode(true) } }), 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(MessageSquareText, { className: "h-4 w-4 text-blue-500" }), "C\u00E2u h\u1ECFi", _jsx("span", { className: "text-xs font-normal text-gray-400", children: "(Kh\u00F4ng b\u1EAFt bu\u1ED9c)" })] }), _jsx(Input, { value: watchedQuestion || '', onChange: (e) => form.setValue('question', e.target.value, { shouldValidate: true }), placeholder: "VD: What is this?", className: "border-gray-200 bg-white transition-all focus:border-blue-300 focus:ring-blue-200" })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-indigo-500" }), "H\u00ECnh \u1EA3nh c\u1EA7n m\u00F4 t\u1EA3"] }), _jsx(FileUpload, { id: "imageUrl", label: "", accept: "image/*", value: watchedImageUrl, onChange: (url) => {
|
|
156
|
+
form.setValue('imageUrl', url, { shouldValidate: true });
|
|
157
|
+
}, maxSize: 10, placeholder: "Upload h\u00ECnh \u1EA3nh", autoUpload: true, prefix: "speaking/describe-image" }), form.hasError('imageUrl') && (_jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('imageUrl') }))] }), imagePreviewUrl && (_jsx("div", { className: "mt-2 rounded-lg overflow-hidden border border-gray-200 flex justify-center bg-gray-100", children: _jsx("img", { src: imagePreviewUrl, alt: "Preview", className: "max-h-[300px] object-contain" }) })), externalErrors && externalErrors.length > 0 && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-red-100", children: _jsx("span", { className: "text-lg", children: "\u274C" }) }), _jsxs("div", { className: "text-sm", children: [_jsx("p", { className: "font-semibold text-red-800", children: "L\u1ED7i:" }), _jsx("ul", { className: "mt-1 list-inside list-disc text-red-700", children: externalErrors.map((error, index) => (_jsx("li", { children: error }, index))) })] })] }))] }), _jsx(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 / Keywords expected ", _jsxs("span", { className: "text-xs font-normal text-gray-500", children: ["(", watchedExpectedAnswers.filter((a) => a.trim()).length, " \u0111\u00E1p \u00E1n)"] })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => {
|
|
158
|
+
const currentAnswers = form.getValues('expectedAnswers');
|
|
159
|
+
form.setValue('expectedAnswers', [...currentAnswers, '']);
|
|
160
|
+
}, 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 d\u00F2ng"] })] }), children: _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) => {
|
|
161
|
+
const newAnswers = [...watchedExpectedAnswers];
|
|
162
|
+
newAnswers[index] = e.target.value;
|
|
159
163
|
form.setValue('expectedAnswers', newAnswers, { shouldValidate: true });
|
|
160
|
-
}, className: "
|
|
164
|
+
}, placeholder: `Ví dụ: The boy is kicking a football.`, className: "border-gray-200 bg-white transition-all 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: () => {
|
|
165
|
+
const newAnswers = watchedExpectedAnswers.filter((_, i) => i !== index);
|
|
166
|
+
form.setValue('expectedAnswers', newAnswers, { shouldValidate: true });
|
|
167
|
+
}, 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) })] }));
|
|
161
168
|
}
|
|
@@ -11,5 +11,5 @@ function sampleAnswers(questionData) {
|
|
|
11
11
|
export function SpontaneousQAClient({ questionData, isReviewMode = false, }) {
|
|
12
12
|
const { previewUrl: questionAudioUrl } = usePresignedFileUrl(questionData.audioUrl || '');
|
|
13
13
|
const answers = sampleAnswers(questionData);
|
|
14
|
-
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-
|
|
14
|
+
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: "H\u1ECFi \u0111\u00E1p t\u1EF1 ph\u00E1t" })] }) }), _jsxs("div", { className: "p-3 space-y-3", children: [questionData.questionText && (_jsxs("div", { className: "flex gap-3", children: [_jsx(FileQuestion, { className: "h-5 w-5 text-indigo-500 shrink-0 mt-0.5" }), _jsx("div", { className: "text-base font-semibold text-gray-800", children: questionData.questionText })] })), questionAudioUrl ? (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-blue-100 bg-blue-50/60 px-4 py-3", children: [_jsx(Volume2, { className: "h-4 w-4 text-blue-500 shrink-0" }), _jsx("audio", { controls: true, src: questionAudioUrl, className: "h-8 w-full max-w-md", children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." })] })) : 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: "\u0110ang t\u1EA3i audio c\u00E2u h\u1ECFi..." })] })) : (_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" })] })), _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 nghe c\u00E2u h\u1ECFi r\u1ED3i nh\u1EA5n micro \u0111\u1EC3 tr\u1EA3 l\u1EDDi b\u1EB1ng gi\u1ECDng n\u00F3i" })] }), isReviewMode && answers.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: "\u0110\u00E1p \u00E1n m\u1EABu" })] }), _jsx("div", { className: "space-y-2", children: answers.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 })] }, `${answer}-${index}`))) })] })), isReviewMode && questionData.explanation && (_jsxs("div", { className: "rounded-xl border border-amber-200 bg-amber-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-amber-600" }), _jsx("p", { className: "text-sm font-semibold text-amber-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("p", { className: "text-sm text-amber-700 leading-relaxed whitespace-pre-wrap", children: questionData.explanation })] }))] })] })] }));
|
|
15
15
|
}
|
|
@@ -5,14 +5,13 @@ import { useParams } from 'next/navigation';
|
|
|
5
5
|
import { Button } from '../../../../components/ui/button';
|
|
6
6
|
import { Label } from '../../../../components/ui/label';
|
|
7
7
|
import { Input } from '../../../../components/ui/input';
|
|
8
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
9
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
10
8
|
import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
11
9
|
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
12
|
-
import { Mic, Type, Volume2,
|
|
10
|
+
import { Mic, Type, Volume2, Info, Loader2, Layers } from 'lucide-react';
|
|
13
11
|
import { toast } from 'sonner';
|
|
14
12
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
15
|
-
import {
|
|
13
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
14
|
+
import { SpontaneousQAClient } from './SpontaneousQAClient';
|
|
16
15
|
import { spontaneousQAFormSchema, } from '../../../../shared/lib/validation/spontaneous-qa.validation';
|
|
17
16
|
export function SpontaneousQACreator({ initialData, onSave, onChange, externalErrors, validationRef, }) {
|
|
18
17
|
const apiData = initialData;
|
|
@@ -47,6 +46,7 @@ export function SpontaneousQACreator({ initialData, onSave, onChange, externalEr
|
|
|
47
46
|
const isInitializingRef = useRef(false);
|
|
48
47
|
const onChangeRef = useRef(onChange);
|
|
49
48
|
const previousPayloadRef = useRef('');
|
|
49
|
+
const [isClientMode, setIsClientMode] = useState(false);
|
|
50
50
|
const [isGeneratingAudio, setIsGeneratingAudio] = useState(false);
|
|
51
51
|
const [previewAudioUrl, setPreviewAudioUrl] = useState(null);
|
|
52
52
|
const [lastGeneratedText, setLastGeneratedText] = useState(apiData?.questionText || apiData?.content?.questionText || '');
|
|
@@ -216,19 +216,26 @@ export function SpontaneousQACreator({ initialData, onSave, onChange, externalEr
|
|
|
216
216
|
setIsGeneratingAudio(false);
|
|
217
217
|
}
|
|
218
218
|
};
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
219
|
+
if (isClientMode) {
|
|
220
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(SpontaneousQAClient, { questionData: {
|
|
221
|
+
inputType: watchedInputType,
|
|
222
|
+
audioUrl: watchedAudioUrl,
|
|
223
|
+
questionText: watchedQuestionText,
|
|
224
|
+
sampleAnswers: watchedSampleAnswers,
|
|
225
|
+
expectedAnswers: watchedSampleAnswers,
|
|
226
|
+
explanation: watchedExplanation,
|
|
227
|
+
points: watchedPoints,
|
|
228
|
+
}, isReviewMode: true })] }));
|
|
229
|
+
}
|
|
230
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(Layers, { className: "h-3.5 w-3.5" }), title: "C\u1EA5u h\u00ECnh N\u1ED9i dung C\u00E2u h\u1ECFi & \u0110\u00E1p \u00E1n", points: {
|
|
231
|
+
value: watchedPoints,
|
|
232
|
+
onValueChange: (value) => form.setValue('points', value, { shouldValidate: true }),
|
|
233
|
+
error: form.hasError('points') ? form.getFieldError('points') : undefined,
|
|
234
|
+
}, preview: { onClick: () => setIsClientMode(true) } }), children: [_jsxs("div", { className: "space-y-2.5", children: [_jsx("div", { className: "grid grid-cols-1 gap-2.5 md:grid-cols-2", children: _jsxs("div", { className: "space-y-3", children: [_jsx(Label, { className: "text-sm font-semibold text-slate-700 block", children: "H\u00ECnh th\u1EE9c nh\u1EADp c\u00E2u h\u1ECFi" }), _jsxs("div", { className: "flex w-fit rounded-lg border border-slate-200 bg-slate-100 p-1", 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'
|
|
235
|
+
? 'bg-white text-blue-700 shadow-sm'
|
|
236
|
+
: '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'
|
|
237
|
+
? 'bg-white text-emerald-700 shadow-sm'
|
|
238
|
+
: '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)"] })] }), form.hasError('inputType') && (_jsx("p", { className: "text-sm text-red-600 mt-1", children: form.getFieldError('inputType') }))] }) }), _jsx("hr", { className: "border-slate-100" }), _jsx("div", { className: "space-y-4", children: _jsx("div", { className: "max-w-3xl", children: watchedInputType === 'AUDIO' ? (_jsxs("div", { className: "space-y-2 animate-in fade-in zoom-in-95 duration-200", children: [_jsx(Label, { className: "text-sm font-medium text-slate-700", children: "File audio c\u00E2u h\u1ECFi" }), _jsx(FileUpload, { id: "audioUrl", label: "", accept: "audio/*", value: watchedAudioUrl || '', onChange: (url) => {
|
|
239
|
+
form.setValue('audioUrl', url, { shouldValidate: true });
|
|
240
|
+
}, maxSize: 20, placeholder: "T\u1EA3i l\u00EAn file audio (MP3, WAV...)", autoUpload: true, prefix: "speaking/spontaneous-qa" }), form.hasError('audioUrl') && (_jsx("p", { className: "text-sm text-red-600 mt-1", children: form.getFieldError('audioUrl') })), _jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-amber-100 bg-amber-50/60 px-3 py-2 mt-2", children: [_jsx(Info, { className: "h-4 w-4 text-amber-500 mt-0.5 flex-shrink-0" }), _jsx("p", { className: "text-xs text-amber-700", children: "Upload file audio ch\u1EE9a c\u00E2u h\u1ECFi. H\u1ECDc sinh s\u1EBD nghe audio n\u00E0y v\u00E0 tr\u1EA3 l\u1EDDi t\u1EF1 do." })] })] })) : (_jsxs("div", { className: "space-y-3 animate-in fade-in zoom-in-95 duration-200", children: [_jsx(Label, { className: "text-sm font-medium text-slate-700", children: "N\u1ED9i dung c\u00E2u h\u1ECFi (Text \u2192 TTS)" }), _jsx(Input, { ...form.register('questionText'), placeholder: "VD: What is your favorite food and why?", className: "border-slate-200 focus:border-blue-400 focus:ring-blue-100" }), form.hasError('questionText') && (_jsx("p", { className: "text-sm text-red-600 mt-1", children: form.getFieldError('questionText') })), _jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-amber-100 bg-amber-50/60 px-3 py-2", children: [_jsx(Info, { className: "h-4 w-4 text-amber-500 mt-0.5 flex-shrink-0" }), _jsx("p", { className: "text-xs text-amber-700", children: "H\u1EC7 th\u1ED1ng s\u1EBD t\u1EF1 \u0111\u1ED9ng chuy\u1EC3n v\u0103n b\u1EA3n th\u00E0nh gi\u1ECDng n\u00F3i (TTS). Click n\u00FAt \"T\u1EA1o Audio\" \u0111\u1EC3 h\u1EC7 th\u1ED1ng x\u1EED l\u00FD." })] }), _jsxs("div", { className: "flex items-center gap-4 mt-2", children: [_jsxs(Button, { type: "button", variant: "default", size: "sm", onClick: handleGenerateAudio, disabled: isGeneratingAudio || !watchedQuestionText || watchedQuestionText === lastGeneratedText, className: "h-8 w-fit gap-1.5 disabled:opacity-50", 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"] }), displayAudioUrl && (_jsx("audio", { controls: true, src: displayAudioUrl, className: "h-8 max-w-[250px]" }))] })] })) }) })] }), externalErrors && externalErrors.length > 0 && (_jsx("div", { className: "rounded-md border border-red-100 bg-red-50 px-3 py-2", 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))) })] }) }) }))] }), _jsx(CompactExplanationToggle, { value: watchedExplanation || '', onValueChange: (value) => form.setValue('explanation', value, { shouldValidate: true }), defaultVisible: Boolean(watchedExplanation) })] }));
|
|
234
241
|
}
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { CircleHelp, Lightbulb } from 'lucide-react';
|
|
4
4
|
export function SpontaneousQaGroupClient({ questionData, }) {
|
|
5
5
|
const parts = questionData.parts || [];
|
|
6
|
-
return (_jsxs("div", { className: "space-y-4", children: [questionData.instruction && (_jsx("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800", children: questionData.instruction })), parts.map((part, partIndex) => (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "border-b border-gray-100 bg-gradient-to-r from-slate-50 to-white px-
|
|
6
|
+
return (_jsxs("div", { className: "space-y-4", children: [questionData.instruction && (_jsx("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800", children: questionData.instruction })), parts.map((part, partIndex) => (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "border-b border-gray-100 bg-gradient-to-r from-slate-50 to-white px-3 py-2", children: [_jsx("p", { className: "text-sm font-semibold text-slate-800", children: part.partTitle || `PART ${part.partNumber || partIndex + 1}` }), part.topic && (_jsx("p", { className: "mt-1 text-sm text-slate-600", children: part.topic }))] }), _jsx("div", { className: "space-y-3 p-3", children: part.questions.map((question, questionIndex) => (_jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-3", children: [_jsxs("div", { className: "mb-2 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-lg bg-blue-100 text-blue-700", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", question.questionNumber || questionIndex + 1] })] }), _jsxs("span", { className: "text-xs text-gray-500", children: [question.points || 0, " \u0111i\u1EC3m"] })] }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: question.questionText || 'Câu hỏi chưa được nhập' }), question.expectedAnswers.filter((item) => item.trim() !== '').length > 0 && (_jsxs("div", { className: "mt-2 rounded-lg border border-amber-100 bg-amber-50 px-3 py-2", children: [_jsxs("div", { className: "mb-1 flex items-center gap-1 text-xs font-semibold text-amber-700", children: [_jsx(Lightbulb, { className: "h-3 w-3" }), "\u0110\u00E1p \u00E1n m\u1EABu"] }), _jsx("ul", { className: "list-disc space-y-1 pl-4 text-xs text-amber-800", children: question.expectedAnswers
|
|
7
7
|
.filter((item) => item.trim() !== '')
|
|
8
8
|
.map((item) => (_jsx("li", { children: item }, item))) })] }))] }, `${part.partNumber}-${question.questionNumber}-${questionIndex}`))) })] }, `part-${part.partNumber}-${partIndex}`)))] }));
|
|
9
9
|
}
|
|
@@ -6,11 +6,11 @@ import { Label } from '../../../../components/ui/label';
|
|
|
6
6
|
import { Input } from '../../../../components/ui/input';
|
|
7
7
|
import { Textarea } from '../../../../components/ui/textarea';
|
|
8
8
|
import { PointsInput } from '../../../../components/ui/points-input';
|
|
9
|
-
import {
|
|
9
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
10
10
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
11
11
|
import { useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
12
12
|
import { toast } from 'sonner';
|
|
13
|
-
import {
|
|
13
|
+
import { Layers, Mic, Plus, Trash2, Type, Volume2 } from 'lucide-react';
|
|
14
14
|
import { SpontaneousQaGroupClient } from './SpontaneousQaGroupClient';
|
|
15
15
|
import { createEmptyPart, createEmptyQuestion, normalizeParts, sumPartPoints, } from './creator-helpers';
|
|
16
16
|
export function SpontaneousQaGroupCreator({ initialData, onChange, externalErrors, onUnsavedChangesChange, validationRef, }) {
|
|
@@ -165,15 +165,17 @@ export function SpontaneousQaGroupCreator({ initialData, onChange, externalError
|
|
|
165
165
|
toast.error('TTS chưa được tích hợp trong component SDK');
|
|
166
166
|
};
|
|
167
167
|
if (isClientMode) {
|
|
168
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsx(
|
|
168
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(SpontaneousQaGroupClient, { questionData: buildPayload(), isReviewMode: true })] }));
|
|
169
169
|
}
|
|
170
170
|
const allErrors = [...errors, ...(externalErrors || [])];
|
|
171
|
-
return (_jsxs("div", { className: "space-y-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
171
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(Layers, { className: "h-3.5 w-3.5" }), title: "Nh\u00F3m h\u1ECFi \u0111\u00E1p t\u1EF1 ph\u00E1t", extraActions: _jsx(PointsInput, { value: sumPartPoints(parts), allowZero: true, readOnly: true }), preview: {
|
|
172
|
+
onClick: () => setIsClientMode(true),
|
|
173
|
+
} }), children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "group-instruction", className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(Input, { id: "group-instruction", value: instruction, onChange: (event) => setInstruction(event.target.value), placeholder: "In pairs, ask and answer the questions.", className: "h-10 border-gray-200 bg-white" })] }), _jsx("p", { className: "text-xs text-gray-500", children: "T\u1ED5ng \u0111i\u1EC3m \u0111\u01B0\u1EE3c c\u1ED9ng t\u1EEB \u0111i\u1EC3m t\u1EEBng c\u00E2u trong t\u1EA5t c\u1EA3 part." })] }), parts.map((part, partIndex) => (_jsxs(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("p", { className: "text-sm font-semibold text-gray-800", children: ["Part ", partIndex + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removePart(partIndex), disabled: parts.length <= 1, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), children: [_jsxs("div", { className: "grid gap-2.5 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Ti\u00EAu \u0111\u1EC1 part" }), _jsx(Input, { value: part.partTitle, onChange: (event) => updatePart(partIndex, { partTitle: event.target.value }), placeholder: "PART 1" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Ch\u1EE7 \u0111\u1EC1" }), _jsx(Input, { value: part.topic, onChange: (event) => updatePart(partIndex, { topic: event.target.value }), placeholder: "Talk about school." })] })] }), part.questions.map((question, questionIndex) => (_jsxs("div", { className: "space-y-2.5 rounded-md border border-slate-200 px-3 py-2.5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("p", { className: "text-sm font-semibold text-slate-700", children: ["C\u00E2u ", questionIndex + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeQuestion(partIndex, questionIndex), disabled: part.questions.length <= 1, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), _jsxs("div", { className: "flex w-fit rounded-lg border border-slate-200 bg-slate-100 p-1", children: [_jsxs("button", { type: "button", onClick: () => updateQuestion(partIndex, questionIndex, { inputType: 'AUDIO' }), className: `flex items-center gap-2 rounded-md px-3 py-1.5 text-xs font-medium ${question.inputType === 'AUDIO' ? 'bg-white text-blue-700 shadow-sm' : 'text-slate-500'}`, children: [_jsx(Mic, { className: "h-3.5 w-3.5" }), " Audio"] }), _jsxs("button", { type: "button", onClick: () => updateQuestion(partIndex, questionIndex, { inputType: 'TEXT' }), className: `flex items-center gap-2 rounded-md px-3 py-1.5 text-xs font-medium ${question.inputType === 'TEXT' ? 'bg-white text-emerald-700 shadow-sm' : 'text-slate-500'}`, children: [_jsx(Type, { className: "h-3.5 w-3.5" }), " Text (TTS)"] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "C\u00E2u h\u1ECFi" }), _jsx(Textarea, { value: question.questionText, onChange: (event) => updateQuestion(partIndex, questionIndex, {
|
|
174
|
+
questionText: event.target.value,
|
|
175
|
+
}), rows: 2, placeholder: "Do you like your timetable at school? Why?/Why not?" })] }), question.inputType === 'AUDIO' ? (_jsx(FileUpload, { id: `audio-${partIndex}-${questionIndex}`, label: "File audio c\u00E2u h\u1ECFi", accept: "audio/*", value: question.audioUrl, onChange: (url) => updateQuestion(partIndex, questionIndex, { audioUrl: url }), maxSize: 20, placeholder: "T\u1EA3i l\u00EAn file audio (MP3, WAV...)", autoUpload: true, prefix: "speaking/spontaneous-qa-group" })) : (_jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: !question.questionText.trim(), onClick: handleGenerateAudio, children: [_jsx(Volume2, { className: "mr-2 h-3.5 w-3.5" }), "T\u1EA1o Audio"] })), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "\u0110\u00E1p \u00E1n m\u1EABu" }), question.expectedAnswers.map((answer, answerIndex) => (_jsx(Input, { value: answer, onChange: (event) => updateExpectedAnswer(partIndex, questionIndex, answerIndex, event.target.value), placeholder: "\u0110\u00E1p \u00E1n m\u1EABu cho AI ch\u1EA5m" }, `ans-${partIndex}-${questionIndex}-${answerIndex}`))), _jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => addExpectedAnswer(partIndex, questionIndex), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n m\u1EABu"] })] }), _jsx(PointsInput, { value: question.points, allowZero: true, onChange: (event) => {
|
|
176
|
+
const parsed = Number.parseInt(event.target.value.replace(/[^0-9]/g, ''), 10);
|
|
177
|
+
updateQuestion(partIndex, questionIndex, {
|
|
178
|
+
points: Number.isNaN(parsed) ? 0 : Math.max(0, parsed),
|
|
179
|
+
});
|
|
180
|
+
} })] }, `q-${partIndex}-${questionIndex}`))), _jsxs(Button, { type: "button", variant: "outline", onClick: () => addQuestion(partIndex), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm c\u00E2u h\u1ECFi"] })] }, `part-${partIndex}`))), _jsxs(Button, { type: "button", variant: "outline", onClick: addPart, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm part"] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) }), allErrors.length > 0 && (_jsx("div", { className: "rounded-md border border-red-200 bg-red-50 p-2 text-xs text-red-700", children: allErrors.map((error) => (_jsx("p", { children: error }, error))) }))] }));
|
|
179
181
|
}
|
|
@@ -23,9 +23,9 @@ export function TrueFalseClient({ questionData, onSubmit, isReviewMode = false,
|
|
|
23
23
|
};
|
|
24
24
|
const hasAnswered = selectedAnswer !== undefined;
|
|
25
25
|
const isAnswerCorrect = isReviewMode && selectedAnswer === questionData.answer;
|
|
26
|
-
return (_jsx("div", { className: "space-y-4", children: _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-
|
|
26
|
+
return (_jsx("div", { className: "space-y-4", children: _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-3 py-2", 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-emerald-500 to-teal-600 text-white shadow-sm", children: _jsx(CircleHelp, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "\u0110\u00FAng / Sai" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAnswerCorrect
|
|
27
27
|
? 'bg-green-100 text-green-700'
|
|
28
|
-
: '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-
|
|
28
|
+
: '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: [_jsx("div", { className: "rounded-lg border border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-4", children: _jsx("h3", { className: "text-base font-semibold leading-relaxed text-gray-800", children: questionData.question || 'Chưa nhập câu hỏi' }) }), (imagePreviewUrl || questionData.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" }) })) : (
|
|
29
29
|
/* eslint-disable-next-line @next/next/no-img-element */
|
|
30
30
|
_jsx("img", { src: imagePreviewUrl || questionData.imageUrl, alt: "Question illustration", className: "max-h-56 w-auto rounded-md object-contain" })) })] })), _jsxs("div", { className: "space-y-2.5", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-gray-500", children: "Ch\u1ECDn \u0111\u00E1p \u00E1n" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("button", { type: "button", onClick: () => handleAnswerChange(true), disabled: isReviewMode, className: cn('group flex items-center justify-center gap-3 rounded-xl border-2 p-4 transition-all', isReviewMode
|
|
31
31
|
? questionData.answer === true
|
|
@@ -2,14 +2,11 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { ImagePreview } from '../../../../components/ui/image-preview';
|
|
4
4
|
import { useState, useRef, useEffect } from 'react';
|
|
5
|
-
import { Button } from '../../../../components/ui/button';
|
|
6
5
|
import { Label } from '../../../../components/ui/label';
|
|
7
6
|
import { Textarea } from '../../../../components/ui/textarea';
|
|
8
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
9
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
10
|
-
import { Checkbox } from '../../../../components/ui/checkbox';
|
|
11
7
|
import { usePresignedFileUrl, useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
12
|
-
import {
|
|
8
|
+
import { ImageIcon, MessageSquare, HelpCircle, Check, X } from 'lucide-react';
|
|
9
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
13
10
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
14
11
|
import { TrueFalseClient } from './TrueFalseClient';
|
|
15
12
|
import { cn } from '../../../../shared/lib/utils';
|
|
@@ -211,33 +208,42 @@ function TrueFalseCreatorContent({ initialData, onSave, onCancel, onChange, exte
|
|
|
211
208
|
groupNumber: currentGroupNumber,
|
|
212
209
|
...(effectiveGroupType === 'ARTICLES' ? { articles } : {}),
|
|
213
210
|
};
|
|
214
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsx(
|
|
211
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(TrueFalseClient, { questionData: questionData, isReviewMode: true, userAnswer: answer })] }));
|
|
215
212
|
}
|
|
216
213
|
// Creator UI
|
|
217
|
-
return (_jsxs("div", { className: "space-y-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
214
|
+
return (_jsxs("div", { className: "space-y-3", children: [isGrouped && effectiveGroupType === 'ARTICLES' && (_jsx(ArticlesGroupCard, { groupNumber: currentGroupNumber, articleCount: articleCount, articles: articles, onArticlesChange: setArticles, title: groupTitle, onTitleChange: setGroupTitle, isReadOnly: !isFirstInGroup, uploadIdPrefix: `true-false-${partId ?? 'articles'}` })), isGrouped && effectiveGroupType === 'BASIC' && (_jsx(BasicQuestionGroupCard, { groupNumber: currentGroupNumber, title: groupTitle, subtitle: groupSubtitle, content: groupContent, imageUrl: groupImageUrl, onTitleChange: setGroupTitle, onSubtitleChange: setGroupSubtitle, onContentChange: setGroupContent, onImageChange: setGroupImageUrl, groupConfig: groupType ? groupConfig : undefined, isReadOnly: !isFirstInGroup, uploadIdPrefix: `true-false-${partId ?? 'group'}` })), isGrouped && effectiveGroupType === 'DOCUMENT' && (_jsx(DocumentGroupCard, { groupNumber: currentGroupNumber, title: groupTitle, subtitle: groupSubtitle, content: groupContent, imageUrl: groupImageUrl, onTitleChange: setGroupTitle, onSubtitleChange: setGroupSubtitle, onContentChange: setGroupContent, onImageChange: setGroupImageUrl, groupConfig: groupType ? groupConfig : undefined, isReadOnly: !isFirstInGroup, uploadIdPrefix: `true-false-${partId ?? 'document'}` })), _jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(HelpCircle, { className: "h-3.5 w-3.5" }), title: "\u0110\u00FAng / Sai", points: {
|
|
215
|
+
value: points,
|
|
216
|
+
onValueChange: setPoints,
|
|
217
|
+
}, example: {
|
|
218
|
+
checked: isExample,
|
|
219
|
+
onChange: setIsExample,
|
|
220
|
+
}, headerExtra: headerExtra, preview: {
|
|
221
|
+
onClick: () => setIsClientMode(true),
|
|
222
|
+
disabled: !question,
|
|
223
|
+
} }), children: [_jsxs("div", { className: "space-y-1.5", children: [_jsxs(Label, { className: "flex items-center gap-1.5 text-sm font-semibold text-gray-800", children: [_jsx(ImageIcon, { className: "h-3.5 w-3.5 text-teal-500" }), "H\u00ECnh \u1EA3nh ", _jsx("span", { className: "text-xs font-normal text-gray-500", children: "(t\u00F9y ch\u1ECDn)" })] }), _jsx(FileUpload, { id: "question-image", label: "", accept: "image/*", value: imageUrl || '', onChange: (url) => setImageUrl(url), onPresignedUrlChange: setImagePreviewUrl, maxSize: 5, placeholder: "Upload \u1EA3nh ho\u1EB7c paste URL", autoUpload: true, prefix: "questions" }), displayPreviewUrl && (_jsx(ImagePreview, { src: displayPreviewUrl, alt: "Preview", className: "mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200" }))] }), _jsxs("div", { className: "space-y-1.5", children: [_jsxs(Label, { htmlFor: "question", className: "flex items-center gap-1.5 text-sm font-semibold text-gray-800", children: [_jsx(MessageSquare, { className: "h-3.5 w-3.5 text-teal-500" }), "C\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Textarea, { id: "question", value: question, onChange: (e) => {
|
|
224
|
+
setQuestion(e.target.value);
|
|
225
|
+
if (validationErrors.question) {
|
|
226
|
+
setValidationErrors((prev) => ({ ...prev, question: undefined }));
|
|
227
|
+
}
|
|
228
|
+
}, placeholder: "Nh\u1EADp c\u00E2u h\u1ECFi... (vd: Paris is the capital of France.)", className: cn('min-h-[100px] border-gray-200 bg-white transition-all focus:border-teal-300 focus:ring-teal-200', validationErrors.question && 'border-red-400 focus:border-red-500 focus:ring-red-200'), rows: 3 }), validationErrors.question && (_jsx("p", { className: "text-sm text-red-600", children: validationErrors.question }))] }), _jsxs("div", { className: "space-y-3", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(HelpCircle, { className: "h-4 w-4 text-emerald-500" }), "\u0110\u00E1p \u00E1n ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("button", { type: "button", onClick: () => {
|
|
229
|
+
setAnswer(true);
|
|
230
|
+
if (validationErrors.answer) {
|
|
231
|
+
setValidationErrors((prev) => ({ ...prev, answer: undefined }));
|
|
221
232
|
}
|
|
222
|
-
},
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
}, className: cn('group flex items-center justify-center gap-2 rounded-lg border-2 px-4 py-2.5 text-center transition-all', answer === false
|
|
237
|
-
? 'border-red-500 bg-red-50 shadow-sm ring-1 ring-red-100'
|
|
238
|
-
: 'border-gray-200 bg-white hover:border-red-300 hover:bg-red-50/50'), children: [_jsx("div", { className: cn('flex h-7 w-7 items-center justify-center rounded-full transition-all', answer === false
|
|
239
|
-
? 'bg-red-500 text-white'
|
|
240
|
-
: 'bg-gray-100 text-gray-500 group-hover:bg-red-100 group-hover:text-red-600'), children: _jsx(X, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: cn('text-sm font-semibold', answer === false ? 'text-red-700' : 'text-gray-600'), children: "Sai (False)" })] })] }), validationErrors.answer && (_jsx("p", { className: "text-sm text-red-600", children: validationErrors.answer }))] }), externalErrors && externalErrors.length > 0 && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-red-100", children: _jsx("span", { className: "text-lg", children: "\u274C" }) }), _jsxs("div", { className: "text-sm", children: [_jsx("p", { className: "font-semibold text-red-800", children: "L\u1ED7i:" }), _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: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", 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-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-3.5 w-3.5 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-base 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-xs 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, { className: "px-4 pb-4", children: _jsx(Textarea, { id: "explanation", 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" }) })] })] }));
|
|
233
|
+
}, className: cn('group flex items-center justify-center gap-2 rounded-lg border-2 px-4 py-2.5 text-center transition-all', answer === true
|
|
234
|
+
? 'border-emerald-500 bg-emerald-50 shadow-sm ring-1 ring-emerald-100'
|
|
235
|
+
: 'border-gray-200 bg-white hover:border-emerald-300 hover:bg-emerald-50/50'), children: [_jsx("div", { className: cn('flex h-7 w-7 items-center justify-center rounded-full transition-all', answer === true
|
|
236
|
+
? 'bg-emerald-500 text-white'
|
|
237
|
+
: 'bg-gray-100 text-gray-500 group-hover:bg-emerald-100 group-hover:text-emerald-600'), children: _jsx(Check, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: cn('text-sm font-semibold', answer === true ? 'text-emerald-700' : 'text-gray-600'), children: "\u0110\u00FAng (True)" })] }), _jsxs("button", { type: "button", onClick: () => {
|
|
238
|
+
setAnswer(false);
|
|
239
|
+
if (validationErrors.answer) {
|
|
240
|
+
setValidationErrors((prev) => ({ ...prev, answer: undefined }));
|
|
241
|
+
}
|
|
242
|
+
}, className: cn('group flex items-center justify-center gap-2 rounded-lg border-2 px-4 py-2.5 text-center transition-all', answer === false
|
|
243
|
+
? 'border-red-500 bg-red-50 shadow-sm ring-1 ring-red-100'
|
|
244
|
+
: 'border-gray-200 bg-white hover:border-red-300 hover:bg-red-50/50'), children: [_jsx("div", { className: cn('flex h-7 w-7 items-center justify-center rounded-full transition-all', answer === false
|
|
245
|
+
? 'bg-red-500 text-white'
|
|
246
|
+
: 'bg-gray-100 text-gray-500 group-hover:bg-red-100 group-hover:text-red-600'), children: _jsx(X, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: cn('text-sm font-semibold', answer === false ? 'text-red-700' : 'text-gray-600'), children: "Sai (False)" })] })] }), validationErrors.answer && (_jsx("p", { className: "text-sm text-red-600", children: validationErrors.answer }))] }), externalErrors && externalErrors.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-red-200 bg-red-50 p-2 text-xs text-red-700", children: [_jsx("span", { className: "font-semibold text-red-800", children: "L\u1ED7i:" }), _jsx("span", { children: externalErrors.join(' · ') })] }))] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation })] }));
|
|
241
247
|
}
|
|
242
248
|
export function TrueFalseCreator(props) {
|
|
243
249
|
return _jsx(TrueFalseCreatorContent, { ...props });
|
package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js
CHANGED
|
@@ -7,7 +7,7 @@ export function TrueFalseCorrectGroupClient({ questionData, isReviewMode = false
|
|
|
7
7
|
return (_jsxs("div", { className: "space-y-4", children: [questionData.instruction && (_jsx("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800", children: questionData.instruction })), items.map((item, itemIndex) => {
|
|
8
8
|
const userValue = userAnswers[itemIndex];
|
|
9
9
|
const correctIsTrue = item.isTrue === true;
|
|
10
|
-
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-
|
|
10
|
+
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-3 py-2", 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-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || itemIndex + 1] }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: item.question || 'Câu hỏi chưa được nhập' }), _jsx("div", { className: "flex items-center gap-3", children: [
|
|
11
11
|
{ value: true, label: 'True' },
|
|
12
12
|
{ value: false, label: 'False' },
|
|
13
13
|
].map((opt) => {
|
package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupCreator.js
CHANGED
|
@@ -9,7 +9,8 @@ import { PointsInput } from '../../../../components/ui/points-input';
|
|
|
9
9
|
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
10
10
|
import { Switch } from '../../../../components/ui/switch';
|
|
11
11
|
import { useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
12
|
-
import {
|
|
12
|
+
import { HelpCircle, Plus, Star, Trash2 } from 'lucide-react';
|
|
13
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
13
14
|
import { TrueFalseCorrectGroupClient } from './TrueFalseCorrectGroupClient';
|
|
14
15
|
import { TRUE_FALSE_CORRECT_GROUP_DEFAULT_INSTRUCTION } from '../../_shared/types/true-false-correct-group.type';
|
|
15
16
|
function parsePointsValue(raw, allowZero = false) {
|
|
@@ -135,10 +136,13 @@ export function TrueFalseCorrectGroupCreator({ initialData, onChange, externalEr
|
|
|
135
136
|
};
|
|
136
137
|
const currentPayload = buildPayload();
|
|
137
138
|
const displayErrors = externalErrors && externalErrors.length > 0 ? externalErrors : errors;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
if (isClientMode) {
|
|
140
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(TrueFalseCorrectGroupClient, { questionData: currentPayload })] }));
|
|
141
|
+
}
|
|
142
|
+
return (_jsxs("div", { className: "space-y-3", children: [displayErrors.length > 0 && (_jsx("div", { className: "rounded-md border border-red-200 bg-red-50 p-2 text-xs text-red-700", children: _jsx("ul", { className: "list-disc space-y-1 pl-4", children: displayErrors.map((err, i) => (_jsx("li", { children: err }, i))) }) })), _jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(HelpCircle, { className: "h-3.5 w-3.5" }), title: "\u0110\u00FAng / Sai + S\u1EEDa c\u00E2u (Nh\u00F3m)", preview: { onClick: () => setIsClientMode(true) } }), children: _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "instruction", className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn b\u00E0i l\u00E0m" }), _jsx(Input, { id: "instruction", value: instruction, onChange: (e) => setInstruction(e.target.value), placeholder: TRUE_FALSE_CORRECT_GROUP_DEFAULT_INSTRUCTION, className: "h-10 border-gray-200 bg-white" })] }) }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-sm", children: [_jsx(CardHeader, { className: "px-3 py-2", children: _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs(CardTitle, { className: "text-sm font-semibold text-gray-800", children: ["Danh s\u00E1ch c\u00E2u h\u1ECFi (", items.length, ")"] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addItem, className: "h-8 gap-1.5 px-2.5 text-xs", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm c\u00E2u h\u1ECFi"] })] }) }), _jsxs(CardContent, { className: "space-y-2.5 px-3 pb-3", children: [items.map((item, index) => (_jsxs("div", { className: "space-y-2.5 rounded-md border border-gray-200 bg-white px-3 py-2.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-blue-100 text-xs font-semibold text-blue-700", children: item.questionNumber || index + 1 }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u h\u1ECFi ", index + 1] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: `example-${index}`, checked: item.isExample || false, onCheckedChange: (checked) => updateItem(index, {
|
|
143
|
+
isExample: checked,
|
|
144
|
+
points: checked ? 0 : 1,
|
|
145
|
+
}) }), _jsxs(Label, { htmlFor: `example-${index}`, className: "flex cursor-pointer items-center gap-1 text-xs", children: [_jsx(Star, { className: "h-3 w-3 text-amber-500" }), "C\u00E2u m\u1EABu (Example)"] })] }), !item.isExample && (_jsx("div", { className: "w-24", children: _jsx(PointsInput, { value: item.points, allowZero: true, onChange: (event) => updateItem(index, {
|
|
146
|
+
points: parsePointsValue(event.target.value, true),
|
|
147
|
+
}) }) })), items.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeItem(index), className: "h-7 w-7 text-red-500 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: `question-${index}`, children: "N\u1ED9i dung c\u00E2u h\u1ECFi" }), _jsx(Textarea, { id: `question-${index}`, value: item.question, onChange: (e) => updateItem(index, { question: e.target.value }), placeholder: "Nh\u1EADp n\u1ED9i dung c\u00E2u h\u1ECFi...", className: "mt-1 min-h-[56px] border-gray-200 bg-white", rows: 2 })] }), _jsxs("div", { children: [_jsx(Label, { className: "mb-1.5 block text-xs font-medium text-gray-700", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: item.isTrue === true ? 'default' : 'outline', size: "sm", onClick: () => setItemTruth(index, true), className: "h-8 w-24", children: "True" }), _jsx(Button, { type: "button", variant: item.isTrue === false ? 'default' : 'outline', size: "sm", onClick: () => setItemTruth(index, false), className: "h-8 w-24", children: "False" })] })] }), item.isTrue === false && (_jsxs("div", { children: [_jsx(Label, { htmlFor: `correction-${index}`, children: "C\u00E2u s\u1EEDa \u0111\u00FAng" }), _jsx(Textarea, { id: `correction-${index}`, value: item.correction, onChange: (e) => updateItem(index, { correction: e.target.value }), placeholder: "Vi\u1EBFt l\u1EA1i c\u00E2u \u0111\u00FAng...", className: "mt-1 min-h-[56px] border-gray-200 bg-white", rows: 2 })] }))] }, index))), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) })] })] })] }));
|
|
144
148
|
}
|