@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
|
@@ -5,13 +5,12 @@ import { Button } from '../../../../components/ui/button';
|
|
|
5
5
|
import { Label } from '../../../../components/ui/label';
|
|
6
6
|
import { Input } from '../../../../components/ui/input';
|
|
7
7
|
import { Textarea } from '../../../../components/ui/textarea';
|
|
8
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
9
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
10
8
|
import { Switch } from '../../../../components/ui/switch';
|
|
11
9
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
12
10
|
import { RichTextEditor } from '../../../../components/shared/RichTextEditor';
|
|
13
11
|
import { useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
14
|
-
import {
|
|
12
|
+
import { BookOpen, HelpCircle, ImageIcon, Plus, Trash2, Type, Volume2 } from 'lucide-react';
|
|
13
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
15
14
|
import { ChooseTheCorrectAnswerGroupClient } from './ChooseTheCorrectAnswerGroupClient';
|
|
16
15
|
import { ChooseAnswerGroupImageUploadItem, ChooseAnswerGroupOptionImageField, } from './ChooseAnswerGroupImageUpload';
|
|
17
16
|
import { fromChooseAnswerGroupImageUrls, getChooseAnswerGroupOptionImageSrc, isChooseAnswerGroupOptionFilled, toChooseAnswerGroupImageUrls, toEditableChooseAnswerGroupImageUrls, } from '../../_shared/types/choose-the-correct-answer-group.type';
|
|
@@ -59,13 +58,6 @@ function normalizeItems(rawItems) {
|
|
|
59
58
|
function sumPoints(items) {
|
|
60
59
|
return items.reduce((total, item) => (item.isExample ? total : total + (typeof item.points === 'number' ? item.points : 1)), 0);
|
|
61
60
|
}
|
|
62
|
-
function parsePointsValue(raw, allowZero) {
|
|
63
|
-
const parsed = Number.parseInt(raw.replace(/[^0-9]/g, ''), 10);
|
|
64
|
-
if (Number.isNaN(parsed)) {
|
|
65
|
-
return allowZero ? 0 : 1;
|
|
66
|
-
}
|
|
67
|
-
return allowZero ? Math.max(0, parsed) : Math.max(1, parsed);
|
|
68
|
-
}
|
|
69
61
|
export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, externalErrors, onUnsavedChangesChange, validationRef, }) {
|
|
70
62
|
const [instruction, setInstruction] = useState(initialData?.instruction || 'Choose the correct answer.');
|
|
71
63
|
const [passage, setPassage] = useState(initialData?.passage || '');
|
|
@@ -75,6 +67,13 @@ export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, exte
|
|
|
75
67
|
const [explanation, setExplanation] = useState(initialData?.explanation || '');
|
|
76
68
|
const [isClientMode, setIsClientMode] = useState(false);
|
|
77
69
|
const [errors, setErrors] = useState([]);
|
|
70
|
+
const [isGroupAudioVisible, setIsGroupAudioVisible] = useState(() => Boolean(initialData?.audioUrl));
|
|
71
|
+
const [isGroupImageVisible, setIsGroupImageVisible] = useState(() => toChooseAnswerGroupImageUrls(initialData?.imageUrl).length > 0);
|
|
72
|
+
const [isPassageVisible, setIsPassageVisible] = useState(() => {
|
|
73
|
+
const html = initialData?.passage;
|
|
74
|
+
return Boolean(html && html !== '<p></p>' && html !== '<p><br></p>' && html.trim());
|
|
75
|
+
});
|
|
76
|
+
const [visibleItemAudio, setVisibleItemAudio] = useState({});
|
|
78
77
|
const onChangeRef = useRef(onChange);
|
|
79
78
|
const previousPayloadRef = useRef('');
|
|
80
79
|
useEffect(() => {
|
|
@@ -164,26 +163,43 @@ export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, exte
|
|
|
164
163
|
});
|
|
165
164
|
};
|
|
166
165
|
if (isClientMode) {
|
|
167
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsx(
|
|
166
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(ChooseTheCorrectAnswerGroupClient, { questionData: buildPayload(), isReviewMode: true })] }));
|
|
168
167
|
}
|
|
169
168
|
const allErrors = [...errors, ...(externalErrors || [])];
|
|
170
|
-
return (_jsxs("div", { className: "space-y-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
169
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(HelpCircle, { className: "h-3.5 w-3.5" }), title: "Nh\u00F3m ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng", points: {
|
|
170
|
+
value: sumPoints(items),
|
|
171
|
+
onValueChange: () => undefined,
|
|
172
|
+
allowZero: true,
|
|
173
|
+
}, preview: { onClick: () => setIsClientMode(true) } }), children: [_jsxs("div", { className: "space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx(Label, { htmlFor: "group-instruction", className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", title: "Th\u00EAm audio chung", onClick: () => setIsGroupAudioVisible(true), className: `inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors ${audioUrl || isGroupAudioVisible
|
|
174
|
+
? 'bg-violet-600 text-white shadow-sm'
|
|
175
|
+
: 'bg-violet-50 text-violet-600 hover:bg-violet-100'}`, children: _jsx(Volume2, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", title: "Th\u00EAm h\u00ECnh \u1EA3nh chung", onClick: () => setIsGroupImageVisible(true), className: `inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors ${imageUrl || isGroupImageVisible
|
|
176
|
+
? 'bg-indigo-600 text-white shadow-sm'
|
|
177
|
+
: 'bg-indigo-50 text-indigo-600 hover:bg-indigo-100'}`, children: _jsx(ImageIcon, { className: "h-4 w-4" }) })] })] }), _jsx(Input, { id: "group-instruction", value: instruction, onChange: (event) => setInstruction(event.target.value), placeholder: "Choose the correct answer.", className: "h-8 border-gray-200" })] }), isGroupAudioVisible && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-gray-200 bg-white p-1.5", children: [_jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-indigo-50", children: _jsx(Volume2, { className: "h-3.5 w-3.5 text-indigo-500" }) }), _jsx("div", { className: "min-w-0 flex-1", children: _jsx(FileUpload, { id: "choose-answer-group-audio", label: "", accept: "audio/*", value: audioUrl, onChange: setAudioUrl, maxSize: 20, placeholder: "Upload file audio ho\u1EB7c paste URL", autoUpload: true, prefix: "questions", showPlayButton: true }) })] })), isGroupImageVisible && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-gray-200 bg-white p-1.5", children: [_jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-indigo-50", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5 text-indigo-500" }) }), _jsx("div", { className: "min-w-0 flex-1", children: _jsx(FileUpload, { id: "choose-answer-group-image", label: "", accept: "image/*", value: imageUrl, onChange: setImageUrl, maxSize: 5, placeholder: "Upload \u1EA3nh nh\u00F3m ho\u1EB7c paste URL", autoUpload: true, prefix: "questions" }) })] })), !isPassageVisible ? (_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setIsPassageVisible(true), className: "h-8 gap-1.5 border-gray-200 px-2.5 text-xs text-gray-600 hover:bg-gray-50", children: [_jsx(BookOpen, { className: "h-3.5 w-3.5" }), "Th\u00EAm n\u1ED9i dung b\u00E0i \u0111\u1ECDc"] })) : (_jsxs("div", { className: "relative space-y-1.5 pr-10", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setIsPassageVisible(false), className: "absolute right-0 top-0 z-10 h-7 px-2 text-xs text-gray-500 hover:text-gray-700", children: "\u1EA8n" }), _jsxs(Label, { className: "text-sm font-semibold text-gray-800", children: ["N\u1ED9i dung b\u00E0i \u0111\u1ECDc ", _jsx("span", { className: "text-xs font-normal text-gray-500", children: "(tu\u1EF3 ch\u1ECDn)" })] }), _jsx(RichTextEditor, { value: passage, onChange: setPassage, variant: "compact", minHeightClassName: "min-h-[72px]" })] }))] }), items.map((item, itemIndex) => (_jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(HelpCircle, { className: "h-3.5 w-3.5" }), title: `Câu ${itemIndex + 1}`, points: !item.isExample ? {
|
|
178
|
+
value: item.points,
|
|
179
|
+
allowZero: true,
|
|
180
|
+
onValueChange: (value) => updateItem(itemIndex, { points: value }),
|
|
181
|
+
} : undefined, example: {
|
|
182
|
+
checked: item.isExample === true,
|
|
183
|
+
onChange: (checked) => updateItem(itemIndex, {
|
|
184
|
+
isExample: checked,
|
|
185
|
+
points: checked ? 0 : (item.points || 1),
|
|
186
|
+
}),
|
|
187
|
+
}, extraActions: _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeItem(itemIndex), disabled: items.length <= 1, className: "h-8 w-8 text-gray-400 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }) }), children: [_jsxs("div", { className: "space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx(Label, { className: "text-sm font-semibold text-gray-800", children: "C\u00E2u h\u1ECFi" }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", title: "Th\u00EAm audio", onClick: () => setVisibleItemAudio((prev) => ({ ...prev, [itemIndex]: true })), className: `inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors ${item.audioUrl || visibleItemAudio[itemIndex]
|
|
188
|
+
? 'bg-violet-600 text-white shadow-sm'
|
|
189
|
+
: 'bg-violet-50 text-violet-600 hover:bg-violet-100'}`, children: _jsx(Volume2, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", title: "Th\u00EAm h\u00ECnh \u1EA3nh", onClick: () => {
|
|
174
190
|
const nextUrls = [...toEditableChooseAnswerGroupImageUrls(item.imageUrl), ''];
|
|
175
191
|
updateItem(itemIndex, { imageUrl: nextUrls });
|
|
176
|
-
}, className:
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
192
|
+
}, className: `inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors ${toEditableChooseAnswerGroupImageUrls(item.imageUrl).length > 0
|
|
193
|
+
? 'bg-indigo-600 text-white shadow-sm'
|
|
194
|
+
: 'bg-indigo-50 text-indigo-600 hover:bg-indigo-100'}`, children: _jsx(ImageIcon, { className: "h-4 w-4" }) })] })] }), _jsx(Textarea, { value: item.question, onChange: (event) => updateItem(itemIndex, { question: event.target.value }), rows: 2, placeholder: "A place for someone to stay or live is called", className: "min-h-[56px] border-gray-200" })] }), (item.audioUrl || visibleItemAudio[itemIndex]) && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-gray-200 bg-white p-1.5", children: [_jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-indigo-50", children: _jsx(Volume2, { className: "h-3.5 w-3.5 text-indigo-500" }) }), _jsx("div", { className: "min-w-0 flex-1", children: _jsx(FileUpload, { id: `choose-answer-item-audio-${itemIndex}`, label: "", accept: "audio/*", value: item.audioUrl || '', onChange: (url) => updateItem(itemIndex, { audioUrl: url }), maxSize: 20, placeholder: "Upload file audio ho\u1EB7c paste URL", autoUpload: true, prefix: "questions", showPlayButton: true }) })] })), toEditableChooseAnswerGroupImageUrls(item.imageUrl).length > 0 && (_jsx("div", { className: "space-y-2", children: toEditableChooseAnswerGroupImageUrls(item.imageUrl).map((imageUrl, imageIndex) => (_jsx(ChooseAnswerGroupImageUploadItem, { index: imageIndex, itemIndex: itemIndex, imageUrl: imageUrl, onChange: (url) => {
|
|
195
|
+
const nextUrls = toEditableChooseAnswerGroupImageUrls(item.imageUrl);
|
|
196
|
+
nextUrls[imageIndex] = url;
|
|
197
|
+
updateItem(itemIndex, { imageUrl: nextUrls });
|
|
198
|
+
}, onRemove: () => {
|
|
199
|
+
const nextUrls = toEditableChooseAnswerGroupImageUrls(item.imageUrl)
|
|
200
|
+
.filter((_, idx) => idx !== imageIndex);
|
|
201
|
+
updateItem(itemIndex, { imageUrl: fromChooseAnswerGroupImageUrls(nextUrls) });
|
|
202
|
+
} }, `${itemIndex}-${imageIndex}`))) })), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { className: "text-sm font-semibold text-gray-800", children: "\u0110\u00E1p \u00E1n" }), _jsxs("div", { className: "flex h-8 items-center gap-1.5 rounded-md border border-gray-200 bg-gray-50 px-2.5", children: [_jsx(Type, { className: `h-3.5 w-3.5 ${item.optionType === 'text' ? 'text-indigo-600' : 'text-gray-400'}` }), _jsx("span", { className: `text-xs font-medium ${item.optionType === 'text' ? 'text-indigo-600' : 'text-gray-400'}`, children: "Text" }), _jsx(Switch, { checked: item.optionType === 'image', onCheckedChange: (checked) => updateItem(itemIndex, {
|
|
203
|
+
optionType: checked ? 'image' : 'text',
|
|
204
|
+
}) }), _jsx(ImageIcon, { className: `h-3.5 w-3.5 ${item.optionType === 'image' ? 'text-indigo-600' : 'text-gray-400'}` }), _jsx("span", { className: `text-xs font-medium ${item.optionType === 'image' ? 'text-indigo-600' : 'text-gray-400'}`, children: "\u1EA2nh" })] })] }), item.options.map((option, optionIndex) => (_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("input", { type: "radio", name: `correct-${itemIndex}`, checked: item.correctAnswer === option.id, onChange: () => updateItem(itemIndex, { correctAnswer: option.id }), className: "mt-2" }), _jsx("span", { className: "mt-2 w-6 text-sm font-semibold text-gray-600", children: OPTION_LABELS[optionIndex] || optionIndex + 1 }), item.optionType === 'image' ? (_jsx(ChooseAnswerGroupOptionImageField, { itemIndex: itemIndex, optionIndex: optionIndex, imageUrl: getChooseAnswerGroupOptionImageSrc(option), onChange: (url) => updateOption(itemIndex, optionIndex, { imageUrl: url, text: '' }) })) : (_jsx(Input, { value: option.text, onChange: (event) => updateOption(itemIndex, optionIndex, { text: event.target.value }), placeholder: `Đáp án ${OPTION_LABELS[optionIndex]}`, className: "h-8 border-gray-200" }))] }, option.id)))] })] }, `item-${itemIndex}`))), _jsxs(Button, { type: "button", variant: "outline", onClick: addItem, className: "h-8 gap-1.5 border-dashed border-green-200 px-2.5 text-xs text-green-600 hover:bg-green-50 hover:text-green-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm c\u00E2u h\u1ECFi"] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) }), allErrors.length > 0 && (_jsx("div", { className: "rounded-md border border-red-200 bg-red-50 p-2 text-xs text-red-700", children: allErrors.map((error) => (_jsx("p", { children: error }, error))) }))] }));
|
|
189
205
|
}
|
package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js
CHANGED
|
@@ -23,12 +23,12 @@ export function ChooseThenAnswerGroupClient({ questionData, isReviewMode = false
|
|
|
23
23
|
text: next.text ?? current.text,
|
|
24
24
|
});
|
|
25
25
|
};
|
|
26
|
-
return (_jsxs("div", { className: "
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-gray-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-full bg-slate-800 text-xs font-bold text-white", children: item.questionNumber || itemIndex + 1 }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-[10px] font-semibold uppercase text-amber-800", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsxs("p", { className: "flex flex-wrap items-center gap-1.5 text-[15px] leading-relaxed text-slate-800", children: [_jsx("span", { children: parts[0] || '' }), _jsx("span", { className: "inline-flex items-center gap-1", children: item.options.map((option, optionIndex) => {
|
|
27
|
+
const selected = current.optionId === option.id;
|
|
28
|
+
const showReview = isReviewMode && !item.isExample && Boolean(expectedId);
|
|
29
|
+
const isCorrect = showReview && option.id === expectedId;
|
|
30
|
+
const isWrong = showReview && selected && option.id !== expectedId;
|
|
31
|
+
return (_jsxs("span", { className: "inline-flex items-center gap-1", children: [optionIndex > 0 && _jsx("span", { className: "text-slate-400", children: "|" }), _jsx("button", { type: "button", disabled: !canEdit || item.isExample, onClick: () => setAnswer({ optionId: option.id }), className: cn('rounded-md border px-2.5 py-1 text-sm transition-colors', selected && !isCorrect && !isWrong && 'border-black bg-black text-white', !selected && !isCorrect && !isWrong && 'border-gray-300 bg-white text-gray-900', isCorrect && 'border-emerald-500 bg-emerald-50 text-emerald-900', isWrong && 'border-red-500 bg-red-50 text-red-900', (!canEdit || item.isExample) && 'cursor-default'), children: option.text || option.id })] }, option.id));
|
|
32
|
+
}) }), _jsx("span", { children: parts.slice(1).join('{0}') })] }), _jsx("textarea", { value: current.text, disabled: !canEdit || item.isExample, onChange: (event) => setAnswer({ text: event.target.value }), placeholder: "Answer for you", rows: 3, className: "w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm text-slate-800 outline-none focus:border-slate-400 disabled:bg-slate-50" })] })] }, `${item.questionNumber}-${itemIndex}`));
|
|
33
33
|
}), isReviewMode && questionData.explanation && (_jsxs("div", { className: "flex items-start gap-2 rounded-xl border border-amber-100 bg-amber-50 px-4 py-3 text-sm text-amber-800", children: [_jsx(Lightbulb, { className: "mt-0.5 h-4 w-4 flex-shrink-0" }), questionData.explanation] }))] }));
|
|
34
34
|
}
|
package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { Button } from '../../../../components/ui/button';
|
|
5
5
|
import { Label } from '../../../../components/ui/label';
|
|
6
6
|
import { Input } from '../../../../components/ui/input';
|
|
7
7
|
import { PointsInput } from '../../../../components/ui/points-input';
|
|
8
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
9
8
|
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
10
9
|
import { Switch } from '../../../../components/ui/switch';
|
|
11
10
|
import { useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
12
|
-
import {
|
|
11
|
+
import { ListChecks, Plus, Star, Trash2 } from 'lucide-react';
|
|
12
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
13
13
|
import { ChooseThenAnswerGroupClient } from './ChooseThenAnswerGroupClient';
|
|
14
14
|
import { createEmptyItem, createEmptyOption, sumGradablePoints, } from './map-choose-then-answer-group-data';
|
|
15
15
|
import { CHOOSE_THEN_ANSWER_GROUP_DEFAULT_INSTRUCTION } from '../../_shared/types/choose-then-answer-group.type';
|
|
@@ -168,7 +168,10 @@ export function ChooseThenAnswerGroupCreator({ initialData, onChange, externalEr
|
|
|
168
168
|
})));
|
|
169
169
|
};
|
|
170
170
|
const displayErrors = [...errors, ...(externalErrors || [])];
|
|
171
|
-
|
|
171
|
+
if (isClientMode) {
|
|
172
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(ChooseThenAnswerGroupClient, { questionData: buildPayload(), isReviewMode: true })] }));
|
|
173
|
+
}
|
|
174
|
+
return (_jsxs("div", { className: "space-y-3", children: [displayErrors.length > 0 && (_jsx("ul", { className: "list-disc space-y-1 rounded-md border border-red-200 bg-red-50 p-2 pl-6 text-xs text-red-700", children: displayErrors.map((error) => (_jsx("li", { children: error }, error))) })), _jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(ListChecks, { className: "h-3.5 w-3.5" }), title: "Ch\u1ECDn r\u1ED3i tr\u1EA3 l\u1EDDi (Nh\u00F3m)", preview: { onClick: () => setIsClientMode(true) } }), children: _jsxs("div", { className: "grid gap-2.5 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "cta-title", className: "text-sm font-semibold text-gray-800", children: "Ti\u00EAu \u0111\u1EC1 (optional)" }), _jsx(Input, { id: "cta-title", value: title, onChange: (event) => setTitle(event.target.value), placeholder: "GRAMMAR", className: "h-10 border-gray-200 bg-white" })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "cta-instruction", className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(Input, { id: "cta-instruction", value: instruction, onChange: (event) => setInstruction(event.target.value), placeholder: CHOOSE_THEN_ANSWER_GROUP_DEFAULT_INSTRUCTION, className: "h-10 border-gray-200 bg-white" })] })] }) }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-sm", children: [_jsx(CardHeader, { className: "px-3 py-2", children: _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs(CardTitle, { className: "text-sm font-semibold text-gray-800", children: ["Danh s\u00E1ch c\u00E2u (", items.length, ")"] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addItem, className: "h-8 gap-1.5 px-2.5 text-xs", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), " Th\u00EAm c\u00E2u"] })] }) }), _jsxs(CardContent, { className: "space-y-2.5 px-3 pb-3", children: [items.map((item, index) => (_jsxs("div", { className: "space-y-2.5 rounded-md border border-gray-200 bg-white px-3 py-2.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", index + 1] }), items.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeItem(index), className: "h-7 w-7 text-red-500 hover:bg-red-50", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }), _jsx(Input, { value: item.question, onChange: (event) => updateItem(index, { question: event.target.value }), placeholder: "Did you eat {0} for lunch today?", className: "h-10 border-gray-200 bg-white" }), _jsxs("p", { className: "text-xs text-slate-500", children: ["D\u00F9ng ", '{0}', " cho ch\u1ED7 hi\u1EC7n chip ch\u1ECDn."] }), item.options.map((option, optionIndex) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { value: option.text, onChange: (event) => updateOption(index, optionIndex, event.target.value), placeholder: `Lựa chọn ${optionIndex + 1}`, className: "h-10 border-gray-200 bg-white" }), item.options.length > 2 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeOption(index, optionIndex), className: "h-7 w-7 text-red-500 hover:bg-red-50", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }, option.id))), _jsxs(Button, { type: "button", size: "sm", variant: "outline", onClick: () => addOption(index), className: "h-8 gap-1.5 px-2.5 text-xs", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), " Th\u00EAm l\u1EF1a ch\u1ECDn"] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("select", { className: "h-10 rounded-md border border-input bg-background px-3 text-sm", value: item.optionId, onChange: (event) => updateItem(index, { optionId: event.target.value }), children: item.options.map((option) => (_jsx("option", { value: option.id, children: option.text || option.id }, option.id))) }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Switch, { id: `cta-example-${index}`, checked: item.isExample === true, onCheckedChange: (checked) => updateItem(index, { isExample: checked }) }), _jsxs(Label, { htmlFor: `cta-example-${index}`, className: "flex cursor-pointer items-center gap-1 text-xs", children: [_jsx(Star, { className: "h-3 w-3 text-amber-500" }), "Example"] })] }), !item.isExample && (_jsx(PointsInput, { value: item.points, allowZero: true, onChange: (event) => updateItem(index, {
|
|
172
175
|
points: Number.parseInt(event.target.value, 10) || 1,
|
|
173
|
-
}) }))] })] }
|
|
176
|
+
}) }))] })] }, `${item.questionNumber}-${index}`))), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) })] })] })] }));
|
|
174
177
|
}
|
|
@@ -20,8 +20,8 @@ export function CrossOutWordGroupClient({ questionData, isReviewMode = false, us
|
|
|
20
20
|
return (_jsxs("div", { className: "space-y-4", children: [questionData.instruction && (_jsx("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800", children: questionData.instruction })), items.map((item, itemIndex) => {
|
|
21
21
|
const userValue = userAnswers[itemIndex];
|
|
22
22
|
const isReplace = item.type === 'CROSS_OUT_AND_REPLACE';
|
|
23
|
-
return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-
|
|
23
|
+
return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || itemIndex + 1] }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] })), _jsx("span", { className: cn('rounded-full px-2 py-0.5 text-xs font-semibold', isReplace
|
|
24
24
|
? 'bg-violet-100 text-violet-700'
|
|
25
|
-
: 'bg-sky-100 text-sky-700'), children: isReplace ? 'Gạch + thay' : 'Chỉ gạch' })] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-3 p-
|
|
25
|
+
: 'bg-sky-100 text-sky-700'), children: isReplace ? 'Gạch + thay' : 'Chỉ gạch' })] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: item.question || 'Câu hỏi chưa được nhập' }), _jsxs("div", { className: "rounded-lg border border-gray-100 bg-gray-50 px-3 py-2 text-sm text-gray-700", children: [_jsx("span", { className: "font-medium text-gray-500", children: "\u0110\u00E1p \u00E1n: " }), formatAnswer(item.correctAnswer)] }), isReviewMode && (_jsxs("div", { className: "rounded-lg border border-blue-100 bg-blue-50 px-3 py-2 text-sm text-blue-800", children: [_jsx("span", { className: "font-medium", children: "H\u1ECDc sinh: " }), formatAnswer(userValue)] }))] })] }, `${item.questionNumber}-${itemIndex}`));
|
|
26
26
|
}), isReviewMode && questionData.explanation && (_jsxs("div", { className: "flex items-start gap-2 rounded-xl border border-amber-100 bg-amber-50 px-4 py-3 text-sm text-amber-800", children: [_jsx(Lightbulb, { className: "mt-0.5 h-4 w-4 flex-shrink-0" }), questionData.explanation] }))] }));
|
|
27
27
|
}
|
|
@@ -10,7 +10,8 @@ import { Card, CardContent, CardHeader, CardTitle } from '../../../../components
|
|
|
10
10
|
import { Switch } from '../../../../components/ui/switch';
|
|
11
11
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../../../components/ui/select';
|
|
12
12
|
import { useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
13
|
-
import {
|
|
13
|
+
import { HelpCircle, Plus, Star, Trash2 } from 'lucide-react';
|
|
14
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
14
15
|
import { CrossOutWordGroupClient } from './CrossOutWordGroupClient';
|
|
15
16
|
function parsePointsValue(raw, allowZero = false) {
|
|
16
17
|
if (raw === '')
|
|
@@ -213,19 +214,22 @@ export function CrossOutWordGroupCreator({ initialData, onChange, externalErrors
|
|
|
213
214
|
};
|
|
214
215
|
const currentPayload = buildPayload();
|
|
215
216
|
const displayErrors = externalErrors && externalErrors.length > 0 ? externalErrors : errors;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
217
|
+
if (isClientMode) {
|
|
218
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(CrossOutWordGroupClient, { questionData: currentPayload })] }));
|
|
219
|
+
}
|
|
220
|
+
return (_jsxs("div", { className: "space-y-3", children: [displayErrors.length > 0 && (_jsx("div", { className: "rounded-md border border-red-200 bg-red-50 p-2 text-xs text-red-700", children: _jsx("ul", { className: "list-disc space-y-1 pl-4", children: displayErrors.map((err, i) => (_jsx("li", { children: err }, i))) }) })), _jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(HelpCircle, { className: "h-3.5 w-3.5" }), title: "G\u1EA1ch t\u1EEB / S\u1EEDa t\u1EEB (Nh\u00F3m)", preview: { onClick: () => setIsClientMode(true) } }), children: _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "instruction", className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn b\u00E0i l\u00E0m" }), _jsx(Input, { id: "instruction", value: instruction, onChange: (e) => setInstruction(e.target.value), placeholder: DEFAULT_INSTRUCTION, className: "h-10 border-gray-200 bg-white" })] }) }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-sm", children: [_jsx(CardHeader, { className: "px-3 py-2", children: _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs(CardTitle, { className: "text-sm font-semibold text-gray-800", children: ["Danh s\u00E1ch c\u00E2u (", items.length, ")"] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addItem, className: "h-8 gap-1.5 px-2.5 text-xs", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm c\u00E2u"] })] }) }), _jsxs(CardContent, { className: "space-y-2.5 px-3 pb-3", children: [items.map((item, index) => {
|
|
221
|
+
const tokens = tokenize(item.question);
|
|
222
|
+
const selectedWordIndex = item.correctAnswer.wordIndex;
|
|
223
|
+
return (_jsxs("div", { className: "space-y-2.5 rounded-md border border-gray-200 bg-white px-3 py-2.5", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-blue-100 text-xs font-semibold text-blue-700", children: item.questionNumber || index + 1 }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", index + 1] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("div", { className: "w-52", children: _jsxs(Select, { value: item.type, onValueChange: (value) => updateItemType(index, value), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Lo\u1EA1i c\u00E2u" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "CROSS_OUT", children: "Ch\u1EC9 g\u1EA1ch (CROSS_OUT)" }), _jsx(SelectItem, { value: "CROSS_OUT_AND_REPLACE", children: "G\u1EA1ch + thay (CROSS_OUT_AND_REPLACE)" })] })] }) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: `example-${index}`, checked: item.isExample || false, onCheckedChange: (checked) => updateItem(index, {
|
|
224
|
+
isExample: checked,
|
|
225
|
+
points: checked ? 0 : 1,
|
|
226
|
+
}) }), _jsxs(Label, { htmlFor: `example-${index}`, className: "flex cursor-pointer items-center gap-1 text-xs", children: [_jsx(Star, { className: "h-3 w-3 text-amber-500" }), "Example"] })] }), !item.isExample && (_jsx("div", { className: "w-24", children: _jsx(PointsInput, { value: item.points, allowZero: true, onChange: (event) => updateItem(index, {
|
|
227
|
+
points: parsePointsValue(event.target.value, true),
|
|
228
|
+
}) }) })), items.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeItem(index), className: "h-7 w-7 text-red-500 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: `question-${index}`, children: "N\u1ED9i dung c\u00E2u" }), _jsx(Textarea, { id: `question-${index}`, value: item.question, onChange: (e) => updateItem(index, { question: e.target.value }), placeholder: "Nh\u1EADp c\u00E2u g\u1ED1c...", className: "mt-1 bg-white", rows: 2 })] }), tokens.length > 0 && (_jsxs("div", { className: "rounded-lg border border-slate-200 bg-white p-3 space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { className: "text-xs font-semibold text-slate-700", children: "Click t\u1EEB c\u1EA7n g\u1EA1ch trong c\u00E2u (t\u1EF1 \u0111\u1ED9ng g\u00E1n wordIndex):" }), typeof selectedWordIndex === 'number' && (_jsxs("span", { className: "text-[11px] font-bold text-rose-600", children: ["\u0110\u00E3 ch\u1ECDn token #", selectedWordIndex + 1, ": \"", tokens[selectedWordIndex], "\""] }))] }), _jsx("div", { className: "flex flex-wrap gap-1.5 pt-1", children: tokens.map((token, tokenIdx) => {
|
|
229
|
+
const isSelected = selectedWordIndex === tokenIdx;
|
|
230
|
+
return (_jsx("button", { type: "button", onClick: () => toggleSelectToken(index, tokenIdx, token), className: `rounded-md px-2 py-1 text-xs font-medium transition-all ${isSelected
|
|
231
|
+
? 'bg-rose-500 text-white font-bold line-through shadow-sm scale-105'
|
|
232
|
+
: 'bg-slate-100 text-slate-700 hover:bg-blue-100 hover:text-blue-700'}`, children: token }, tokenIdx));
|
|
233
|
+
}) })] })), _jsxs("div", { className: "grid gap-2.5 md:grid-cols-2", children: [_jsxs("div", { children: [_jsxs(Label, { htmlFor: `crossout-${index}`, children: ["T\u1EEB c\u1EA7n g\u1EA1ch (crossOut)", item.type === 'CROSS_OUT' && (_jsx("span", { className: "ml-1 text-xs font-normal text-gray-500", children: "\u2014 \u0111\u1EC3 tr\u1ED1ng n\u1EBFu c\u00E2u \u0111\u00FAng" }))] }), _jsxs("div", { className: "flex gap-2 items-center mt-1", children: [_jsx(Input, { id: `crossout-${index}`, value: item.correctAnswer.crossOut, onChange: (e) => updateItemAnswer(index, { crossOut: e.target.value }), placeholder: item.type === 'CROSS_OUT' ? 'VD: to (hoặc trống)' : 'VD: have', className: "bg-white" }), typeof selectedWordIndex === 'number' && (_jsxs("span", { className: "whitespace-nowrap rounded bg-slate-100 px-2 py-1 text-xs text-slate-600 font-mono", children: ["idx: ", selectedWordIndex] }))] })] }), item.type === 'CROSS_OUT_AND_REPLACE' && (_jsxs("div", { children: [_jsx(Label, { htmlFor: `replace-${index}`, children: "T\u1EEB thay th\u1EBF (replaceWith)" }), _jsx(Input, { id: `replace-${index}`, value: item.correctAnswer.replaceWith || '', onChange: (e) => updateItemAnswer(index, { replaceWith: e.target.value }), placeholder: "VD: has", className: "mt-1 bg-white" })] }))] })] }, index));
|
|
234
|
+
}), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) })] })] })] }));
|
|
231
235
|
}
|
|
@@ -30,7 +30,7 @@ export function CrosswordPuzzleClient({ content, correctAnswer, isReviewMode = f
|
|
|
30
30
|
}
|
|
31
31
|
return { cellMap: cMap, clueNumberMap: numMap };
|
|
32
32
|
}, [acrossClues, downClues]);
|
|
33
|
-
return (_jsxs("div", { className: "flex flex-col gap-
|
|
33
|
+
return (_jsxs("div", { className: "flex flex-col gap-3 p-3 bg-white rounded-xl border border-gray-200 shadow-sm", children: [content?.title && (_jsx("h3", { className: "text-lg font-bold text-gray-800 text-center", children: content.title })), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-12 gap-6 items-start", children: [_jsxs("div", { className: "lg:col-span-5 flex flex-col gap-4", children: [_jsxs("div", { className: "rounded-lg border border-blue-200 bg-blue-50/50 p-4", children: [_jsxs("h4", { className: "font-bold text-blue-900 mb-2.5 flex items-center gap-2", children: [_jsx("span", { className: "px-2 py-0.5 rounded bg-blue-600 text-white text-xs font-semibold", children: "Across" }), _jsx("span", { children: "H\u00E0ng ngang" })] }), _jsx("div", { className: "flex flex-col gap-2 max-h-60 overflow-y-auto pr-1", children: acrossClues.map((c) => {
|
|
34
34
|
const answer = correctAnswer?.answers?.[`across-${c.number}`];
|
|
35
35
|
const userAns = userAnswers[`across-${c.number}`];
|
|
36
36
|
return (_jsxs("div", { className: "flex items-start gap-2 text-sm bg-white p-2 rounded border border-blue-100", children: [_jsxs("span", { className: "font-bold text-blue-700 min-w-[20px]", children: [c.number, "."] }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-gray-800", children: c.clue }), isReviewMode && answer && (_jsxs("div", { className: "text-xs mt-1", children: [_jsx("span", { className: "text-gray-500", children: "\u0110\u00E1p \u00E1n: " }), _jsx("span", { className: "font-bold text-green-700", children: answer }), userAns && userAns !== answer && (_jsx("span", { className: "ml-2 line-through text-red-500 font-semibold", children: userAns }))] }))] }), _jsxs("span", { className: "text-xs text-gray-400", children: ["(", c.length, " ch\u1EEF)"] })] }, `across-${c.number}`));
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect, useMemo } from 'react';
|
|
4
|
-
import { Plus, Trash2, Edit2, Check, AlertCircle } from 'lucide-react';
|
|
4
|
+
import { Plus, Trash2, Edit2, Check, AlertCircle, Grid3X3 } from 'lucide-react';
|
|
5
|
+
import { CompactCreatorHeader, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
6
|
+
import { CrosswordPuzzleClient } from './CrosswordPuzzleClient';
|
|
5
7
|
export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChangesChange, }) {
|
|
6
8
|
const [title, setTitle] = useState(() => {
|
|
7
9
|
return initialData?.content?.title || 'Nationalities Crossword';
|
|
@@ -18,6 +20,7 @@ export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChanges
|
|
|
18
20
|
const [points, setPoints] = useState(() => {
|
|
19
21
|
return initialData?.points ?? 11;
|
|
20
22
|
});
|
|
23
|
+
const [isClientMode, setIsClientMode] = useState(false);
|
|
21
24
|
const [clues, setClues] = useState(() => {
|
|
22
25
|
if (Array.isArray(initialData?.content?.clues)) {
|
|
23
26
|
return initialData.content.clues;
|
|
@@ -182,32 +185,66 @@ export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChanges
|
|
|
182
185
|
setFormAnswer('');
|
|
183
186
|
}
|
|
184
187
|
};
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
188
|
+
if (isClientMode) {
|
|
189
|
+
const across = clues
|
|
190
|
+
.filter((c) => c.direction === 'across')
|
|
191
|
+
.map((c) => ({
|
|
192
|
+
number: c.number,
|
|
193
|
+
clue: c.clue.trim(),
|
|
194
|
+
startRow: c.startRow,
|
|
195
|
+
startCol: c.startCol,
|
|
196
|
+
length: c.length || c.answer.trim().length,
|
|
197
|
+
}));
|
|
198
|
+
const down = clues
|
|
199
|
+
.filter((c) => c.direction === 'down')
|
|
200
|
+
.map((c) => ({
|
|
201
|
+
number: c.number,
|
|
202
|
+
clue: c.clue.trim(),
|
|
203
|
+
startRow: c.startRow,
|
|
204
|
+
startCol: c.startCol,
|
|
205
|
+
length: c.length || c.answer.trim().length,
|
|
206
|
+
}));
|
|
207
|
+
const answers = {};
|
|
208
|
+
for (const c of clues) {
|
|
209
|
+
if (c.answer) {
|
|
210
|
+
answers[`${c.direction}-${c.number}`] = c.answer.trim().toUpperCase();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(CrosswordPuzzleClient, { content: {
|
|
214
|
+
title,
|
|
215
|
+
gridSize: { rows, cols },
|
|
216
|
+
clues: { across, down },
|
|
217
|
+
}, correctAnswer: { answers, caseSensitive }, isReviewMode: true, userAnswers: answers })] }));
|
|
218
|
+
}
|
|
219
|
+
return (_jsx("div", { className: "space-y-3", children: _jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(Grid3X3, { className: "h-3.5 w-3.5" }), title: "\u00D4 ch\u1EEF (Crossword)", points: {
|
|
220
|
+
value: points,
|
|
221
|
+
onValueChange: setPoints,
|
|
222
|
+
}, preview: { onClick: () => setIsClientMode(true) } }), children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 p-3 bg-gray-50 rounded-lg border border-gray-200", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-xs font-bold text-gray-700 mb-1", children: "Ti\u00EAu \u0111\u1EC1 \u00F4 ch\u1EEF" }), _jsx("input", { type: "text", value: title, onChange: (e) => setTitle(e.target.value), placeholder: "vd: Nationalities Crossword", className: "w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 outline-none" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-xs font-bold text-gray-700 mb-1", children: "K\u00EDch th\u01B0\u1EDBc l\u01B0\u1EDBi (H\u00E0ng x C\u1ED9t)" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { type: "number", min: 3, max: 30, value: rows, onChange: (e) => setRows(Math.max(3, parseInt(e.target.value) || 12)), className: "w-20 px-2 py-1.5 text-sm border border-gray-300 rounded outline-none" }), _jsx("span", { className: "text-gray-500 font-bold", children: "x" }), _jsx("input", { type: "number", min: 3, max: 30, value: cols, onChange: (e) => setCols(Math.max(3, parseInt(e.target.value) || 12)), className: "w-20 px-2 py-1.5 text-sm border border-gray-300 rounded outline-none" })] })] }), _jsx("div", { className: "flex items-center pt-5", children: _jsxs("label", { className: "flex items-center gap-2 text-sm text-gray-700 cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: caseSensitive, onChange: (e) => setCaseSensitive(e.target.checked), className: "rounded text-blue-600 focus:ring-blue-500 h-4 w-4" }), _jsx("span", { children: "Ph\u00E2n bi\u1EC7t hoa/th\u01B0\u1EDDng" })] }) })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-12 gap-6", children: [_jsxs("div", { className: "lg:col-span-5 flex flex-col gap-4", children: [_jsxs("form", { onSubmit: handleSaveClue, className: "p-4 rounded-xl border border-blue-200 bg-blue-50/40 flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-sm font-bold text-blue-900", children: editingClueId ? 'Chỉnh sửa gợi ý' : 'Thêm gợi ý mới' }), editingClueId && (_jsx("button", { type: "button", onClick: () => {
|
|
223
|
+
setEditingClueId(null);
|
|
224
|
+
setFormClue('');
|
|
225
|
+
setFormAnswer('');
|
|
226
|
+
}, className: "text-xs text-gray-500 hover:text-gray-700 underline", children: "H\u1EE7y ch\u1EC9nh s\u1EEDa" }))] }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-[11px] font-semibold text-gray-600 mb-0.5", children: "S\u1ED1 Clue" }), _jsx("input", { type: "number", min: 1, value: formNumber, onChange: (e) => setFormNumber(parseInt(e.target.value) || 1), className: "w-full px-2 py-1 text-sm border border-gray-300 rounded outline-none" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[11px] font-semibold text-gray-600 mb-0.5", children: "H\u01B0\u1EDBng" }), _jsxs("select", { value: formDirection, onChange: (e) => setFormDirection(e.target.value), className: "w-full px-2 py-1 text-sm border border-gray-300 rounded outline-none bg-white", children: [_jsx("option", { value: "across", children: "Across (Ngang)" }), _jsx("option", { value: "down", children: "Down (D\u1ECDc)" })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[11px] font-semibold text-gray-600 mb-0.5", children: "To\u1EA1 \u0111\u1ED9 (R, C)" }), _jsxs("div", { className: "flex gap-1", children: [_jsx("input", { type: "number", min: 1, max: rows, value: formStartRow, onChange: (e) => setFormStartRow(parseInt(e.target.value) || 1), placeholder: "R", className: "w-1/2 px-1 py-1 text-xs border border-gray-300 rounded text-center" }), _jsx("input", { type: "number", min: 1, max: cols, value: formStartCol, onChange: (e) => setFormStartCol(parseInt(e.target.value) || 1), placeholder: "C", className: "w-1/2 px-1 py-1 text-xs border border-gray-300 rounded text-center" })] })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[11px] font-semibold text-gray-600 mb-0.5", children: "C\u00E2u g\u1EE3i \u00FD (Clue)" }), _jsx("input", { type: "text", value: formClue, onChange: (e) => setFormClue(e.target.value), placeholder: "vd: Spain, Capital of France...", className: "w-full px-3 py-1.5 text-sm border border-gray-300 rounded outline-none", required: true })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[11px] font-semibold text-gray-600 mb-0.5", children: "T\u1EEB \u0111\u00E1p \u00E1n (Answer Word)" }), _jsx("input", { type: "text", value: formAnswer, onChange: (e) => setFormAnswer(e.target.value), placeholder: "vd: SPANISH", className: "w-full px-3 py-1.5 text-sm border border-gray-300 rounded outline-none uppercase font-semibold", required: true })] }), _jsxs("button", { type: "submit", className: "mt-1 flex items-center justify-center gap-1.5 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded text-sm font-semibold transition", children: [editingClueId ? _jsx(Check, { className: "h-4 w-4" }) : _jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: editingClueId ? 'Cập nhật gợi ý' : 'Thêm vào danh sách' })] })] }), _jsxs("div", { className: "flex flex-col gap-2 max-h-[420px] overflow-y-auto pr-1", children: [_jsxs("span", { className: "text-xs font-bold text-gray-600", children: ["Danh s\u00E1ch (", clues.length, " t\u1EEB):"] }), clues.length === 0 && (_jsx("p", { className: "text-xs text-gray-400 italic", children: "Ch\u01B0a c\u00F3 g\u1EE3i \u00FD n\u00E0o. H\u00E3y th\u00EAm g\u1EE3i \u00FD \u1EDF tr\u00EAn." })), clues.map((c) => (_jsxs("div", { className: "flex items-center justify-between p-2.5 bg-white rounded-lg border border-gray-200 hover:border-blue-300 transition text-sm shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 flex-1 min-w-0", children: [_jsx("span", { className: `flex items-center justify-center h-6 w-6 rounded text-xs font-bold text-white ${c.direction === 'across' ? 'bg-blue-600' : 'bg-purple-600'}`, children: c.number }), _jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [_jsx("span", { className: "font-semibold text-gray-800 truncate", children: c.clue }), _jsxs("span", { className: "text-xs text-gray-500 flex items-center gap-2", children: [_jsx("span", { className: "font-bold text-blue-700", children: c.answer }), _jsxs("span", { children: ["(", c.length, " ch\u1EEF)"] }), _jsxs("span", { children: ["[", c.direction === 'across' ? '→ Ngang' : '↓ Dọc', " (", c.startRow, ",", c.startCol, ")]"] })] })] })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => handleEditClue(c), className: "p-1 text-gray-500 hover:text-blue-600 rounded", title: "S\u1EEDa", children: _jsx(Edit2, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", onClick: () => handleDeleteClue(c.id), className: "p-1 text-gray-500 hover:text-red-600 rounded", title: "X\u00F3a", children: _jsx(Trash2, { className: "h-4 w-4" }) })] })] }, c.id)))] })] }), _jsxs("div", { className: "lg:col-span-7 flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-bold text-gray-700", children: "Ma tr\u1EADn l\u01B0\u1EDBi \u00F4 ch\u1EEF tr\u1EF1c quan:" }), conflicts.length > 0 && (_jsxs("span", { className: "flex items-center gap-1 text-xs font-bold text-red-600 bg-red-50 px-2 py-0.5 rounded border border-red-200", children: [_jsx(AlertCircle, { className: "h-3.5 w-3.5" }), _jsxs("span", { children: ["C\u00F3 ", conflicts.length, " \u00F4 b\u1ECB xung \u0111\u1ED9t ch\u1EEF c\u00E1i!"] })] }))] }), _jsx("div", { className: "flex justify-center overflow-x-auto p-4 bg-gray-100 rounded-xl border border-gray-300 min-h-[380px]", children: _jsx("div", { className: "inline-grid gap-1", style: {
|
|
227
|
+
gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
|
|
228
|
+
}, children: Array.from({ length: rows }).map((_, rIdx) => {
|
|
229
|
+
const r = rIdx + 1;
|
|
230
|
+
return Array.from({ length: cols }).map((_, cIdx) => {
|
|
231
|
+
const c = cIdx + 1;
|
|
232
|
+
const key = `${r}-${c}`;
|
|
233
|
+
const cellEntries = cellMap.get(key);
|
|
234
|
+
const clueNums = clueNumberMap.get(key);
|
|
235
|
+
const isConflict = conflicts.includes(key);
|
|
236
|
+
if (!cellEntries || cellEntries.length === 0) {
|
|
237
|
+
return (_jsx("button", { type: "button", onClick: () => {
|
|
238
|
+
setFormStartRow(r);
|
|
239
|
+
setFormStartCol(c);
|
|
240
|
+
}, title: `Ô trống (${r},${c}) - Click để chọn toạ độ bắt đầu`, className: "h-8 w-8 sm:h-9 sm:w-9 md:h-10 md:w-10 bg-transparent hover:bg-gray-200/60 rounded flex items-center justify-center transition text-[10px] text-gray-300 hover:text-gray-500", children: "\u00B7" }, key));
|
|
241
|
+
}
|
|
242
|
+
const displayLetter = cellEntries[0]?.letter || '';
|
|
243
|
+
return (_jsxs("div", { className: `relative flex items-center justify-center rounded border-2 font-bold shadow-sm h-8 w-8 sm:h-9 sm:w-9 md:h-10 md:w-10 text-sm sm:text-base md:text-lg select-none transition ${isConflict
|
|
244
|
+
? 'border-red-500 bg-red-100 text-red-700 animate-pulse'
|
|
245
|
+
: cellEntries.length > 1
|
|
246
|
+
? 'border-indigo-400 bg-indigo-50 text-indigo-900'
|
|
247
|
+
: 'border-gray-400 bg-white text-gray-800'}`, children: [clueNums && clueNums.length > 0 && (_jsx("span", { className: "absolute top-0.5 left-0.5 text-[9px] font-bold text-gray-500 leading-none", children: clueNums.join('/') })), _jsx("span", { children: displayLetter })] }, key));
|
|
248
|
+
});
|
|
249
|
+
}) }) })] })] })] }) }) }));
|
|
213
250
|
}
|