@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
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
5
4
|
import { Label } from '../../../../components/ui/label';
|
|
6
5
|
import { Button } from '../../../../components/ui/button';
|
|
7
6
|
import { Input } from '../../../../components/ui/input';
|
|
8
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
9
7
|
import { Form, FormControl, FormField, FormItem, FormMessage } from '../../../../components/ui/form';
|
|
10
8
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
11
|
-
import {
|
|
9
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
12
10
|
// Stores & hooks
|
|
13
11
|
import { useCompareImagesGroup } from '../../_shared/hooks/useCompareImagesGroup';
|
|
14
12
|
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
15
13
|
import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
16
14
|
import { listenAndSpeakCompareImagesSchema } from '../../../../shared/lib/validation/listen-and-speak-compare-images.validation';
|
|
17
15
|
// Icons
|
|
18
|
-
import { Plus, Trash2, Mic, Volume2, ImageIcon, MessageSquareText, ListChecks,
|
|
19
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
16
|
+
import { Plus, Trash2, Mic, Volume2, ImageIcon, MessageSquareText, ListChecks, Info } from 'lucide-react';
|
|
20
17
|
import { ListenAndSpeakCompareImagesClient } from './ListenAndSpeakCompareImagesClient';
|
|
21
18
|
// ─── Sub-component: Image preview with presigned URL ─────────────────────────
|
|
22
19
|
function ImagePreview({ imageUrl, label }) {
|
|
@@ -281,15 +278,14 @@ export function ListenAndSpeakCompareImagesCreator({ questionIndex, initialData,
|
|
|
281
278
|
candidateImageUrl: candidateImageUrl || '',
|
|
282
279
|
examinerImageUrl: examinerImageUrl || '',
|
|
283
280
|
};
|
|
284
|
-
return (_jsxs("div", { className: "space-y-
|
|
281
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(ListenAndSpeakCompareImagesClient, { questionData: questionData, isReviewMode: true })] }));
|
|
285
282
|
}
|
|
286
|
-
return (_jsxs("div", { className: "space-y-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow shadow-amber-100/30", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-amber-400 to-orange-400" }), _jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-amber-100", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5 text-amber-600" }) }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "H\u00ECnh \u1EA3nh so s\u00E1nh" }), _jsxs("span", { className: "inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-500", children: ["Nh\u00F3m ", displayGroupNumber, " \u2014 Ch\u1EC9 \u0111\u1ECDc"] })] }), _jsx("p", { className: "text-xs text-gray-400 mt-0.5", children: "\u0110\u01B0\u1EE3c qu\u1EA3n l\u00FD b\u1EDFi c\u00E2u h\u1ECFi \u0111\u1EA7u ti\u00EAn trong nh\u00F3m" })] })] }) }), _jsx(CardContent, { className: "pt-0", children: candidateImageUrl || examinerImageUrl ? (_jsxs("div", { className: "space-y-4", 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(ImagePreview, { 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(ImagePreview, { imageUrl: examinerImageUrl, label: "Examiner's Copy" })] }))] })) : (_jsx("div", { className: "text-center py-4 text-sm text-gray-400 italic", children: "Ch\u01B0a c\u00F3 h\u00ECnh \u1EA3nh n\u00E0o trong nh\u00F3m" })) })] })), _jsxs(Form, { ...form, children: [_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-indigo-400 to-purple-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 shadow-sm", children: _jsx(Mic, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "C\u00E2u h\u1ECFi Nghe & So s\u00E1nh" }), _jsx("p", { className: "text-sm text-gray-500", children: "H\u1ECDc sinh nghe \u00E2m thanh v\u00E0 n\u00F3i \u0111i\u1EC3m kh\u00E1c bi\u1EC7t gi\u1EEFa hai h\u00ECnh \u1EA3nh" })] })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsx("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: _jsxs("div", { className: "flex flex-col md:flex-row gap-6", children: [_jsx("div", { className: "flex-1", children: _jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), error: form.formState.errors?.points?.message }) }), _jsx("div", { className: "flex-1 flex flex-col justify-center pt-2" })] }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: [_jsx(Volume2, { className: "h-4 w-4 text-indigo-500" }), "\u00C2m thanh c\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FormField, { control: control, name: "audioUrl", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormControl, { children: _jsx(FileUpload, { id: `audio-compare-${displayQuestionNumber}`, label: "", accept: "audio/*", value: field.value, onChange: (url) => { field.onChange(url); clearErrors('audioUrl'); }, maxSize: 10, placeholder: "T\u1EA3i l\u00EAn file \u00E2m thanh (mp3, wav...)", autoUpload: true, prefix: "listen-and-speak" }) }), _jsx(FormMessage, {})] })) })] })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-green-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-green-400 to-emerald-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", 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-green-500 to-emerald-600 shadow-sm", children: _jsx(MessageSquareText, { className: "h-4 w-4 text-white" }) }), _jsx("div", { children: _jsxs(CardTitle, { className: "text-lg font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n mong mu\u1ED1n (", watchedExpectedAnswers.filter((a) => a.trim()).length, ")"] }) })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addExpectedAnswer, 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"] })] }) }), _jsxs(CardContent, { className: "space-y-4", 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, { placeholder: `VD: ${index === 0 ? 'The tree is bigger in picture A' : 'The dog is brown in picture B'}`, value: answer || '', onChange: (e) => handleExpectedAnswerChange(index, e.target.value), 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, { type: "button", variant: "ghost", size: "sm", onClick: () => removeExpectedAnswer(index), 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 }))] })] })] }), _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, { ...form.register('explanation'), placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0... v\u00EC...", className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none", rows: 3 }) })] })] })] }));
|
|
283
|
+
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 ", displayGroupNumber] }), points: {
|
|
284
|
+
value: watchedPoints,
|
|
285
|
+
onValueChange: (value) => form.setValue('points', value, { shouldValidate: true }),
|
|
286
|
+
error: form.formState.errors?.points?.message,
|
|
287
|
+
}, preview: {
|
|
288
|
+
onClick: () => setIsClientMode(true),
|
|
289
|
+
disabled: !watchedAudioUrl || watchedExpectedAnswers.filter((a) => a.trim()).length === 0,
|
|
290
|
+
} }), contentClassName: "hidden", children: null }), isFirstInGroup ? (_jsx(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 shrink-0 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 so s\u00E1nh" })] }), children: _jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: ["\uD83D\uDCF7 Candidate's Copy Image ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: `candidate-img-group-${displayGroupNumber}`, label: "", accept: "image/*", value: candidateImageUrl || '', onChange: (url) => updateCandidateImage(url), maxSize: 5, placeholder: "Upload Candidate's Copy", autoUpload: true, prefix: "questions/compare-images" }), candidateImageUrl && (_jsx(ImagePreview, { imageUrl: candidateImageUrl, label: "Candidate's Copy" })), !candidateImageUrl && (_jsxs("div", { className: `flex flex-col items-center justify-center rounded-xl border-2 border-dashed ${groupImageErrors.candidate ? 'border-red-300 bg-red-50/50' : 'border-amber-200 bg-amber-50/50'} py-6 px-4 text-center`, children: [_jsx(ImageIcon, { className: `h-8 w-8 ${groupImageErrors.candidate ? 'text-red-400' : 'text-amber-400'} mb-2` }), _jsx("p", { className: "text-xs text-gray-500", children: "H\u00ECnh \u1EA3nh d\u00E0nh cho th\u00ED sinh" })] })), groupImageErrors.candidate && (_jsx("p", { className: "text-sm font-medium text-destructive", children: groupImageErrors.candidate }))] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: ["\uD83D\uDCF7 Examiner's Copy Image ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: `examiner-img-group-${displayGroupNumber}`, label: "", accept: "image/*", value: examinerImageUrl || '', onChange: (url) => updateExaminerImage(url), maxSize: 5, placeholder: "Upload Examiner's Copy", autoUpload: true, prefix: "questions/compare-images" }), examinerImageUrl && (_jsx(ImagePreview, { imageUrl: examinerImageUrl, label: "Examiner's Copy" })), !examinerImageUrl && (_jsxs("div", { className: `flex flex-col items-center justify-center rounded-xl border-2 border-dashed ${groupImageErrors.examiner ? 'border-red-300 bg-red-50/50' : 'border-orange-200 bg-orange-50/50'} py-6 px-4 text-center`, children: [_jsx(ImageIcon, { className: `h-8 w-8 ${groupImageErrors.examiner ? 'text-red-400' : 'text-orange-400'} mb-2` }), _jsx("p", { className: "text-xs text-gray-500", children: "H\u00ECnh \u1EA3nh d\u00E0nh cho gi\u00E1m kh\u1EA3o" })] })), groupImageErrors.examiner && (_jsx("p", { className: "text-sm font-medium text-destructive", children: groupImageErrors.examiner }))] })] }) })) : (_jsx(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-gray-100", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("p", { className: "text-sm font-semibold text-gray-800", children: "H\u00ECnh \u1EA3nh so s\u00E1nh" }), _jsxs("span", { className: "inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-500", children: ["Nh\u00F3m ", displayGroupNumber, " \u2014 Ch\u1EC9 \u0111\u1ECDc"] })] }), _jsx("p", { className: "text-xs text-gray-400 mt-0.5", children: "\u0110\u01B0\u1EE3c qu\u1EA3n l\u00FD b\u1EDFi c\u00E2u h\u1ECFi \u0111\u1EA7u ti\u00EAn trong nh\u00F3m" })] })] }), children: candidateImageUrl || examinerImageUrl ? (_jsxs("div", { className: "space-y-4", 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(ImagePreview, { 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(ImagePreview, { imageUrl: examinerImageUrl, label: "Examiner's Copy" })] }))] })) : (_jsx("div", { className: "text-center py-4 text-sm text-gray-400 italic", children: "Ch\u01B0a c\u00F3 h\u00ECnh \u1EA3nh n\u00E0o trong nh\u00F3m" })) })), _jsxs(Form, { ...form, children: [_jsx(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 shrink-0 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: "C\u00E2u h\u1ECFi Nghe & So s\u00E1nh" })] }), children: _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: [_jsx(Volume2, { className: "h-4 w-4 text-indigo-500" }), "\u00C2m thanh c\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FormField, { control: control, name: "audioUrl", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormControl, { children: _jsx(FileUpload, { id: `audio-compare-${displayQuestionNumber}`, label: "", accept: "audio/*", value: field.value, onChange: (url) => { field.onChange(url); clearErrors('audioUrl'); }, maxSize: 10, placeholder: "T\u1EA3i l\u00EAn file \u00E2m thanh (mp3, wav...)", autoUpload: true, prefix: "listen-and-speak" }) }), _jsx(FormMessage, {})] })) })] }) }), _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, { type: "button", variant: "outline", size: "sm", onClick: addExpectedAnswer, 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, { placeholder: `VD: ${index === 0 ? 'The tree is bigger in picture A' : 'The dog is brown in picture B'}`, value: answer || '', onChange: (e) => handleExpectedAnswerChange(index, e.target.value), 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, { type: "button", variant: "ghost", size: "sm", onClick: () => removeExpectedAnswer(index), 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) })] })] }));
|
|
295
291
|
}
|
|
@@ -18,7 +18,7 @@ function ImageWithPresigned({ url, alt }) {
|
|
|
18
18
|
// ─── Main Client Component ───────────────────────────────────────────────────
|
|
19
19
|
export function ListenAndSpeakImageGroupClient({ questionData, isReviewMode = false, }) {
|
|
20
20
|
const { audioUrl, activeImageId, groupImages = [], expectedAnswers = [], explanation } = questionData;
|
|
21
|
-
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-
|
|
21
|
+
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(Images, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 n\u00F3i (Nh\u00F3m 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" })] })), groupImages.length > 0 ? (_jsx("div", { className: "rounded-xl border border-gray-100 bg-gradient-to-b from-gray-50/80 to-white p-5", children: _jsx("div", { className: "flex flex-row justify-center gap-4 sm:gap-5 flex-wrap", children: groupImages.map((img, index) => {
|
|
22
22
|
const isActive = img.id === activeImageId;
|
|
23
23
|
const letter = String.fromCharCode(65 + index);
|
|
24
24
|
return (_jsxs("div", { className: cn('flex flex-col items-center gap-2 transition-transform duration-300', isActive && 'scale-[1.03]'), children: [_jsxs("div", { className: cn('relative w-28 h-28 sm:w-36 sm:h-36 rounded-xl overflow-hidden flex items-center justify-center bg-white transition-all duration-300', isActive
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
5
4
|
import { Label } from '../../../../components/ui/label';
|
|
6
5
|
import { Button } from '../../../../components/ui/button';
|
|
7
6
|
import { Input } from '../../../../components/ui/input';
|
|
8
|
-
import {
|
|
9
|
-
import { Plus, Trash2, Pencil, Eye, Mic, Volume2, ImageIcon, Image as ImageIcon2, CheckCircle2, MessageSquareText, Info, ListChecks, BookOpen } from 'lucide-react';
|
|
7
|
+
import { Plus, Trash2, Mic, Volume2, ImageIcon, Image as ImageIcon2, CheckCircle2, MessageSquareText, Info, ListChecks } from 'lucide-react';
|
|
10
8
|
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
11
9
|
import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
12
10
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
13
|
-
import {
|
|
11
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
14
12
|
// Stores & hooks
|
|
15
13
|
import { useImageQuestionGroup } from '../../_shared/hooks/useImageQuestionGroup';
|
|
16
14
|
import { generateObjectId } from '../../_shared/utils/id-generator';
|
|
@@ -18,7 +16,6 @@ import { generateObjectId } from '../../_shared/utils/id-generator';
|
|
|
18
16
|
import { ListenAndSpeakImageGroupClient } from './ListenAndSpeakImageGroupClient';
|
|
19
17
|
// Validation & Types
|
|
20
18
|
import { listenAndSpeakImageGroupFormSchema } from '../../../../shared/lib/validation/listen-and-speak-image-group.validation';
|
|
21
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
22
19
|
// ─── Sub-component: Image preview with presigned URL ─────────────────────────
|
|
23
20
|
function ImagePreview({ imageUrl }) {
|
|
24
21
|
const { previewUrl } = usePresignedFileUrl(imageUrl);
|
|
@@ -261,25 +258,26 @@ export function ListenAndSpeakImageGroupCreator({ initialData, onSave, onCancel,
|
|
|
261
258
|
groupImages: images,
|
|
262
259
|
groupNumber: isGrouped ? currentGroupNumber : undefined,
|
|
263
260
|
};
|
|
264
|
-
return (_jsxs("div", { className: "space-y-
|
|
261
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(ListenAndSpeakImageGroupClient, { questionData: questionData, isReviewMode: true })] }));
|
|
265
262
|
}
|
|
266
263
|
// ─── Creator UI ────────────────────────────────────────────────────────────
|
|
267
|
-
return (_jsxs("div", { className: "space-y-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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 }), _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(ImageIcon2, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Nh\u00F3m H\u00ECnh \u1EA2nh" })] }), images.length < 4 && (_jsxs(Button, { variant: "outline", size: "sm", onClick: handleAddImage, 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 h\u00ECnh \u1EA3nh"] }))] }), children: [_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: images.map((img, index) => (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-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-blue-500 to-indigo-600 text-white text-sm font-bold shadow-sm", children: String.fromCharCode(65 + index) }), _jsxs("span", { className: "text-sm font-semibold text-gray-700", children: ["H\u00ECnh \u1EA3nh ", String.fromCharCode(65 + index)] })] }), images.length > 1 && (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => handleRemoveImage(index), className: "h-7 w-7 p-0 text-red-500 hover:text-red-700 hover:bg-red-50", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }), _jsx(FileUpload, { id: `group-img-${img.id}`, label: "", accept: "image/*", value: img.url, onChange: (url) => handleUpdateImage(index, 'url', url), maxSize: 5, placeholder: "Upload h\u00ECnh \u1EA3nh", autoUpload: true, prefix: "questions/images" }), img.url && _jsx(ImagePreview, { imageUrl: img.url }), _jsxs("div", { className: "mt-3", children: [_jsx(Label, { className: "text-xs font-medium text-gray-600", children: "Nh\u00E3n" }), _jsx(Input, { value: img.label, onChange: (e) => handleUpdateImage(index, 'label', e.target.value), placeholder: "Nh\u1EADp nh\u00E3n / m\u00F4 t\u1EA3 ng\u1EAFn", className: "mt-1 border-gray-200" })] })] }, img.id))) }), images.length === 0 && (_jsxs("div", { className: "flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-indigo-300 p-8 bg-indigo-50/50", children: [_jsx(ImageIcon2, { className: "h-10 w-10 text-indigo-400 mb-2" }), _jsx("p", { className: "text-sm text-gray-500 mb-3", children: "Ch\u01B0a c\u00F3 h\u00ECnh \u1EA3nh n\u00E0o" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: handleAddImage, className: "gap-2", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm h\u00ECnh \u1EA3nh \u0111\u1EA7u ti\u00EAn"] })] }))] }), _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(ImageIcon, { className: "h-4 w-4 text-indigo-500" }), "Ch\u1ECDn h\u00ECnh \u1EA3nh \u0111ang \u0111\u01B0\u1EE3c h\u1ECFi"] }), images.filter((o) => o.url).length > 0 ? (_jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-4", children: images.map((img, index) => {
|
|
272
|
+
if (!img.url)
|
|
273
|
+
return null;
|
|
274
|
+
const isSelected = watchedActiveImageId === img.id;
|
|
275
|
+
return (_jsxs("button", { type: "button", onClick: () => {
|
|
276
|
+
form.setValue('activeImageId', img.id, { shouldValidate: true, shouldDirty: true });
|
|
277
|
+
}, className: `relative text-left rounded-xl border-2 p-2 transition-all ${isSelected
|
|
278
|
+
? 'border-indigo-500 bg-indigo-50 ring-2 ring-indigo-200 shadow-md'
|
|
279
|
+
: 'border-gray-200 hover:border-gray-400 hover:shadow-sm'}`, children: [_jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx("div", { className: `flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg text-sm font-bold shadow-sm ${isSelected
|
|
280
|
+
? 'bg-indigo-500 text-white'
|
|
281
|
+
: 'bg-gray-100 text-gray-600'}`, children: String.fromCharCode(65 + index) }), _jsx(ImagePreviewInline, { imageUrl: img.url }), img.label && (_jsx("p", { className: `text-xs text-center ${isSelected ? 'text-indigo-700 font-medium' : 'text-gray-600'}`, children: img.label }))] }), isSelected && (_jsx("div", { className: "absolute top-1 right-1", children: _jsx(CheckCircle2, { className: "h-5 w-5 text-indigo-500" }) }))] }, img.id));
|
|
282
|
+
}) })) : (_jsx("p", { className: "text-sm text-amber-600 italic", children: "Vui l\u00F2ng upload \u1EA3nh cho h\u00ECnh \u1EA3nh \u1EDF ph\u1EA7n tr\u00EAn tr\u01B0\u1EDBc" })), form.hasError('activeImageId') && (_jsx("div", { className: "mt-2 rounded-lg border border-red-200 bg-red-50 p-3", children: _jsxs("p", { className: "text-sm text-red-700", children: ["\u26A0\uFE0F ", form.getFieldError('activeImageId')] }) })), !form.hasError('activeImageId') && !watchedActiveImageId && images.filter((o) => o.url).length > 0 && (_jsx("p", { className: "mt-1 text-xs text-amber-600", children: "Ch\u01B0a ch\u1ECDn h\u00ECnh \u1EA3nh \u0111ang \u0111\u01B0\u1EE3c h\u1ECFi" }))] }), _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: "question-audio", label: "", accept: "audio/*", value: watchedAudioUrl, onChange: (url) => form.setValue('audioUrl', url, { shouldValidate: true, shouldDirty: true }), maxSize: 20, placeholder: "T\u1EA3i l\u00EAn file \u00E2m thanh (mp3, wav...)", autoUpload: true, prefix: "questions/audio" }), 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: addExpectedAnswer, 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) => updateExpectedAnswer(index, e.target.value), placeholder: `Ví dụ: ${index === 0 ? 'A kite' : 'It is a kite'}`, 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: () => removeExpectedAnswer(index), 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) })] }));
|
|
285
283
|
}
|
|
@@ -32,7 +32,7 @@ export function ListenAndSpeakInfoExchangeClient({ questionData, isReviewMode =
|
|
|
32
32
|
.map((f) => ({ key: f.key, value: f.answer }));
|
|
33
33
|
const examinerRows = (qData.examinerFields || [])
|
|
34
34
|
.map((f) => ({ key: f.key, value: f.answer }));
|
|
35
|
-
return (_jsx("div", { className: "space-y-4", children: _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-
|
|
35
|
+
return (_jsx("div", { className: "space-y-4", children: _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(ArrowLeftRight, { className: "h-4 w-4" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["Nghe v\u00E0 n\u00F3i (Trao \u0111\u1ED5i th\u00F4ng tin \u2014 ", viewMode === 'ANSWER' ? 'Trả lời' : 'Đặt câu hỏi', ")"] })] }) }), _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" })] })), _jsxs("div", { className: "flex flex-col lg:flex-row gap-4", children: [imageUrl && (_jsx("div", { className: "lg:w-1/2", children: _jsx(ImagePreview, { imageUrl: imageUrl, title: title }) })), _jsxs("div", { className: "flex-1 space-y-4", children: [_jsx(InfoTable, { label: "CANDIDATE'S COPY", title: title, rows: candidateRows, headerColor: "bg-teal-600" }), _jsx(InfoTable, { label: "EXAMINER'S COPY", title: title, rows: examinerRows, headerColor: "bg-amber-600" })] })] }), _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: viewMode === 'ANSWER'
|
|
36
36
|
? 'Học sinh sẽ nhấn nút ghi âm và nói câu trả lời tại đây'
|
|
37
37
|
: 'Học sinh sẽ nhấn nút ghi âm và đặt câu hỏi tại đây' })] }), 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: viewMode === 'ANSWER' ? 'Các đáp án mong muốn' : 'Các câu hỏi mong muốn' })] }), _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 })] }))] })] }) }));
|
|
38
38
|
}
|
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
5
4
|
import { Label } from '../../../../components/ui/label';
|
|
6
5
|
import { Button } from '../../../../components/ui/button';
|
|
7
6
|
import { Input } from '../../../../components/ui/input';
|
|
8
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
9
7
|
import { Form, FormControl, FormField, FormItem, FormMessage } from '../../../../components/ui/form';
|
|
10
8
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
11
|
-
import {
|
|
9
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
12
10
|
// Stores & hooks
|
|
13
11
|
import { useInfoExchangeGroup } from '../../_shared/hooks/useInfoExchangeGroup';
|
|
14
12
|
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
15
13
|
import { listenAndSpeakInfoExchangeSchema, } from '../../../../shared/lib/validation/listen-and-speak-info-exchange.validation';
|
|
16
14
|
// Utilities & Icons
|
|
17
|
-
import { Plus, Trash2, Mic, Volume2, Table2, MessageSquareText, ImageIcon,
|
|
15
|
+
import { Plus, Trash2, Mic, Volume2, Table2, MessageSquareText, ImageIcon, Info, ListChecks } from 'lucide-react';
|
|
18
16
|
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
19
17
|
import { useDebouncedCallback } from '../../../../shared/lib/hooks/useDebouncedCallback';
|
|
20
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
21
18
|
import { ListenAndSpeakInfoExchangeClient } from './ListenAndSpeakInfoExchangeClient';
|
|
22
19
|
// ─── Sub-component: Image preview with presigned URL ─────────────────────────
|
|
23
20
|
function ImagePreview({ imageUrl, label }) {
|
|
@@ -352,7 +349,7 @@ export function ListenAndSpeakInfoExchangeCreator({ questionIndex, initialData,
|
|
|
352
349
|
const [isClientMode, setIsClientMode] = useState(false);
|
|
353
350
|
// ─── Preview Mode ────────────────────────────────────────────────
|
|
354
351
|
if (isClientMode) {
|
|
355
|
-
return (_jsxs("div", { className: "space-y-
|
|
352
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(ListenAndSpeakInfoExchangeClient, { questionData: {
|
|
356
353
|
audioUrl: watchedAudioUrl,
|
|
357
354
|
title: groupTitle,
|
|
358
355
|
imageUrl: groupImageUrl,
|
|
@@ -364,22 +361,18 @@ export function ListenAndSpeakInfoExchangeCreator({ questionIndex, initialData,
|
|
|
364
361
|
explanation: watchedExplanation,
|
|
365
362
|
}, viewMode: viewMode })] }));
|
|
366
363
|
}
|
|
367
|
-
return (_jsxs("div", { className: "space-y-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}, placeholder: "Upload h\u00ECnh \u1EA3nh", label: "", autoUpload: true, prefix: "info-exchange" }), groupImageUrl && (_jsx(ImagePreview, { imageUrl: groupImageUrl, label: "H\u00ECnh \u1EA3nh minh ho\u1EA1" }))] }), _jsxs("div", { className: "space-y-5", children: [_jsx("div", { className: "space-y-3", children: _jsxs("div", { className: "rounded-xl border border-teal-200 overflow-hidden shadow-sm bg-white", children: [_jsx("div", { className: "bg-gradient-to-r from-teal-600 to-emerald-600 px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-white/20 backdrop-blur-sm", children: _jsx(Table2, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx("h4", { className: "text-sm font-bold text-white tracking-wide", children: "Candidate's Copy" }), _jsx("p", { className: "text-[10px] text-teal-100 font-medium", children: "B\u1EA3ng th\u00F4ng tin h\u1ECDc sinh nh\u00ECn th\u1EA5y" })] })] }), _jsx("div", { className: "flex items-center gap-2", children: _jsxs("span", { className: "inline-flex items-center rounded-full bg-white/20 backdrop-blur-sm px-2 py-0.5 text-[10px] font-bold text-white", children: [groupCandidateFields.length, " d\u00F2ng"] }) })] }) }), _jsx("div", { className: "bg-teal-50/60 border-b border-teal-100 px-4 py-2", children: _jsx("p", { className: "text-xs font-semibold text-teal-800 text-center", children: groupTitle || '(Chưa nhập tiêu đề)' }) }), _jsxs("div", { className: "grid grid-cols-[40px_1fr_1fr_36px] bg-gradient-to-r from-teal-50/80 to-emerald-50/80 border-b border-teal-100", children: [_jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-teal-600 uppercase tracking-wider text-center", children: "#" }), _jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-teal-600 uppercase tracking-wider", children: "Key" }), _jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-teal-600 uppercase tracking-wider", children: "Value" }), _jsx("div", {})] }), _jsx("div", { className: "divide-y divide-gray-100", children: groupCandidateFields.map((field, index) => (_jsxs("div", { className: "grid grid-cols-[40px_1fr_1fr_36px] items-center bg-white hover:bg-teal-50/30 transition-colors group/row", children: [_jsx("div", { className: "px-2 py-2 flex justify-center", children: _jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-teal-100 text-teal-700 font-bold text-[11px]", children: index + 1 }) }), _jsx("div", { className: "px-1.5 py-1.5", children: _jsx(Input, { value: field.key, onChange: (e) => handleUpdateCandidateField(index, 'key', e.target.value), placeholder: "VD: Where", className: "h-8 text-sm border-gray-200 bg-white focus:border-teal-400 focus:ring-teal-200 transition-all" }) }), _jsx("div", { className: "px-1.5 py-1.5", children: _jsx(Input, { value: field.answer, onChange: (e) => handleUpdateCandidateField(index, 'answer', e.target.value), placeholder: "VD: King Road", className: "h-8 text-sm border-gray-200 bg-white focus:border-teal-400 focus:ring-teal-200 transition-all" }) }), _jsx("div", { className: "px-1 py-1.5 flex justify-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => handleRemoveCandidateField(index), disabled: groupCandidateFields.length <= 1, className: "h-7 w-7 text-gray-300 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/row:opacity-100 transition-all disabled:opacity-0 disabled:group-hover/row:opacity-30", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }) })] }, index))) }), _jsx("div", { className: "border-t border-dashed border-teal-200 bg-teal-50/30", children: _jsxs("button", { type: "button", onClick: handleAddCandidateField, className: "w-full flex items-center justify-center gap-1.5 py-2.5 text-xs font-semibold text-teal-600 hover:text-teal-700 hover:bg-teal-50 transition-colors cursor-pointer", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm d\u00F2ng"] }) })] }) }), _jsx("div", { className: "space-y-3", children: _jsxs("div", { className: "rounded-xl border border-amber-200 overflow-hidden shadow-sm bg-white", children: [_jsx("div", { className: "bg-gradient-to-r from-amber-600 to-orange-600 px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-white/20 backdrop-blur-sm", children: _jsx(Table2, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx("h4", { className: "text-sm font-bold text-white tracking-wide", children: "Examiner's Copy" }), _jsx("p", { className: "text-[10px] text-amber-100 font-medium", children: "B\u1EA3ng th\u00F4ng tin gi\u00E1m kh\u1EA3o nh\u00ECn th\u1EA5y" })] })] }), _jsx("div", { className: "flex items-center gap-2", children: _jsxs("span", { className: "inline-flex items-center rounded-full bg-white/20 backdrop-blur-sm px-2 py-0.5 text-[10px] font-bold text-white", children: [groupExaminerFields.length, " d\u00F2ng"] }) })] }) }), _jsx("div", { className: "bg-amber-50/60 border-b border-amber-100 px-4 py-2", children: _jsx("p", { className: "text-xs font-semibold text-amber-800 text-center", children: groupTitle || '(Chưa nhập tiêu đề)' }) }), _jsxs("div", { className: "grid grid-cols-[40px_1fr_1fr_36px] bg-gradient-to-r from-amber-50/80 to-orange-50/80 border-b border-amber-100", children: [_jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-amber-600 uppercase tracking-wider text-center", children: "#" }), _jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-amber-600 uppercase tracking-wider", children: "Key" }), _jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-amber-600 uppercase tracking-wider", children: "Value" }), _jsx("div", {})] }), _jsx("div", { className: "divide-y divide-gray-100", children: groupExaminerFields.map((field, index) => (_jsxs("div", { className: "grid grid-cols-[40px_1fr_1fr_36px] items-center bg-white hover:bg-amber-50/30 transition-colors group/row", children: [_jsx("div", { className: "px-2 py-2 flex justify-center", children: _jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-amber-100 text-amber-700 font-bold text-[11px]", children: index + 1 }) }), _jsx("div", { className: "px-1.5 py-1.5", children: _jsx(Input, { value: field.key, onChange: (e) => handleUpdateExaminerField(index, 'key', e.target.value), placeholder: "VD: Where", className: "h-8 text-sm border-gray-200 bg-white focus:border-amber-400 focus:ring-amber-200 transition-all" }) }), _jsx("div", { className: "px-1.5 py-1.5", children: _jsx(Input, { value: field.answer, onChange: (e) => handleUpdateExaminerField(index, 'answer', e.target.value), placeholder: "VD: King Road", className: "h-8 text-sm border-gray-200 bg-white focus:border-amber-400 focus:ring-amber-200 transition-all" }) }), _jsx("div", { className: "px-1 py-1.5 flex justify-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => handleRemoveExaminerField(index), disabled: groupExaminerFields.length <= 1, className: "h-7 w-7 text-gray-300 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/row:opacity-100 transition-all disabled:opacity-0 disabled:group-hover/row:opacity-30", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }) })] }, index))) }), _jsx("div", { className: "border-t border-dashed border-amber-200 bg-amber-50/30", children: _jsxs("button", { type: "button", onClick: handleAddExaminerField, className: "w-full flex items-center justify-center gap-1.5 py-2.5 text-xs font-semibold text-amber-600 hover:text-amber-700 hover:bg-amber-50 transition-colors cursor-pointer", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm d\u00F2ng"] }) })] }) })] })] })] })) : (
|
|
381
|
-
/* Read-only view of the shared group data for non-first questions */
|
|
382
|
-
_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow shadow-teal-100/30", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-teal-400 via-emerald-400 to-cyan-400" }), _jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-teal-100 shadow-sm", children: _jsx(Table2, { className: "h-4 w-4 text-teal-600" }) }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-base font-semibold text-gray-800", children: "Th\u00F4ng tin chung" }), _jsxs("span", { className: "inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-500", children: ["Nh\u00F3m ", currentGroupNumber, " \u2014 Ch\u1EC9 \u0111\u1ECDc"] })] }), _jsx("p", { className: "text-xs text-gray-400 mt-0.5", children: "\u0110\u01B0\u1EE3c qu\u1EA3n l\u00FD b\u1EDFi c\u00E2u h\u1ECFi \u0111\u1EA7u ti\u00EAn trong nh\u00F3m" })] })] }) }), _jsx(CardContent, { className: "pt-0", children: _jsxs("div", { className: "space-y-3", children: [groupTitle && (_jsxs("div", { className: "flex items-center gap-2 px-3 py-2 rounded-lg bg-teal-50/50 border border-teal-100", children: [_jsx(MessageSquareText, { className: "h-4 w-4 text-teal-500 shrink-0" }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: groupTitle })] })), groupImageUrl && (_jsx(ImagePreview, { imageUrl: groupImageUrl, label: groupTitle || 'Hình ảnh minh hoạ' })), _jsxs("div", { className: "space-y-4", children: [groupCandidateFields.length > 0 && (_jsxs("div", { className: "rounded-xl overflow-hidden border border-teal-200 shadow-sm", children: [_jsx("div", { className: "bg-gradient-to-r from-teal-600 to-emerald-600 px-3 py-2.5", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded bg-white/20", children: _jsx(Table2, { className: "h-3 w-3 text-white" }) }), _jsx("span", { className: "text-xs font-bold text-white tracking-wide", children: "Candidate's Copy" })] }), _jsxs("span", { className: "text-[10px] font-semibold text-teal-100 bg-white/15 rounded-full px-1.5 py-0.5", children: [groupCandidateFields.length, " d\u00F2ng"] })] }) }), _jsx("div", { className: "bg-teal-50/60 text-center font-semibold text-xs py-2 border-b border-teal-100 text-teal-800", children: groupTitle || '(Chưa nhập tiêu đề)' }), _jsxs("div", { className: "grid grid-cols-2 bg-teal-50/40 border-b border-teal-100", children: [_jsx("div", { className: "px-3 py-1.5 text-[10px] font-bold text-teal-600 uppercase tracking-wider", children: "Key" }), _jsx("div", { className: "px-3 py-1.5 text-[10px] font-bold text-teal-600 uppercase tracking-wider", children: "Value" })] }), _jsx("div", { className: "divide-y divide-gray-100", children: groupCandidateFields.map((row, index) => (_jsxs("div", { className: "grid grid-cols-2 bg-white hover:bg-teal-50/20 transition-colors", children: [_jsx("div", { className: "px-3 py-2 text-sm font-medium text-gray-700", children: row.key || _jsx("span", { className: "text-gray-300 italic", children: '(Chưa nhập)' }) }), _jsx("div", { className: "px-3 py-2 text-sm text-gray-800", children: row.answer || _jsx("span", { className: "text-gray-300", children: '—' }) })] }, index))) })] })), groupExaminerFields.length > 0 && (_jsxs("div", { className: "rounded-xl overflow-hidden border border-amber-200 shadow-sm", children: [_jsx("div", { className: "bg-gradient-to-r from-amber-600 to-orange-600 px-3 py-2.5", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded bg-white/20", children: _jsx(Table2, { className: "h-3 w-3 text-white" }) }), _jsx("span", { className: "text-xs font-bold text-white tracking-wide", children: "Examiner's Copy" })] }), _jsxs("span", { className: "text-[10px] font-semibold text-amber-100 bg-white/15 rounded-full px-1.5 py-0.5", children: [groupExaminerFields.length, " d\u00F2ng"] })] }) }), _jsx("div", { className: "bg-amber-50/60 text-center font-semibold text-xs py-2 border-b border-amber-100 text-amber-800", children: groupTitle || '(Chưa nhập tiêu đề)' }), _jsxs("div", { className: "grid grid-cols-2 bg-amber-50/40 border-b border-amber-100", children: [_jsx("div", { className: "px-3 py-1.5 text-[10px] font-bold text-amber-600 uppercase tracking-wider", children: "Key" }), _jsx("div", { className: "px-3 py-1.5 text-[10px] font-bold text-amber-600 uppercase tracking-wider", children: "Value" })] }), _jsx("div", { className: "divide-y divide-gray-100", children: groupExaminerFields.map((row, index) => (_jsxs("div", { className: "grid grid-cols-2 bg-white hover:bg-amber-50/20 transition-colors", children: [_jsx("div", { className: "px-3 py-2 text-sm font-medium text-gray-700", children: row.key || _jsx("span", { className: "text-gray-300 italic", children: '(Chưa nhập)' }) }), _jsx("div", { className: "px-3 py-2 text-sm text-gray-800", children: row.answer || _jsx("span", { className: "text-gray-300", children: '—' }) })] }, index))) })] }))] }), groupCandidateFields.length === 0 && groupExaminerFields.length === 0 && (_jsx("div", { className: "text-center py-4 text-sm text-gray-400 italic", children: "Ch\u01B0a c\u00F3 d\u1EEF li\u1EC7u b\u1EA3ng th\u00F4ng tin" }))] }) })] })), _jsxs(Form, { ...form, children: [_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 shadow-sm", children: _jsx(Mic, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsxs(CardTitle, { className: "text-lg font-bold text-gray-900", children: ["C\u00E2u h\u1ECFi #", displayQuestionNumber] }), _jsx("p", { className: "text-sm text-gray-500", children: viewMode === 'ANSWER'
|
|
383
|
-
? 'Học sinh nghe câu hỏi của Examiner và trả lời'
|
|
384
|
-
: 'Học sinh đặt câu hỏi cho Examiner' })] })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsx("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: _jsx("div", { className: "flex flex-col md:flex-row gap-6", children: _jsx("div", { className: "flex-1", children: _jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), error: form.formState.errors?.points?.message }) }) }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: [_jsx(Volume2, { className: "h-4 w-4 text-indigo-500" }), audioLabel, " ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FormField, { control: control, name: "audioUrl", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormControl, { children: _jsx(FileUpload, { id: `audio-info-exchange-${displayQuestionNumber}`, label: "", accept: "audio/*", value: field.value, onChange: (url) => { field.onChange(url); clearErrors('audioUrl'); }, maxSize: 10, placeholder: "T\u1EA3i l\u00EAn file \u00E2m thanh (mp3, wav...)", autoUpload: true, prefix: "info-exchange" }) }), _jsx(FormMessage, {})] })) })] })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-green-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-green-400 to-emerald-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 shadow-sm", children: _jsx(MessageSquareText, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-lg font-bold text-gray-900", children: "\u0110\u00E1p \u00E1n mong mu\u1ED1n" }), _jsxs("p", { className: "text-sm text-gray-500", children: [watchedExpectedAnswers.filter(a => a.trim()).length, " \u0111\u00E1p \u00E1n \u0111\u00E3 nh\u1EADp"] })] })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addExpectedAnswer, 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"] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2 text-xs text-blue-700", children: [_jsx(Info, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }), _jsx("span", { children: "Ch\u1EA5m th\u1EE7 c\u00F4ng: gi\u00E1o vi\u00EAn tham kh\u1EA3o \u0111\u00E1p \u00E1n \u0111\u1EC3 ch\u1EA5m \u00B7 Ch\u1EA5m t\u1EF1 \u0111\u1ED9ng: AI so s\u00E1nh gi\u1ECDng n\u00F3i h\u1ECDc sinh v\u1EDBi \u0111\u00E1p \u00E1n." })] }), _jsx(FormField, { control: control, name: "expectedAnswers", render: () => (_jsxs(FormItem, { children: [_jsx("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-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 text-sm font-bold mt-1", children: index + 1 }), _jsxs("div", { className: "flex-1", children: [_jsx(Input, { placeholder: answerPlaceholder, value: answer || '', onChange: (e) => handleExpectedAnswerChange(index, e.target.value), 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, { type: "button", variant: "ghost", size: "sm", onClick: () => removeExpectedAnswer(index), className: "mt-0.5 h-8 w-8 rounded-full text-gray-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity", "aria-label": `Xóa đáp án ${index + 1}`, children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }, index))) }), form.formState.errors?.expectedAnswers?.message && (_jsx(FormMessage, {}))] })) })] })] }), _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, { ...form.register('explanation'), placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0... v\u00EC...", className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none", rows: 3 }) })] })] })] }));
|
|
364
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(ListChecks, { className: "h-3.5 w-3.5" }), title: `Trao đổi thông tin — Câu #${displayQuestionNumber}`, extraActions: _jsx("span", { className: `inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium ${modeColors.badge}`, children: modeLabel }), points: {
|
|
365
|
+
value: watchedPoints,
|
|
366
|
+
onValueChange: (value) => form.setValue('points', value, { shouldValidate: true }),
|
|
367
|
+
error: form.formState.errors?.points?.message,
|
|
368
|
+
}, preview: {
|
|
369
|
+
onClick: () => setIsClientMode(true),
|
|
370
|
+
disabled: !watchedAudioUrl,
|
|
371
|
+
} }), contentClassName: "hidden", children: null }), _jsxs("div", { className: `inline-flex items-center gap-2 px-3 py-1.5 rounded-full text-xs font-semibold border ${modeColors.badge}`, children: [modeColors.icon, " ", viewMode === 'ANSWER' ? 'Examiner → Candidate' : 'Candidate → Examiner', _jsx("span", { className: "text-gray-400", children: "|" }), " ", modeLabel] }), isFirstInGroup ? (_jsxs(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-gray-100", children: _jsx(Table2, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Th\u00F4ng tin chung" }), isGrouped && (_jsxs("span", { className: "inline-flex items-center rounded-full border border-teal-300 bg-teal-100 px-2.5 py-0.5 text-xs font-semibold text-teal-700", children: ["Nh\u00F3m ", currentGroupNumber] }))] }), _jsx("p", { className: "text-sm text-gray-500", children: "D\u00F9ng chung cho t\u1EA5t c\u1EA3 c\u00E2u h\u1ECFi trong nh\u00F3m \u2014 Ti\u00EAu \u0111\u1EC1, h\u00ECnh \u1EA3nh, v\u00E0 2 b\u1EA3ng th\u00F4ng tin" })] })] }), children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: [_jsx(MessageSquareText, { className: "h-4 w-4 text-teal-500" }), "Ti\u00EAu \u0111\u1EC1 ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { value: groupTitle, onChange: (e) => {
|
|
372
|
+
updateGroupData({ title: e.target.value });
|
|
373
|
+
setValue('title', e.target.value, { shouldDirty: true });
|
|
374
|
+
}, placeholder: "VD: Katy's house", className: "border-gray-200 bg-white transition-all focus:border-teal-300 focus:ring-teal-200" })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-teal-500" }), "H\u00ECnh \u1EA3nh minh ho\u1EA1"] }), _jsx(FileUpload, { accept: "image/*", value: groupImageUrl, onChange: (url) => {
|
|
375
|
+
updateGroupData({ imageUrl: url });
|
|
376
|
+
setValue('imageUrl', url, { shouldDirty: true });
|
|
377
|
+
}, placeholder: "Upload h\u00ECnh \u1EA3nh", label: "", autoUpload: true, prefix: "info-exchange" }), groupImageUrl && (_jsx(ImagePreview, { imageUrl: groupImageUrl, label: "H\u00ECnh \u1EA3nh minh ho\u1EA1" }))] }), _jsxs("div", { className: "space-y-5", children: [_jsx("div", { className: "space-y-3", children: _jsxs("div", { className: "rounded-xl border border-teal-200 overflow-hidden shadow-sm bg-white", children: [_jsx("div", { className: "bg-gradient-to-r from-teal-600 to-emerald-600 px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-white/20 backdrop-blur-sm", children: _jsx(Table2, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx("h4", { className: "text-sm font-bold text-white tracking-wide", children: "Candidate's Copy" }), _jsx("p", { className: "text-[10px] text-teal-100 font-medium", children: "B\u1EA3ng th\u00F4ng tin h\u1ECDc sinh nh\u00ECn th\u1EA5y" })] })] }), _jsx("div", { className: "flex items-center gap-2", children: _jsxs("span", { className: "inline-flex items-center rounded-full bg-white/20 backdrop-blur-sm px-2 py-0.5 text-[10px] font-bold text-white", children: [groupCandidateFields.length, " d\u00F2ng"] }) })] }) }), _jsx("div", { className: "bg-teal-50/60 border-b border-teal-100 px-4 py-2", children: _jsx("p", { className: "text-xs font-semibold text-teal-800 text-center", children: groupTitle || '(Chưa nhập tiêu đề)' }) }), _jsxs("div", { className: "grid grid-cols-[40px_1fr_1fr_36px] bg-gradient-to-r from-teal-50/80 to-emerald-50/80 border-b border-teal-100", children: [_jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-teal-600 uppercase tracking-wider text-center", children: "#" }), _jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-teal-600 uppercase tracking-wider", children: "Key" }), _jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-teal-600 uppercase tracking-wider", children: "Value" }), _jsx("div", {})] }), _jsx("div", { className: "divide-y divide-gray-100", children: groupCandidateFields.map((field, index) => (_jsxs("div", { className: "grid grid-cols-[40px_1fr_1fr_36px] items-center bg-white hover:bg-teal-50/30 transition-colors group/row", children: [_jsx("div", { className: "px-2 py-2 flex justify-center", children: _jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-teal-100 text-teal-700 font-bold text-[11px]", children: index + 1 }) }), _jsx("div", { className: "px-1.5 py-1.5", children: _jsx(Input, { value: field.key, onChange: (e) => handleUpdateCandidateField(index, 'key', e.target.value), placeholder: "VD: Where", className: "h-8 text-sm border-gray-200 bg-white focus:border-teal-400 focus:ring-teal-200 transition-all" }) }), _jsx("div", { className: "px-1.5 py-1.5", children: _jsx(Input, { value: field.answer, onChange: (e) => handleUpdateCandidateField(index, 'answer', e.target.value), placeholder: "VD: King Road", className: "h-8 text-sm border-gray-200 bg-white focus:border-teal-400 focus:ring-teal-200 transition-all" }) }), _jsx("div", { className: "px-1 py-1.5 flex justify-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => handleRemoveCandidateField(index), disabled: groupCandidateFields.length <= 1, className: "h-7 w-7 text-gray-300 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/row:opacity-100 transition-all disabled:opacity-0 disabled:group-hover/row:opacity-30", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }) })] }, index))) }), _jsx("div", { className: "border-t border-dashed border-teal-200 bg-teal-50/30", children: _jsxs("button", { type: "button", onClick: handleAddCandidateField, className: "w-full flex items-center justify-center gap-1.5 py-2.5 text-xs font-semibold text-teal-600 hover:text-teal-700 hover:bg-teal-50 transition-colors cursor-pointer", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm d\u00F2ng"] }) })] }) }), _jsx("div", { className: "space-y-3", children: _jsxs("div", { className: "rounded-xl border border-amber-200 overflow-hidden shadow-sm bg-white", children: [_jsx("div", { className: "bg-gradient-to-r from-amber-600 to-orange-600 px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-white/20 backdrop-blur-sm", children: _jsx(Table2, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx("h4", { className: "text-sm font-bold text-white tracking-wide", children: "Examiner's Copy" }), _jsx("p", { className: "text-[10px] text-amber-100 font-medium", children: "B\u1EA3ng th\u00F4ng tin gi\u00E1m kh\u1EA3o nh\u00ECn th\u1EA5y" })] })] }), _jsx("div", { className: "flex items-center gap-2", children: _jsxs("span", { className: "inline-flex items-center rounded-full bg-white/20 backdrop-blur-sm px-2 py-0.5 text-[10px] font-bold text-white", children: [groupExaminerFields.length, " d\u00F2ng"] }) })] }) }), _jsx("div", { className: "bg-amber-50/60 border-b border-amber-100 px-4 py-2", children: _jsx("p", { className: "text-xs font-semibold text-amber-800 text-center", children: groupTitle || '(Chưa nhập tiêu đề)' }) }), _jsxs("div", { className: "grid grid-cols-[40px_1fr_1fr_36px] bg-gradient-to-r from-amber-50/80 to-orange-50/80 border-b border-amber-100", children: [_jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-amber-600 uppercase tracking-wider text-center", children: "#" }), _jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-amber-600 uppercase tracking-wider", children: "Key" }), _jsx("div", { className: "px-2 py-2 text-[10px] font-bold text-amber-600 uppercase tracking-wider", children: "Value" }), _jsx("div", {})] }), _jsx("div", { className: "divide-y divide-gray-100", children: groupExaminerFields.map((field, index) => (_jsxs("div", { className: "grid grid-cols-[40px_1fr_1fr_36px] items-center bg-white hover:bg-amber-50/30 transition-colors group/row", children: [_jsx("div", { className: "px-2 py-2 flex justify-center", children: _jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-amber-100 text-amber-700 font-bold text-[11px]", children: index + 1 }) }), _jsx("div", { className: "px-1.5 py-1.5", children: _jsx(Input, { value: field.key, onChange: (e) => handleUpdateExaminerField(index, 'key', e.target.value), placeholder: "VD: Where", className: "h-8 text-sm border-gray-200 bg-white focus:border-amber-400 focus:ring-amber-200 transition-all" }) }), _jsx("div", { className: "px-1.5 py-1.5", children: _jsx(Input, { value: field.answer, onChange: (e) => handleUpdateExaminerField(index, 'answer', e.target.value), placeholder: "VD: King Road", className: "h-8 text-sm border-gray-200 bg-white focus:border-amber-400 focus:ring-amber-200 transition-all" }) }), _jsx("div", { className: "px-1 py-1.5 flex justify-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => handleRemoveExaminerField(index), disabled: groupExaminerFields.length <= 1, className: "h-7 w-7 text-gray-300 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/row:opacity-100 transition-all disabled:opacity-0 disabled:group-hover/row:opacity-30", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }) })] }, index))) }), _jsx("div", { className: "border-t border-dashed border-amber-200 bg-amber-50/30", children: _jsxs("button", { type: "button", onClick: handleAddExaminerField, className: "w-full flex items-center justify-center gap-1.5 py-2.5 text-xs font-semibold text-amber-600 hover:text-amber-700 hover:bg-amber-50 transition-colors cursor-pointer", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm d\u00F2ng"] }) })] }) })] })] })) : (_jsx(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-gray-100", children: _jsx(Table2, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Th\u00F4ng tin chung" }), _jsxs("span", { className: "inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-500", children: ["Nh\u00F3m ", currentGroupNumber, " \u2014 Ch\u1EC9 \u0111\u1ECDc"] })] }), _jsx("p", { className: "text-xs text-gray-400 mt-0.5", children: "\u0110\u01B0\u1EE3c qu\u1EA3n l\u00FD b\u1EDFi c\u00E2u h\u1ECFi \u0111\u1EA7u ti\u00EAn trong nh\u00F3m" })] })] }), children: _jsxs("div", { className: "space-y-3", children: [groupTitle && (_jsxs("div", { className: "flex items-center gap-2 px-3 py-2 rounded-lg bg-teal-50/50 border border-teal-100", children: [_jsx(MessageSquareText, { className: "h-4 w-4 text-teal-500 shrink-0" }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: groupTitle })] })), groupImageUrl && (_jsx(ImagePreview, { imageUrl: groupImageUrl, label: groupTitle || 'Hình ảnh minh hoạ' })), _jsxs("div", { className: "space-y-4", children: [groupCandidateFields.length > 0 && (_jsxs("div", { className: "rounded-xl overflow-hidden border border-teal-200 shadow-sm", children: [_jsx("div", { className: "bg-gradient-to-r from-teal-600 to-emerald-600 px-3 py-2.5", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded bg-white/20", children: _jsx(Table2, { className: "h-3 w-3 text-white" }) }), _jsx("span", { className: "text-xs font-bold text-white tracking-wide", children: "Candidate's Copy" })] }), _jsxs("span", { className: "text-[10px] font-semibold text-teal-100 bg-white/15 rounded-full px-1.5 py-0.5", children: [groupCandidateFields.length, " d\u00F2ng"] })] }) }), _jsx("div", { className: "bg-teal-50/60 text-center font-semibold text-xs py-2 border-b border-teal-100 text-teal-800", children: groupTitle || '(Chưa nhập tiêu đề)' }), _jsxs("div", { className: "grid grid-cols-2 bg-teal-50/40 border-b border-teal-100", children: [_jsx("div", { className: "px-3 py-1.5 text-[10px] font-bold text-teal-600 uppercase tracking-wider", children: "Key" }), _jsx("div", { className: "px-3 py-1.5 text-[10px] font-bold text-teal-600 uppercase tracking-wider", children: "Value" })] }), _jsx("div", { className: "divide-y divide-gray-100", children: groupCandidateFields.map((row, index) => (_jsxs("div", { className: "grid grid-cols-2 bg-white hover:bg-teal-50/20 transition-colors", children: [_jsx("div", { className: "px-3 py-2 text-sm font-medium text-gray-700", children: row.key || _jsx("span", { className: "text-gray-300 italic", children: '(Chưa nhập)' }) }), _jsx("div", { className: "px-3 py-2 text-sm text-gray-800", children: row.answer || _jsx("span", { className: "text-gray-300", children: '—' }) })] }, index))) })] })), groupExaminerFields.length > 0 && (_jsxs("div", { className: "rounded-xl overflow-hidden border border-amber-200 shadow-sm", children: [_jsx("div", { className: "bg-gradient-to-r from-amber-600 to-orange-600 px-3 py-2.5", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded bg-white/20", children: _jsx(Table2, { className: "h-3 w-3 text-white" }) }), _jsx("span", { className: "text-xs font-bold text-white tracking-wide", children: "Examiner's Copy" })] }), _jsxs("span", { className: "text-[10px] font-semibold text-amber-100 bg-white/15 rounded-full px-1.5 py-0.5", children: [groupExaminerFields.length, " d\u00F2ng"] })] }) }), _jsx("div", { className: "bg-amber-50/60 text-center font-semibold text-xs py-2 border-b border-amber-100 text-amber-800", children: groupTitle || '(Chưa nhập tiêu đề)' }), _jsxs("div", { className: "grid grid-cols-2 bg-amber-50/40 border-b border-amber-100", children: [_jsx("div", { className: "px-3 py-1.5 text-[10px] font-bold text-amber-600 uppercase tracking-wider", children: "Key" }), _jsx("div", { className: "px-3 py-1.5 text-[10px] font-bold text-amber-600 uppercase tracking-wider", children: "Value" })] }), _jsx("div", { className: "divide-y divide-gray-100", children: groupExaminerFields.map((row, index) => (_jsxs("div", { className: "grid grid-cols-2 bg-white hover:bg-amber-50/20 transition-colors", children: [_jsx("div", { className: "px-3 py-2 text-sm font-medium text-gray-700", children: row.key || _jsx("span", { className: "text-gray-300 italic", children: '(Chưa nhập)' }) }), _jsx("div", { className: "px-3 py-2 text-sm text-gray-800", children: row.answer || _jsx("span", { className: "text-gray-300", children: '—' }) })] }, index))) })] }))] }), groupCandidateFields.length === 0 && groupExaminerFields.length === 0 && (_jsx("div", { className: "text-center py-4 text-sm text-gray-400 italic", children: "Ch\u01B0a c\u00F3 d\u1EEF li\u1EC7u b\u1EA3ng th\u00F4ng tin" }))] }) })), _jsxs(Form, { ...form, children: [_jsx(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-gray-100", children: _jsx(Mic, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsxs("p", { className: "text-sm font-semibold text-gray-800", children: ["C\u00E2u h\u1ECFi #", displayQuestionNumber] })] }), children: _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: [_jsx(Volume2, { className: "h-4 w-4 text-indigo-500" }), audioLabel, " ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FormField, { control: control, name: "audioUrl", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormControl, { children: _jsx(FileUpload, { id: `audio-info-exchange-${displayQuestionNumber}`, label: "", accept: "audio/*", value: field.value, onChange: (url) => { field.onChange(url); clearErrors('audioUrl'); }, maxSize: 10, placeholder: "T\u1EA3i l\u00EAn file \u00E2m thanh (mp3, wav...)", autoUpload: true, prefix: "info-exchange" }) }), _jsx(FormMessage, {})] })) })] }) }), _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" }) }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: "\u0110\u00E1p \u00E1n mong mu\u1ED1n" })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addExpectedAnswer, 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 text-xs text-blue-700", children: [_jsx(Info, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }), _jsx("span", { children: "Ch\u1EA5m th\u1EE7 c\u00F4ng: gi\u00E1o vi\u00EAn tham kh\u1EA3o \u0111\u00E1p \u00E1n \u0111\u1EC3 ch\u1EA5m \u00B7 Ch\u1EA5m t\u1EF1 \u0111\u1ED9ng: AI so s\u00E1nh gi\u1ECDng n\u00F3i h\u1ECDc sinh v\u1EDBi \u0111\u00E1p \u00E1n." })] }), _jsx(FormField, { control: control, name: "expectedAnswers", render: () => (_jsxs(FormItem, { children: [_jsx("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-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 text-sm font-bold mt-1", children: index + 1 }), _jsxs("div", { className: "flex-1", children: [_jsx(Input, { placeholder: answerPlaceholder, value: answer || '', onChange: (e) => handleExpectedAnswerChange(index, e.target.value), 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, { type: "button", variant: "ghost", size: "sm", onClick: () => removeExpectedAnswer(index), className: "mt-0.5 h-8 w-8 rounded-full text-gray-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity", "aria-label": `Xóa đáp án ${index + 1}`, children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }, index))) }), form.formState.errors?.expectedAnswers?.message && (_jsx(FormMessage, {}))] })) })] }), _jsx(CompactExplanationToggle, { value: watchedExplanation || '', onValueChange: (value) => form.setValue('explanation', value, { shouldValidate: true }), defaultVisible: Boolean(watchedExplanation) })] })] }));
|
|
385
378
|
}
|
|
@@ -20,7 +20,7 @@ function OddOneOutImage({ imageUrl, index }) {
|
|
|
20
20
|
// ─── Main Client Component ───────────────────────────────────────────────────
|
|
21
21
|
export function ListenAndSpeakOddOneOutClient({ questionData, isReviewMode = false, }) {
|
|
22
22
|
const { audioUrl, images, oddOneOutIndex, expectedAnswers = [], explanation } = questionData;
|
|
23
|
-
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-
|
|
23
|
+
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(CircleOff, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 n\u00F3i (T\u00ECm h\u00ECnh kh\u00E1c lo\u1EA1i)" })] }) }), _jsxs("div", { className: "p-3 space-y-3", children: [images && images.length > 0 && (_jsx("div", { className: "bg-gradient-to-br from-amber-50/80 to-orange-50/60 rounded-2xl p-6 border border-amber-100/80 shadow-sm space-y-4", children: images.length === 4 ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "grid grid-cols-3 gap-4", children: images.slice(0, 3).map((imgUrl, index) => (_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-1.5 mb-1", children: [_jsx("span", { className: `flex h-6 w-6 items-center justify-center rounded-md text-xs font-bold ${isReviewMode && oddOneOutIndex === index ? 'bg-red-200 text-red-800' : 'bg-amber-200 text-amber-800'}`, children: index + 1 }), isReviewMode && oddOneOutIndex === index && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-red-100 border border-red-200 px-1.5 py-0.5 text-[10px] font-bold text-red-700", children: [_jsx(CircleOff, { className: "h-2.5 w-2.5" }), "Kh\u00E1c lo\u1EA1i"] }))] }), _jsx("div", { className: `relative aspect-square rounded-xl overflow-hidden border-2 bg-white shadow-sm ${isReviewMode && oddOneOutIndex === index ? 'border-red-400 ring-2 ring-red-200' : 'border-gray-200'}`, children: imgUrl ? (_jsx(OddOneOutImage, { imageUrl: imgUrl, index: index })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center text-gray-400 text-sm", children: _jsx(ImageIcon, { className: "h-6 w-6" }) })) })] }, index))) }), _jsx("div", { className: "flex justify-center", children: _jsx("div", { className: "w-1/3", children: _jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-1.5 mb-1", children: [_jsx("span", { className: `flex h-6 w-6 items-center justify-center rounded-md text-xs font-bold ${isReviewMode && oddOneOutIndex === 3 ? 'bg-red-200 text-red-800' : 'bg-amber-200 text-amber-800'}`, children: "4" }), isReviewMode && oddOneOutIndex === 3 && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-red-100 border border-red-200 px-1.5 py-0.5 text-[10px] font-bold text-red-700", children: [_jsx(CircleOff, { className: "h-2.5 w-2.5" }), "Kh\u00E1c lo\u1EA1i"] }))] }), _jsx("div", { className: `relative aspect-square rounded-xl overflow-hidden border-2 bg-white shadow-sm ${isReviewMode && oddOneOutIndex === 3 ? 'border-red-400 ring-2 ring-red-200' : 'border-gray-200'}`, children: images[3] ? (_jsx(OddOneOutImage, { imageUrl: images[3], index: 3 })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center text-gray-400 text-sm", children: _jsx(ImageIcon, { className: "h-6 w-6" }) })) })] }) }) })] })) : (
|
|
24
24
|
/* Fallback: equal grid for other counts */
|
|
25
25
|
_jsx("div", { className: "grid grid-cols-2 gap-4", children: images.map((imgUrl, index) => (_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-1.5 mb-1", children: [_jsx("span", { className: `flex h-6 w-6 items-center justify-center rounded-md text-xs font-bold ${isReviewMode && oddOneOutIndex === index ? 'bg-red-200 text-red-800' : 'bg-amber-200 text-amber-800'}`, children: index + 1 }), isReviewMode && oddOneOutIndex === index && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-red-100 border border-red-200 px-1.5 py-0.5 text-[10px] font-bold text-red-700", children: [_jsx(CircleOff, { className: "h-2.5 w-2.5" }), "Kh\u00E1c lo\u1EA1i"] }))] }), _jsx("div", { className: `relative aspect-square rounded-xl overflow-hidden border-2 bg-white shadow-sm ${isReviewMode && oddOneOutIndex === index ? 'border-red-400 ring-2 ring-red-200' : 'border-gray-200'}`, children: imgUrl ? (_jsx(OddOneOutImage, { imageUrl: imgUrl, index: index })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center text-gray-400 text-sm", children: _jsx(ImageIcon, { className: "h-6 w-6" }) })) })] }, index))) })) })), 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" })] })), _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 })] }))] })] })] }));
|
|
26
26
|
}
|