@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
|
@@ -39,7 +39,7 @@ export function FindWordsInMatrixClient({ content, correctAnswer, showSolution =
|
|
|
39
39
|
});
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
-
return (_jsxs("div", { className: "space-y-
|
|
42
|
+
return (_jsxs("div", { className: "space-y-3", children: [content.title && (_jsx("h3", { className: "text-lg font-bold text-gray-800 dark:text-gray-100", children: content.title })), isCategorize ? (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4", children: categories.map((cat) => {
|
|
43
43
|
const wordsInCat = wordBank.filter((w) => w.categoryId === cat.id);
|
|
44
44
|
return (_jsxs(Card, { className: "p-3.5 bg-gray-50/70 dark:bg-gray-800/40 border", children: [_jsxs("div", { className: "font-bold text-sm text-gray-800 dark:text-gray-200 border-b pb-1.5 mb-2.5 flex items-center justify-between", children: [_jsx("span", { children: cat.name }), _jsx(Badge, { variant: "outline", className: "text-xs", children: wordsInCat.length })] }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: wordsInCat.map((w, idx) => {
|
|
45
45
|
const isExample = Boolean(w.isExample);
|
|
@@ -9,7 +9,9 @@ import { Badge } from '../../../../components/ui/badge';
|
|
|
9
9
|
import { Checkbox } from '../../../../components/ui/checkbox';
|
|
10
10
|
import { Textarea } from '../../../../components/ui/textarea';
|
|
11
11
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../../../components/ui/select';
|
|
12
|
-
import { Plus, Trash2, Sparkles, Clipboard, MousePointer, RotateCcw, Layers, ListFilter, FolderPlus, } from 'lucide-react';
|
|
12
|
+
import { Plus, Trash2, Sparkles, Clipboard, MousePointer, RotateCcw, Layers, ListFilter, FolderPlus, Search, } from 'lucide-react';
|
|
13
|
+
import { CompactCreatorHeader, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
14
|
+
import { FindWordsInMatrixClient } from './FindWordsInMatrixClient';
|
|
13
15
|
const PALETTE = [
|
|
14
16
|
'#f59e0b', '#10b981', '#0ea5e9', '#8b5cf6', '#f43f5e',
|
|
15
17
|
'#6366f1', '#14b8a6', '#eab308', '#ec4899', '#06b6d4',
|
|
@@ -83,6 +85,7 @@ export function FindWordsInMatrixCreator({ initialData, onChange, onUnsavedChang
|
|
|
83
85
|
const [isPickingPath, setIsPickingPath] = useState(false);
|
|
84
86
|
const [pasteModalOpen, setPasteModalOpen] = useState(false);
|
|
85
87
|
const [pasteText, setPasteText] = useState('');
|
|
88
|
+
const [isClientMode, setIsClientMode] = useState(false);
|
|
86
89
|
const updateState = useCallback((updater) => {
|
|
87
90
|
setState((prev) => {
|
|
88
91
|
const next = updater(prev);
|
|
@@ -231,17 +234,54 @@ export function FindWordsInMatrixCreator({ initialData, onChange, onUnsavedChang
|
|
|
231
234
|
cellColorMap[`${coord.row}-${coord.col}`] = { color, word: w.word };
|
|
232
235
|
});
|
|
233
236
|
});
|
|
234
|
-
|
|
237
|
+
if (isClientMode) {
|
|
238
|
+
const exampleWord = state.words.find((w) => w.isExample && w.path && w.path.length > 0);
|
|
239
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(FindWordsInMatrixClient, { content: {
|
|
240
|
+
type: state.type,
|
|
241
|
+
title: state.title,
|
|
242
|
+
gridSize: { rows: state.rows, cols: state.cols },
|
|
243
|
+
grid: state.grid,
|
|
244
|
+
...(state.type === 'CATEGORIZE' ? { categories: state.categories } : {}),
|
|
245
|
+
wordBank: state.words.map((w) => ({
|
|
246
|
+
id: w.id,
|
|
247
|
+
word: w.word,
|
|
248
|
+
isExample: w.isExample,
|
|
249
|
+
...(w.categoryId ? { categoryId: w.categoryId } : {}),
|
|
250
|
+
})),
|
|
251
|
+
...(exampleWord
|
|
252
|
+
? {
|
|
253
|
+
exampleSelection: {
|
|
254
|
+
wordId: exampleWord.id,
|
|
255
|
+
...(exampleWord.categoryId ? { categoryId: exampleWord.categoryId } : {}),
|
|
256
|
+
path: exampleWord.path || [],
|
|
257
|
+
},
|
|
258
|
+
}
|
|
259
|
+
: {}),
|
|
260
|
+
}, correctAnswer: {
|
|
261
|
+
words: state.words
|
|
262
|
+
.filter((w) => !w.isExample && w.path && w.path.length > 0)
|
|
263
|
+
.map((w) => ({
|
|
264
|
+
wordId: w.id,
|
|
265
|
+
word: w.word,
|
|
266
|
+
...(w.categoryId ? { categoryId: w.categoryId } : {}),
|
|
267
|
+
path: w.path || [],
|
|
268
|
+
})),
|
|
269
|
+
}, showSolution: true })] }));
|
|
270
|
+
}
|
|
271
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(Search, { className: "h-3.5 w-3.5" }), title: "T\u00ECm t\u1EEB trong ma tr\u1EADn", points: {
|
|
272
|
+
value: state.points,
|
|
273
|
+
onValueChange: (value) => updateState((p) => ({ ...p, points: value })),
|
|
274
|
+
}, preview: { onClick: () => setIsClientMode(true) } }), children: [_jsxs("div", { children: [_jsx(Label, { className: "font-semibold text-sm mb-2 block", children: "D\u1EA1ng b\u00E0i t\u1EADp (Type / Mode)" }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3", children: [_jsxs("button", { type: "button", onClick: () => updateState((p) => ({ ...p, type: 'WORD_BANK' })), className: `flex items-center gap-3 p-3 rounded-xl border text-left transition-all ${state.type === 'WORD_BANK'
|
|
235
275
|
? 'border-blue-500 bg-blue-50/60 dark:bg-blue-950/30 text-blue-900 dark:text-blue-200 ring-2 ring-blue-400'
|
|
236
276
|
: 'border-gray-200 hover:bg-gray-50 dark:border-gray-700 text-gray-700 dark:text-gray-300'}`, children: [_jsx("div", { className: "p-2 rounded-lg bg-blue-100 dark:bg-blue-900 text-blue-600", children: _jsx(ListFilter, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("div", { className: "font-bold text-sm", children: "1. Danh s\u00E1ch t\u1EEB (Word Bank)" }), _jsx("div", { className: "text-xs text-gray-500", children: "T\u00ECm c\u00E1c t\u1EEB c\u00F3 s\u1EB5n trong b\u1EA3ng ch\u1EEF c\u00E1i (VD: Gadgets)." })] })] }), _jsxs("button", { type: "button", onClick: () => updateState((p) => ({ ...p, type: 'CATEGORIZE' })), className: `flex items-center gap-3 p-3 rounded-xl border text-left transition-all ${state.type === 'CATEGORIZE'
|
|
237
277
|
? 'border-emerald-500 bg-emerald-50/60 dark:bg-emerald-950/30 text-emerald-900 dark:text-emerald-200 ring-2 ring-emerald-400'
|
|
238
|
-
: 'border-gray-200 hover:bg-gray-50 dark:border-gray-700 text-gray-700 dark:text-gray-300'}`, children: [_jsx("div", { className: "p-2 rounded-lg bg-emerald-100 dark:bg-emerald-900 text-emerald-600", children: _jsx(Layers, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("div", { className: "font-bold text-sm", children: "2. T\u00ECm t\u1EEB & Ph\u00E2n nh\u00F3m (Categorize)" }), _jsx("div", { className: "text-xs text-gray-500", children: "T\u00ECm t\u1EEB \u1EA9n v\u00E0 x\u1EBFp v\u00E0o c\u00E1c nh\u00F3m ti\u00EAu \u0111\u1EC1 (VD: Plants / Animals)." })] })] })] })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4 pt-2 border-t", children: [_jsxs("div", { className: "md:col-span-2", children: [_jsx(Label, { className: "font-semibold", children: "Ti\u00EAu \u0111\u1EC1 b\u00E0i t\u1EADp" }), _jsx(Input, { value: state.title || '', onChange: (e) => updateState((p) => ({ ...p, title: e.target.value })), placeholder: state.type === 'CATEGORIZE' ? 'VD: Plants and Animals Word Search' : 'VD: Technology Gadgets Word Search', className: "mt-1" })] }), _jsxs("div", { children: [_jsx(Label, { className: "font-semibold", children: "K\u00EDch th\u01B0\u1EDBc ma tr\u1EADn (H\u00E0ng x C\u1ED9t)" }), _jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx(Input, { type: "number", min: 4, max: 25, value: state.rows, onChange: (e) => handleResize(Number(e.target.value), state.cols), className: "w-20 text-center font-bold" }), _jsx("span", { className: "font-bold text-gray-400", children: "\u00D7" }), _jsx(Input, { type: "number", min: 4, max: 25, value: state.cols, onChange: (e) => handleResize(state.rows, Number(e.target.value)), className: "w-20 text-center font-bold" })] })] })] })] }), state.type === 'CATEGORIZE' && (_jsxs(Card, { className: "
|
|
278
|
+
: 'border-gray-200 hover:bg-gray-50 dark:border-gray-700 text-gray-700 dark:text-gray-300'}`, children: [_jsx("div", { className: "p-2 rounded-lg bg-emerald-100 dark:bg-emerald-900 text-emerald-600", children: _jsx(Layers, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("div", { className: "font-bold text-sm", children: "2. T\u00ECm t\u1EEB & Ph\u00E2n nh\u00F3m (Categorize)" }), _jsx("div", { className: "text-xs text-gray-500", children: "T\u00ECm t\u1EEB \u1EA9n v\u00E0 x\u1EBFp v\u00E0o c\u00E1c nh\u00F3m ti\u00EAu \u0111\u1EC1 (VD: Plants / Animals)." })] })] })] })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4 pt-2 border-t", children: [_jsxs("div", { className: "md:col-span-2", children: [_jsx(Label, { className: "font-semibold", children: "Ti\u00EAu \u0111\u1EC1 b\u00E0i t\u1EADp" }), _jsx(Input, { value: state.title || '', onChange: (e) => updateState((p) => ({ ...p, title: e.target.value })), placeholder: state.type === 'CATEGORIZE' ? 'VD: Plants and Animals Word Search' : 'VD: Technology Gadgets Word Search', className: "mt-1" })] }), _jsxs("div", { children: [_jsx(Label, { className: "font-semibold", children: "K\u00EDch th\u01B0\u1EDBc ma tr\u1EADn (H\u00E0ng x C\u1ED9t)" }), _jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx(Input, { type: "number", min: 4, max: 25, value: state.rows, onChange: (e) => handleResize(Number(e.target.value), state.cols), className: "w-20 text-center font-bold" }), _jsx("span", { className: "font-bold text-gray-400", children: "\u00D7" }), _jsx(Input, { type: "number", min: 4, max: 25, value: state.cols, onChange: (e) => handleResize(state.rows, Number(e.target.value)), className: "w-20 text-center font-bold" })] })] })] })] }), state.type === 'CATEGORIZE' && (_jsxs(Card, { className: "overflow-hidden border-0 bg-white p-4 space-y-3 shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsxs("h4", { className: "font-bold text-sm text-emerald-900 dark:text-emerald-300 flex items-center gap-1.5", children: [_jsx(Layers, { className: "h-4 w-4 text-emerald-600" }), "C\u00E1c nh\u00F3m danh m\u1EE5c (Categories / Headings)"] }), _jsx("p", { className: "text-xs text-gray-500", children: "T\u1EA1o c\u00E1c c\u1ED9t ti\u00EAu \u0111\u1EC1 \u0111\u1EC3 h\u1ECDc sinh ph\u00E2n lo\u1EA1i t\u1EEB v\u00E0o (t\u1ED1i thi\u1EC3u 2 nh\u00F3m)." })] }), _jsxs(Button, { type: "button", size: "sm", variant: "outline", onClick: handleAddCategory, className: "gap-1 text-xs", children: [_jsx(FolderPlus, { className: "h-3.5 w-3.5" }), " Th\u00EAm nh\u00F3m"] })] }), _jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3", children: state.categories.map((cat, idx) => (_jsxs("div", { className: "flex items-center gap-2 p-2.5 bg-white dark:bg-gray-900 rounded-lg border shadow-xs", children: [_jsx(Badge, { variant: "secondary", className: "font-mono text-xs", children: idx + 1 }), _jsx(Input, { value: cat.name, onChange: (e) => {
|
|
239
279
|
const val = e.target.value;
|
|
240
280
|
updateState((p) => ({
|
|
241
281
|
...p,
|
|
242
282
|
categories: p.categories.map((c, i) => (i === idx ? { ...c, name: val } : c)),
|
|
243
283
|
}));
|
|
244
|
-
}, placeholder: "T\u00EAn nh\u00F3m (VD: Plants)", className: "h-8 text-sm font-semibold" }), state.categories.length > 2 && (_jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => handleRemoveCategory(cat.id), className: "h-8 w-8 p-0 text-red-500 hover:text-red-700", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }, cat.id || idx))) })] })), _jsxs(Card, { className: "p-4 space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 border-b pb-3", children: [_jsxs("div", { children: [_jsxs("h4", { className: "font-bold text-base text-gray-800 dark:text-gray-200", children: ["Ma tr\u1EADn ch\u1EEF c\u00E1i (", state.rows, " h\u00E0ng \u00D7 ", state.cols, " c\u1ED9t)"] }), _jsx("p", { className: "text-xs text-gray-500", children: isPickingPath
|
|
284
|
+
}, placeholder: "T\u00EAn nh\u00F3m (VD: Plants)", className: "h-8 text-sm font-semibold" }), state.categories.length > 2 && (_jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => handleRemoveCategory(cat.id), className: "h-8 w-8 p-0 text-red-500 hover:text-red-700", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }, cat.id || idx))) })] })), _jsxs(Card, { className: "overflow-hidden border-0 bg-white p-4 space-y-4 shadow-sm", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 border-b pb-3", children: [_jsxs("div", { children: [_jsxs("h4", { className: "font-bold text-base text-gray-800 dark:text-gray-200", children: ["Ma tr\u1EADn ch\u1EEF c\u00E1i (", state.rows, " h\u00E0ng \u00D7 ", state.cols, " c\u1ED9t)"] }), _jsx("p", { className: "text-xs text-gray-500", children: isPickingPath
|
|
245
285
|
? `👉 Đang gán vị trí cho từ: "${state.words[selectedWordIdx]?.word}". Hãy click các ô trên lưới theo thứ tự.`
|
|
246
286
|
: 'Nhập trực tiếp ký tự vào ô, hoặc dùng các nút công cụ bên phải.' })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setPasteModalOpen(!pasteModalOpen), className: "gap-1 text-xs", children: [_jsx(Clipboard, { className: "h-3.5 w-3.5" }), "Paste ma tr\u1EADn"] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: handleRandomFill, className: "gap-1 text-xs text-emerald-600 hover:text-emerald-700", children: [_jsx(Sparkles, { className: "h-3.5 w-3.5" }), "L\u1EA5p ng\u1EABu nhi\u00EAn (A-Z)"] })] })] }), pasteModalOpen && (_jsxs("div", { className: "p-3 bg-blue-50 dark:bg-blue-950/30 rounded-lg border border-blue-200 space-y-2", children: [_jsx(Label, { className: "text-xs font-bold text-blue-900 dark:text-blue-200", children: "D\u00E1n v\u0103n b\u1EA3n ma tr\u1EADn (m\u1ED7i d\u00F2ng 1 h\u00E0ng):" }), _jsx(Textarea, { value: pasteText, onChange: (e) => setPasteText(e.target.value), placeholder: `SEMONOFFP\nCROCODILE\n...`, rows: 4, className: "font-mono text-xs" }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: () => setPasteModalOpen(false), children: "H\u1EE7y" }), _jsx(Button, { size: "sm", onClick: handleApplyPaste, children: "\u00C1p d\u1EE5ng v\u00E0o ma tr\u1EADn" })] })] })), _jsx("div", { className: "overflow-x-auto pb-2 flex justify-center", children: _jsx("div", { className: "inline-grid gap-1 p-2 bg-gray-100 dark:bg-gray-900 rounded-lg border border-gray-300", style: {
|
|
247
287
|
gridTemplateColumns: `repeat(${state.cols}, minmax(0, 1fr))`,
|
|
@@ -255,7 +295,7 @@ export function FindWordsInMatrixCreator({ initialData, onChange, onUnsavedChang
|
|
|
255
295
|
? 'border-2 text-white font-extrabold shadow-sm'
|
|
256
296
|
: 'bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-100 border-gray-300'}`, style: highlight ? { backgroundColor: highlight.color, borderColor: highlight.color } : {}, title: highlight ? `${highlight.word} (${r},${c})` : `(${r},${c})` }) }, `${r}-${c}`));
|
|
257
297
|
});
|
|
258
|
-
}) }) })] }), _jsxs(Card, { className: "p-4 space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between border-b pb-2", children: [_jsxs("div", { children: [_jsx("h4", { className: "font-bold text-base", children: state.type === 'CATEGORIZE' ? 'Danh sách từ & Nhóm phân loại' : 'Danh sách từ cần tìm (Word Bank)' }), _jsxs("p", { className: "text-xs text-gray-500", children: ["Nh\u1EADp t\u1EEB v\u1EF1ng, ", state.type === 'CATEGORIZE' ? 'chọn nhóm thuộc tính, ' : '', "nh\u1EA5n \"G\u00E1n v\u1ECB tr\u00ED\" v\u00E0 click c\u00E1c \u00F4 t\u01B0\u01A1ng \u1EE9ng tr\u00EAn ma tr\u1EADn."] })] }), _jsxs(Button, { type: "button", size: "sm", onClick: handleAddWord, className: "gap-1", children: [_jsx(Plus, { className: "h-4 w-4" }), " Th\u00EAm t\u1EEB"] })] }), _jsx("div", { className: "space-y-3", children: state.words.map((w, idx) => {
|
|
298
|
+
}) }) })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white p-4 space-y-4 shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b pb-2", children: [_jsxs("div", { children: [_jsx("h4", { className: "font-bold text-base", children: state.type === 'CATEGORIZE' ? 'Danh sách từ & Nhóm phân loại' : 'Danh sách từ cần tìm (Word Bank)' }), _jsxs("p", { className: "text-xs text-gray-500", children: ["Nh\u1EADp t\u1EEB v\u1EF1ng, ", state.type === 'CATEGORIZE' ? 'chọn nhóm thuộc tính, ' : '', "nh\u1EA5n \"G\u00E1n v\u1ECB tr\u00ED\" v\u00E0 click c\u00E1c \u00F4 t\u01B0\u01A1ng \u1EE9ng tr\u00EAn ma tr\u1EADn."] })] }), _jsxs(Button, { type: "button", size: "sm", onClick: handleAddWord, className: "gap-1", children: [_jsx(Plus, { className: "h-4 w-4" }), " Th\u00EAm t\u1EEB"] })] }), _jsx("div", { className: "space-y-3", children: state.words.map((w, idx) => {
|
|
259
299
|
const isSelected = selectedWordIdx === idx;
|
|
260
300
|
const color = PALETTE[idx % PALETTE.length];
|
|
261
301
|
const pathLength = w.path?.length || 0;
|
|
@@ -68,7 +68,7 @@ export function LabelThePictureClient({ questionData, userAnswer = {}, isReviewM
|
|
|
68
68
|
});
|
|
69
69
|
// Sort labels by number
|
|
70
70
|
const sortedLabels = [...labels].sort((a, b) => a.number - b.number);
|
|
71
|
-
return (_jsxs("div", { className: "space-y-
|
|
71
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Label the Picture" }), _jsx("p", { className: "text-sm text-gray-500", children: "Nh\u00ECn tranh v\u00E0 \u0111i\u1EC1n t\u1EEB v\u00E0o c\u00E1c v\u1ECB tr\u00ED t\u01B0\u01A1ng \u1EE9ng" })] }) }), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-red-400" }), _jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-green-400" }), _jsx("span", { className: "ml-3 text-sm font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh" })] }) }), _jsx("div", { className: "flex justify-center bg-gradient-to-br from-slate-50 to-gray-100 p-4", children: _jsxs("div", { className: "relative max-h-[400px] w-full max-w-2xl overflow-hidden rounded-lg", children: [isLoadingUrl && (_jsx("div", { className: "flex h-64 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" }) })), !isLoadingUrl && (previewUrl || imageUrl) ? (
|
|
72
72
|
/* eslint-disable-next-line @next/next/no-img-element */
|
|
73
73
|
_jsx("img", { src: previewUrl || imageUrl, alt: "Label the picture", className: "h-full w-full object-contain", onError: (e) => {
|
|
74
74
|
console.error('Failed to load image:', imageUrl, 'previewUrl:', previewUrl);
|
|
@@ -76,7 +76,7 @@ export function LabelThePictureClient({ questionData, userAnswer = {}, isReviewM
|
|
|
76
76
|
const target = e.target;
|
|
77
77
|
target.onerror = null; // Prevent infinite loop
|
|
78
78
|
target.src = '/images/placeholder-image.svg';
|
|
79
|
-
} })) : (_jsxs("div", { className: "flex h-64 flex-col items-center justify-center gap-3 bg-gray-50 rounded-lg border-2 border-dashed border-gray-200", children: [_jsx("svg", { className: "h-16 w-16 text-gray-300", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }), _jsx("p", { className: "text-gray-400 font-medium", children: "Ch\u01B0a c\u00F3 h\u00ECnh \u1EA3nh" })] }))] }) })] }), wordBank.length > 0 && (_jsxs("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-gray-100 bg-gradient-to-r from-amber-50 to-orange-50 px-
|
|
79
|
+
} })) : (_jsxs("div", { className: "flex h-64 flex-col items-center justify-center gap-3 bg-gray-50 rounded-lg border-2 border-dashed border-gray-200", children: [_jsx("svg", { className: "h-16 w-16 text-gray-300", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }), _jsx("p", { className: "text-gray-400 font-medium", children: "Ch\u01B0a c\u00F3 h\u00ECnh \u1EA3nh" })] }))] }) })] }), wordBank.length > 0 && (_jsxs("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-gray-100 bg-gradient-to-r from-amber-50 to-orange-50 px-3 py-2", children: [_jsx("svg", { className: "h-5 w-5 text-amber-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" }) }), _jsx("h3", { className: "text-sm font-semibold text-gray-800", children: "Word Bank" })] }), _jsx("div", { className: "p-3 space-y-3", children: _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((word, index) => {
|
|
80
80
|
const isUsedAsPreFilled = labels.some((label) => label.isPreFilled && label.correctAnswer === word);
|
|
81
81
|
const isUsedByUser = Object.values(answers).includes(word);
|
|
82
82
|
return (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-lg border-2 px-3 py-1.5 text-sm font-semibold transition-all', isUsedAsPreFilled
|
|
@@ -4,10 +4,9 @@ import { useState, useRef, useEffect } from 'react';
|
|
|
4
4
|
import { Button } from '../../../../components/ui/button';
|
|
5
5
|
import { Label } from '../../../../components/ui/label';
|
|
6
6
|
import { Input } from '../../../../components/ui/input';
|
|
7
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
8
7
|
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { Plus, X, Image, Tags } from 'lucide-react';
|
|
9
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
11
10
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
12
11
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../../../components/ui/select';
|
|
13
12
|
import { LabelThePictureClient } from './LabelThePictureClient';
|
|
@@ -313,20 +312,25 @@ function LabelThePictureCreatorContent({ initialData, onSave, onCancel, onChange
|
|
|
313
312
|
acc[label.id] = label.correctAnswer;
|
|
314
313
|
return acc;
|
|
315
314
|
}, {});
|
|
316
|
-
return (_jsxs("div", { className: "space-y-
|
|
315
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(LabelThePictureClient, { questionData: questionData, userAnswer: correctAnswers, isReviewMode: true, explanation: explanation })] }));
|
|
317
316
|
}
|
|
318
|
-
return (_jsxs("div", { className: "space-y-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
317
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(Image, { "aria-label": "G\u1EAFn nh\u00E3n h\u00ECnh \u1EA3nh", className: "h-3.5 w-3.5" }), title: "G\u1EAFn nh\u00E3n h\u00ECnh \u1EA3nh", points: {
|
|
318
|
+
value: points,
|
|
319
|
+
onValueChange: setPoints,
|
|
320
|
+
}, preview: {
|
|
321
|
+
onClick: handlePreview,
|
|
322
|
+
} }), children: _jsxs("div", { className: "space-y-1.5", children: [_jsxs(Label, { className: "flex items-center gap-1.5 text-sm font-semibold text-gray-800", children: [_jsx(Image, { "aria-label": "H\u00ECnh \u1EA3nh", className: "h-3.5 w-3.5 text-indigo-500" }), "H\u00ECnh \u1EA3nh ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: "label-picture-image", label: "H\u00ECnh \u1EA3nh", accept: "image/*", value: imageUrl, onChange: (url) => {
|
|
323
|
+
setImageUrl(url);
|
|
324
|
+
form.clearErrors();
|
|
325
|
+
}, onPresignedUrlChange: (presignedUrl) => {
|
|
326
|
+
setImagePreviewUrl(presignedUrl);
|
|
327
|
+
}, maxSize: 5, placeholder: "Upload \u1EA3nh ho\u1EB7c paste URL", required: true, autoUpload: true, prefix: "questions" }), errors.imageUrl && (_jsx("p", { className: "mt-1 text-sm text-red-600", children: errors.imageUrl })), imagePreviewUrl && (_jsx("div", { className: "relative mt-2 h-64 w-full max-w-2xl overflow-hidden rounded border", children: _jsx("img", { src: imagePreviewUrl, alt: "Preview for question", className: "h-full w-full object-contain", onError: () => {
|
|
328
|
+
console.error('Failed to load image:', imageUrl);
|
|
329
|
+
} }) }))] }) }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-sm", children: [_jsx(CardHeader, { className: "px-3 py-2", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100", children: _jsx(Tags, { className: "h-3.5 w-3.5 text-indigo-600" }) }), _jsxs(CardTitle, { className: "text-lg font-semibold text-gray-800", children: ["Word Bank ", _jsxs("span", { className: "text-sm font-normal text-gray-500", children: ["(", wordBank.filter(w => w.trim()).length, " t\u1EEB)"] })] })] }), _jsxs(Button, { type: "button", size: "sm", onClick: addWordToBank, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700", variant: "outline", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm t\u1EEB"] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [errors.wordBank && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4", children: [_jsx("span", { className: "text-lg", children: "\u274C" }), _jsx("p", { className: "text-sm text-red-600", children: errors.wordBank })] })), _jsx("div", { className: "grid gap-3 sm:grid-cols-2", children: wordBank.map((word, index) => (_jsxs("div", { className: `group relative rounded-xl border-2 p-3 transition-all duration-200 ${word.trim()
|
|
326
330
|
? 'border-green-200 bg-gradient-to-r from-green-50 to-emerald-50'
|
|
327
331
|
: 'border-gray-200 bg-white hover:border-gray-300'}`, children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: `flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg font-bold transition-all ${word.trim()
|
|
328
332
|
? 'bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md'
|
|
329
|
-
: 'bg-gray-100 text-gray-600'}`, children: index + 1 }), _jsx(Input, { value: word, onChange: (e) => updateWordInBank(index, e.target.value), placeholder: `Từ ${index + 1}`, className: `flex-1 border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${errors.wordBankItems?.[index] ? 'border-red-400' : ''}` }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeWordFromBank(index), className: "text-gray-400 hover:bg-red-50 hover:text-red-500", children: _jsx(X, { className: "h-4 w-4" }) })] }), errors.wordBankItems?.[index] && (_jsx("p", { className: "mt-2 ml-11 text-sm text-red-600", children: errors.wordBankItems[index] }))] }, index))) }), wordBank.length === 0 && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", children: [_jsx("span", { className: "text-lg", children: "\u26A0\uFE0F" }), _jsx("p", { className: "text-sm text-amber-800", children: "Ch\u01B0a c\u00F3 t\u1EEB n\u00E0o. Nh\u1EA5n \"Th\u00EAm t\u1EEB\" \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u." })] }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-
|
|
333
|
+
: 'bg-gray-100 text-gray-600'}`, children: index + 1 }), _jsx(Input, { value: word, onChange: (e) => updateWordInBank(index, e.target.value), placeholder: `Từ ${index + 1}`, className: `flex-1 border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${errors.wordBankItems?.[index] ? 'border-red-400' : ''}` }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeWordFromBank(index), className: "text-gray-400 hover:bg-red-50 hover:text-red-500", children: _jsx(X, { className: "h-4 w-4" }) })] }), errors.wordBankItems?.[index] && (_jsx("p", { className: "mt-2 ml-11 text-sm text-red-600", children: errors.wordBankItems[index] }))] }, index))) }), wordBank.length === 0 && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", children: [_jsx("span", { className: "text-lg", children: "\u26A0\uFE0F" }), _jsx("p", { className: "text-sm text-amber-800", children: "Ch\u01B0a c\u00F3 t\u1EEB n\u00E0o. Nh\u1EA5n \"Th\u00EAm t\u1EEB\" \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u." })] }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-sm", children: [_jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-purple-100", children: _jsx(Tags, { className: "h-3.5 w-3.5 text-purple-600" }) }), _jsxs(CardTitle, { className: "text-lg font-semibold text-gray-800", children: ["V\u1ECB tr\u00ED c\u1EA7n \u0111i\u1EC1n ", _jsxs("span", { className: "text-sm font-normal text-gray-500", children: ["(", labels.length, " v\u1ECB tr\u00ED)"] })] })] }), _jsxs(Button, { type: "button", size: "sm", onClick: addLabel, className: "gap-2 border-purple-200 text-purple-600 hover:bg-purple-50 hover:text-purple-700", variant: "outline", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm v\u1ECB tr\u00ED"] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [errors.labels && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4", children: [_jsx("span", { className: "text-lg", children: "\u274C" }), _jsx("p", { className: "text-sm text-red-600", children: errors.labels })] })), _jsx("div", { className: "space-y-4", children: labels
|
|
330
334
|
.sort((a, b) => a.number - b.number)
|
|
331
335
|
.map((label) => {
|
|
332
336
|
const hasAnswer = label.correctAnswer && label.correctAnswer.trim();
|
|
@@ -337,7 +341,7 @@ function LabelThePictureCreatorContent({ initialData, onSave, onCancel, onChange
|
|
|
337
341
|
: 'bg-purple-100 text-purple-700'}`, children: label.number }), _jsxs("div", { className: "flex-1 space-y-3", children: [_jsxs("div", { children: [_jsxs(Label, { className: "text-sm font-semibold text-gray-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx("div", { className: "mt-1.5", children: _jsxs(Select, { value: label.correctAnswer || '', onValueChange: (value) => updateLabel(label.id, 'correctAnswer', value), children: [_jsx(SelectTrigger, { className: `w-full border-gray-200 transition-all focus:border-indigo-300 focus:ring-indigo-200 ${errors.labelItems?.[label.id] ? 'border-red-400' : ''}`, children: _jsx(SelectValue, { placeholder: "Ch\u1ECDn t\u1EEB trong word bank" }) }), _jsx(SelectContent, { children: wordBank
|
|
338
342
|
.filter((word) => word.trim() !== '')
|
|
339
343
|
.map((word, index) => (_jsx(SelectItem, { value: word, children: word }, index))) })] }) }), errors.labelItems?.[label.id] && (_jsx("p", { className: "mt-1 text-sm text-red-600", children: errors.labelItems[label.id] }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { type: "checkbox", id: `prefilled-${label.id}`, checked: label.isPreFilled || false, onChange: (e) => updateLabel(label.id, 'isPreFilled', e.target.checked), className: "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" }), _jsx(Label, { htmlFor: `prefilled-${label.id}`, className: "text-sm font-normal text-gray-600 cursor-pointer", children: "\u0110\u00E1nh d\u1EA5u l\u00E0 t\u1EEB g\u1EE3i \u00FD (\u0111\u00E3 \u0111i\u1EC1n s\u1EB5n)" })] })] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeLabel(label.id), className: "text-gray-400 hover:bg-red-50 hover:text-red-500", children: _jsx(X, { className: "h-4 w-4" }) })] }) }, label.id));
|
|
340
|
-
}) }), labels.length === 0 && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", children: [_jsx("span", { className: "text-lg", children: "\u26A0\uFE0F" }), _jsx("p", { className: "text-sm text-amber-800", children: "Ch\u01B0a c\u00F3 v\u1ECB tr\u00ED n\u00E0o. Nh\u1EA5n \"Th\u00EAm v\u1ECB tr\u00ED\" \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u." })] }))] })] }),
|
|
344
|
+
}) }), labels.length === 0 && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", children: [_jsx("span", { className: "text-lg", children: "\u26A0\uFE0F" }), _jsx("p", { className: "text-sm text-amber-800", children: "Ch\u01B0a c\u00F3 v\u1ECB tr\u00ED n\u00E0o. Nh\u1EA5n \"Th\u00EAm v\u1ECB tr\u00ED\" \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u." })] }))] })] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation })] }));
|
|
341
345
|
}
|
|
342
346
|
export function LabelThePictureCreator(props) {
|
|
343
347
|
return _jsx(LabelThePictureCreatorContent, { ...props });
|
|
@@ -47,9 +47,9 @@ export function ListenAndChooseFromAnswerGroupClient({ questionData, onSubmit, i
|
|
|
47
47
|
const hasAnswered = !!selectedAnswer;
|
|
48
48
|
const selectedOption = questionData.options.find((o) => o.id === selectedAnswer);
|
|
49
49
|
const isAnswerCorrect = isReviewMode && selectedAnswer === resolvedCorrectAnswer;
|
|
50
|
-
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-
|
|
50
|
+
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gray-50 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100 text-gray-600", children: _jsx(Volume2, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 ch\u1ECDn trong nh\u00F3m \u0111\u00E1p \u00E1n" })] }), isReviewMode && hasAnswered && (_jsx("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAnswerCorrect
|
|
51
51
|
? 'bg-green-100 text-green-700'
|
|
52
|
-
: 'bg-red-100 text-red-700'), children: isAnswerCorrect ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-3.5 w-3.5" }), " \u0110\u00FAng"] })) : (_jsxs(_Fragment, { children: [_jsx(X, { className: "h-3.5 w-3.5" }), " Sai"] })) }))] }), _jsxs("div", { className: "p-
|
|
52
|
+
: 'bg-red-100 text-red-700'), children: isAnswerCorrect ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-3.5 w-3.5" }), " \u0110\u00FAng"] })) : (_jsxs(_Fragment, { children: [_jsx(X, { className: "h-3.5 w-3.5" }), " Sai"] })) }))] }), _jsxs("div", { className: "p-3 space-y-3", children: [questionData.audioUrl && (_jsx(QuestionAudioPlayer, { audioUrl: questionData.audioUrl })), _jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3", children: questionData.options.map((option, optIndex) => {
|
|
53
53
|
const { isSelected, isCorrect, isWrong } = getOptionStatus(option.id);
|
|
54
54
|
return (_jsxs("button", { type: "button", onClick: () => handleOptionSelect(option.id), disabled: isReviewMode, className: cn('group relative flex flex-col items-center gap-2 rounded-xl border-2 p-3 transition-all', isReviewMode
|
|
55
55
|
? isCorrect
|
|
@@ -3,19 +3,18 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState, useRef, useEffect } from 'react';
|
|
4
4
|
import { Button } from '../../../../components/ui/button';
|
|
5
5
|
import { Input } from '../../../../components/ui/input';
|
|
6
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
7
6
|
import { Label } from '../../../../components/ui/label';
|
|
8
|
-
import { Card, CardContent, CardHeader
|
|
9
|
-
import {
|
|
7
|
+
import { Card, CardContent, CardHeader } from '../../../../components/ui/card';
|
|
8
|
+
import { Play, Pause, Plus, Trash2, Music, CheckCircle2, ListChecks, ImageIcon } from 'lucide-react';
|
|
10
9
|
import { toast } from 'sonner';
|
|
11
10
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
11
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
12
12
|
import { ListenAndChooseFromAnswerGroupClient } from './ListenAndChooseFromAnswerGroupClient';
|
|
13
13
|
import { usePresignedFileUrl, useAudioPreview, useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
14
14
|
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
15
15
|
import { listenAndChooseFromAnswerGroupFormSchema } from '../../../../shared/lib/validation/listen-and-choose-from-answer-group.validation';
|
|
16
16
|
import { useAnswerGroupQuestionGroup } from '../../_shared/hooks/useAnswerGroupQuestionGroup';
|
|
17
17
|
import { generateObjectId } from '../../_shared/utils/id-generator';
|
|
18
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
19
18
|
// ─── Sub-component: Audio preview ────────────────────────────────────────────
|
|
20
19
|
function AudioPreview({ audioUrl }) {
|
|
21
20
|
const { previewUrl } = usePresignedFileUrl(audioUrl);
|
|
@@ -190,34 +189,35 @@ function ListenAndChooseFromAnswerGroupCreatorContent({ initialData, onSave, onC
|
|
|
190
189
|
explanation,
|
|
191
190
|
isBackup,
|
|
192
191
|
};
|
|
193
|
-
return (_jsxs("div", { className: "space-y-
|
|
192
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(ListenAndChooseFromAnswerGroupClient, { questionData: questionData, onSubmit: () => { }, isReviewMode: true, userAnswer: correctAnswer || undefined, correctAnswer: correctAnswer || undefined, explanation: explanation })] }));
|
|
194
193
|
}
|
|
195
194
|
// ─── Creator UI ────────────────────────────────────────────────────────────
|
|
196
|
-
return (_jsxs("div", { className: "space-y-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
195
|
+
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: {
|
|
196
|
+
value: points ?? 1,
|
|
197
|
+
onValueChange: (value) => {
|
|
198
|
+
setPoints(value);
|
|
199
|
+
form.clearErrors('points');
|
|
200
|
+
},
|
|
201
|
+
error: form.formState.errors.points?.message,
|
|
202
|
+
}, preview: {
|
|
203
|
+
onClick: () => setIsClientMode(true),
|
|
204
|
+
disabled: options.filter((o) => o.imageUrl).length < 2 || !audioUrl,
|
|
205
|
+
} }), 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(ListChecks, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsxs("p", { className: "text-sm font-semibold text-gray-800", children: ["Nh\u00F3m \u0111\u00E1p \u00E1n (", options.length, " \u0111\u00E1p \u00E1n)"] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: addOption, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n"] })] }), contentClassName: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [form.formState.errors.options?.root && (_jsx("p", { className: "col-span-full text-sm text-red-500", children: form.formState.errors.options.root.message })), options.map((option, index) => (_jsxs(Card, { className: "border border-gray-200", children: [_jsx(CardHeader, { className: "pb-2 pt-3 px-4", children: _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-lg bg-gradient-to-br from-amber-500 to-orange-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: ["\u0110\u00E1p \u00E1n ", String.fromCharCode(65 + index)] })] }), options.length > 2 && (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => removeOption(option.id), 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" }) }))] }) }), _jsxs(CardContent, { className: "space-y-3 px-4 pb-4", children: [_jsxs("div", { children: [_jsx(FileUpload, { id: `option-image-${option.id}`, label: "\u1EA2nh \u0111\u00E1p \u00E1n", accept: "image/*", value: option.imageUrl, onChange: (url) => updateOptionImageUrl(option.id, url), maxSize: 5, placeholder: "Upload \u1EA3nh cho \u0111\u00E1p \u00E1n", required: true, autoUpload: true, prefix: "questions" }), form.formState.errors.options?.[index]?.imageUrl && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.options[index].imageUrl.message })), option.imageUrl && _jsx(OptionImagePreview, { imageUrl: option.imageUrl })] }), _jsxs("div", { children: [_jsx(Label, { className: "font-medium", children: "Nh\u00E3n" }), _jsx(Input, { value: option.label || '', onChange: (e) => updateOptionLabel(option.id, e.target.value), placeholder: "Nh\u1EADp label / m\u00F4 t\u1EA3 ng\u1EAFn", className: "mt-1" })] })] })] }, option.id))), options.length === 0 && (_jsxs("div", { className: "col-span-1 md:col-span-2 flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-amber-300 p-8 bg-amber-50/50", children: [_jsx(ImageIcon, { className: "h-10 w-10 text-amber-400 mb-2" }), _jsx("p", { className: "text-sm text-gray-500 mb-3", children: "Ch\u01B0a c\u00F3 \u0111\u00E1p \u00E1n n\u00E0o" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: addOption, className: "gap-2", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n \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(Music, { 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" })] }), children: [_jsxs("div", { children: [_jsx(FileUpload, { id: "question-audio", label: "File \u00E2m thanh", accept: "audio/*", value: audioUrl, onChange: (url) => {
|
|
206
|
+
setAudioUrl(url);
|
|
207
|
+
form.clearErrors('audioUrl');
|
|
208
|
+
}, maxSize: 10, placeholder: "Upload file audio", required: true, autoUpload: true, prefix: "questions" }), form.formState.errors.audioUrl && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.audioUrl.message })), audioUrl && _jsx(AudioPreview, { audioUrl: audioUrl })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-sm font-semibold text-gray-700 mb-2 block", children: "Ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng *" }), options.filter((o) => o.imageUrl).length > 0 ? (_jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3", children: options.map((option, optIndex) => {
|
|
209
|
+
if (!option.imageUrl)
|
|
210
|
+
return null;
|
|
211
|
+
const isSelected = correctAnswer === option.id;
|
|
212
|
+
return (_jsxs("button", { type: "button", onClick: () => {
|
|
213
|
+
setCorrectAnswer(option.id);
|
|
214
|
+
form.clearErrors('correctAnswer');
|
|
215
|
+
}, className: `relative text-left rounded-xl border-2 p-2 transition-all ${isSelected
|
|
216
|
+
? 'border-green-500 bg-green-50 ring-2 ring-green-200 shadow-md'
|
|
217
|
+
: '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
|
|
218
|
+
? 'bg-green-500 text-white'
|
|
219
|
+
: 'bg-gray-100 text-gray-600'}`, children: String.fromCharCode(65 + optIndex) }), _jsx(OptionImagePreviewInline, { imageUrl: option.imageUrl }), option.label && (_jsx("p", { className: `text-xs text-center ${isSelected ? 'text-green-700 font-medium' : 'text-gray-600'}`, children: option.label }))] }), isSelected && (_jsx("div", { className: "absolute top-1 right-1", children: _jsx(CheckCircle2, { className: "h-5 w-5 text-green-500" }) }))] }, option.id));
|
|
220
|
+
}) })) : (_jsx("p", { className: "text-sm text-amber-600 italic", children: "Vui l\u00F2ng upload \u1EA3nh cho \u0111\u00E1p \u00E1n \u1EDF ph\u1EA7n tr\u00EAn tr\u01B0\u1EDBc" })), form.formState.errors.correctAnswer && (_jsx("div", { className: "mt-3 rounded-lg border border-red-200 bg-red-50 p-3", children: _jsxs("p", { className: "text-sm text-red-700", children: ["\u26A0\uFE0F ", form.formState.errors.correctAnswer.message] }) })), !form.formState.errors.correctAnswer && !correctAnswer && options.filter((o) => o.imageUrl).length > 0 && (_jsx("p", { className: "mt-1 text-xs text-amber-600", children: "Ch\u01B0a ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" }))] })] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(explanation) })] }));
|
|
221
221
|
}
|
|
222
222
|
// ─── Inline image preview (for correct answer selection) ─────────────────────
|
|
223
223
|
function OptionImagePreviewInline({ imageUrl }) {
|
|
@@ -55,9 +55,9 @@ export function ListenAndChooseObjectsInSceneClient({ questionData, onSubmit, is
|
|
|
55
55
|
const hasAnswered = selectedObjectId !== '';
|
|
56
56
|
const selectedNode = questionData.objectNodes.find((n) => n.id === selectedObjectId);
|
|
57
57
|
const isAnswerCorrect = isReviewMode && correctAnswer && selectedObjectId === correctAnswer.objectId;
|
|
58
|
-
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-
|
|
58
|
+
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gray-50 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100 text-gray-600", children: _jsx(CircleHelp, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 ch\u1ECDn \u0111\u1ED1i t\u01B0\u1EE3ng trong c\u1EA3nh" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAnswerCorrect
|
|
59
59
|
? 'bg-green-100 text-green-700'
|
|
60
|
-
: 'bg-red-100 text-red-700'), children: [isAnswerCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5" })) : (_jsx(X, { className: "h-3.5 w-3.5" })), isAnswerCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-
|
|
60
|
+
: 'bg-red-100 text-red-700'), children: [isAnswerCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5" })) : (_jsx(X, { className: "h-3.5 w-3.5" })), isAnswerCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [audioUrl && (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-white", children: _jsx(Volume2, { className: "h-4 w-4" }) }), _jsx("audio", { controls: true, className: "h-8 w-full", src: audioUrl, children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." })] })), _jsxs("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gradient-to-br from-slate-50 to-gray-100", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-1.5", children: _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-2 w-2 rounded-full bg-red-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-green-400" }), _jsx("span", { className: "ml-2 text-xs font-medium text-gray-500", children: "Scene" })] }) }), _jsx("div", { className: "relative p-2", children: bgLoading ? (_jsx("div", { className: "flex h-64 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" }) })) : backgroundUrl ? (_jsxs("div", { className: "relative w-full", children: [_jsx("img", { src: backgroundUrl, alt: "Scene background", className: "w-full rounded-md object-contain", draggable: false }), questionData.objectNodes.map((node) => {
|
|
61
61
|
const isSelected = selectedObjectId === node.id;
|
|
62
62
|
const isCorrectNode = isReviewMode && correctAnswer?.objectId === node.id;
|
|
63
63
|
const isWrongNode = isReviewMode && isSelected && correctAnswer?.objectId !== node.id;
|