@tinyweb_dev/oe-exam-sdk 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/exams/ExamCreator.js +1 -16
- package/dist/components/exams/take/ExamTakingPageContainer.js +0 -12
- package/dist/components/exams/take/components/QuestionRenderer.js +2 -15
- package/dist/components/exams/take/components/question-renderers/index.d.ts +0 -3
- package/dist/components/exams/take/components/question-renderers/index.js +0 -3
- package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionRenderer.js +0 -4
- package/dist/components/exams/take/types/speaking.types.d.ts +0 -31
- package/dist/components/exams/take/types.d.ts +0 -65
- package/dist/components/exams/take/utils/answer-transformers.js +6 -60
- package/dist/components/exams/take/utils/question-transformers.d.ts +2 -15
- package/dist/components/exams/take/utils/question-transformers.js +0 -143
- package/dist/components/questions/_shared/components/compact/CompactCreatorHeader.d.ts +23 -0
- package/dist/components/questions/_shared/components/compact/CompactCreatorHeader.js +9 -0
- package/dist/components/questions/_shared/components/compact/CompactExampleToggle.d.ts +6 -0
- package/dist/components/questions/_shared/components/compact/CompactExampleToggle.js +8 -0
- package/dist/components/questions/_shared/components/compact/CompactExplanationToggle.d.ts +7 -0
- package/dist/components/questions/_shared/components/compact/CompactExplanationToggle.js +14 -0
- package/dist/components/questions/_shared/components/compact/CompactFieldCard.d.ts +9 -0
- package/dist/components/questions/_shared/components/compact/CompactFieldCard.js +7 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewBanner.d.ts +7 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewBanner.js +7 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewButton.d.ts +6 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewButton.js +7 -0
- package/dist/components/questions/_shared/components/compact/index.d.ts +6 -0
- package/dist/components/questions/_shared/components/compact/index.js +6 -0
- package/dist/components/questions/_shared/config/question-types.config.js +0 -24
- package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +0 -9
- package/dist/components/questions/_shared/types/index.d.ts +0 -1
- package/dist/components/questions/_shared/types/index.js +0 -1
- package/dist/components/questions/_shared/types/look-and-choose-answer.type.d.ts +0 -13
- package/dist/components/questions/_shared/utils/answer-builder.js +0 -11
- package/dist/components/questions/components/ColorRegionCreator.js +66 -63
- package/dist/components/questions/components/QuestionSearchDropdown.js +0 -8
- package/dist/components/questions/components/QuestionTypeSelector.js +0 -2
- package/dist/components/questions/components/index.d.ts +1 -0
- package/dist/components/questions/components/index.js +1 -0
- package/dist/components/questions/creator/question-type-registry.js +0 -8
- package/dist/components/questions/groups/ArticlesGroupCard.js +2 -2
- package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -1
- package/dist/components/questions/groups/DocumentGroupCard.js +1 -1
- package/dist/components/questions/groups/ImageLabelGroupCard.js +1 -1
- package/dist/components/questions/groups/MatchAnswerGroupCard.js +2 -2
- package/dist/components/questions/question-bank/QuestionEditorRenderer.d.ts +0 -3
- package/dist/components/questions/question-bank/QuestionEditorRenderer.js +1 -10
- package/dist/components/questions/question-bank/editor-adapters/choice-adapters.d.ts +0 -2
- package/dist/components/questions/question-bank/editor-adapters/choice-adapters.js +0 -41
- package/dist/components/questions/question-bank/editor-adapters/index.d.ts +2 -2
- package/dist/components/questions/question-bank/editor-adapters/index.js +2 -2
- package/dist/components/questions/question-bank/editor-adapters/media-adapters.d.ts +0 -1
- package/dist/components/questions/question-bank/editor-adapters/media-adapters.js +0 -28
- package/dist/components/questions/question-bank/question-bank-picker.utils.js +0 -7
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +16 -16
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +36 -30
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +1 -1
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +7 -6
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +2 -2
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +11 -7
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -1
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +11 -5
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +5 -5
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +34 -28
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.js +2 -2
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +1 -1
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +44 -28
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +7 -7
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.js +8 -5
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +2 -2
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupCreator.js +20 -16
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +1 -1
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleCreator.js +66 -29
- package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +5 -5
- package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +45 -37
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +24 -20
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +2 -2
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +68 -65
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +1 -1
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.js +45 -5
- package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +2 -2
- package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +18 -14
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +2 -2
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +30 -30
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +2 -2
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +24 -31
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +2 -2
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +29 -32
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +27 -29
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +11 -15
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +22 -24
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +17 -24
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +31 -31
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +11 -15
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +14 -15
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +2 -2
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +27 -25
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +12 -12
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesCreator.js +8 -5
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +8 -8
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureCreator.js +25 -20
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +1 -1
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +13 -10
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +8 -5
- package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +7 -11
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -1
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +15 -12
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +1 -1
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesCreator.js +5 -5
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +35 -39
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +2 -2
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +1 -1
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +7 -8
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -1
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +30 -23
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQAClient.js +1 -1
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +26 -19
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +1 -1
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +13 -11
- package/dist/components/questions/types/true-false/TrueFalseClient.js +2 -2
- package/dist/components/questions/types/true-false/TrueFalseCreator.js +35 -29
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +1 -1
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupCreator.js +11 -7
- package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +1 -1
- package/dist/components/questions/types/true-false-group/TrueFalseGroupCreator.js +11 -7
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +2 -2
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +21 -56
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -1
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +30 -24
- package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +3 -3
- package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupCreator.js +12 -14
- package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -1
- package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +27 -18
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +7 -7
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupCreator.js +16 -13
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +2 -2
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +34 -28
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +2 -2
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +22 -22
- package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +21 -18
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -1
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +7 -4
- package/dist/components/questions/viewer/QuestionViewer.js +0 -98
- package/dist/components/results/renderers/review-question-body-movers.js +2 -26
- package/dist/components/results/review-data.js +0 -4
- package/dist/components/ui/points-input.d.ts +1 -0
- package/dist/components/ui/points-input.js +12 -2
- package/dist/shared/constants/question-skills.js +0 -11
- package/dist/shared/lib/i18n/messages/en/admin.d.ts +0 -3
- package/dist/shared/lib/i18n/messages/en/admin.js +0 -3
- package/dist/shared/lib/i18n/messages/en.d.ts +0 -3
- package/dist/shared/lib/i18n/messages/vi/admin.d.ts +0 -3
- package/dist/shared/lib/i18n/messages/vi/admin.js +0 -3
- package/dist/shared/lib/i18n/messages/vi.d.ts +0 -3
- package/dist/shared/lib/utils/question-reverse-transform.js +0 -19
- package/dist/shared/lib/utils/question-transform-types.d.ts +1 -23
- package/dist/shared/lib/utils/question-transform-validation.js +0 -40
- package/dist/shared/lib/utils/question-transform.js +0 -9
- package/dist/shared/types/common.types.d.ts +1 -1
- package/dist/shared/types/entities/question.types.d.ts +1 -11
- package/dist/shared/types/questions/index.d.ts +0 -3
- package/dist/shared/types/questions/index.js +0 -6
- package/package.json +1 -1
- package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.d.ts +0 -2
- package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.js +0 -131
- package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.d.ts +0 -2
- package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.js +0 -76
- package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.d.ts +0 -2
- package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.js +0 -68
- package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.d.ts +0 -16
- package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.js +0 -212
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.d.ts +0 -32
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +0 -1
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.d.ts +0 -47
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +0 -1
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.d.ts +0 -2
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +0 -262
- package/dist/components/questions/types/gn-speaking-interview/register.d.ts +0 -2
- package/dist/components/questions/types/gn-speaking-interview/register.js +0 -121
- package/dist/components/questions/types/gn-speaking-interview/transform.d.ts +0 -2
- package/dist/components/questions/types/gn-speaking-interview/transform.js +0 -48
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.d.ts +0 -20
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +0 -72
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.d.ts +0 -15
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +0 -230
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.d.ts +0 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +0 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.d.ts +0 -2
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +0 -66
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.d.ts +0 -2
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +0 -42
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.d.ts +0 -6
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +0 -96
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.d.ts +0 -15
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +0 -238
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +0 -76
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +0 -19
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.d.ts +0 -7
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +0 -134
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.d.ts +0 -3
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +0 -617
- package/dist/components/questions/types/look-picture-fill-word-hint/index.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/index.js +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.js +0 -82
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +0 -29
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.d.ts +0 -9
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +0 -12
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.d.ts +0 -17
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +0 -47
- package/dist/shared/types/questions/look-picture-choose-correct-answer.d.ts +0 -38
- package/dist/shared/types/questions/look-picture-choose-correct-answer.js +0 -5
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.d.ts +0 -63
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +0 -9
- package/dist/shared/types/questions/look-picture-fill-word-hint.d.ts +0 -57
- package/dist/shared/types/questions/look-picture-fill-word-hint.js +0 -7
package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupCreator.js
CHANGED
|
@@ -11,6 +11,7 @@ import { Textarea } from '../../../../components/ui/textarea';
|
|
|
11
11
|
import { Switch } from '../../../../components/ui/switch';
|
|
12
12
|
import { Label } from '../../../../components/ui/label';
|
|
13
13
|
import { cn } from '../../../../shared/lib/utils';
|
|
14
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
14
15
|
import { WordOrderAndMatchGroupClient } from './WordOrderAndMatchGroupClient';
|
|
15
16
|
const itemSchema = z.object({
|
|
16
17
|
scrambledWords: z.array(z.string().min(1, 'Từ không được để trống')).min(2, 'Cần ít nhất 2 từ xáo trộn'),
|
|
@@ -149,34 +150,31 @@ export function WordOrderAndMatchGroupCreator({ initialData, onSave, onChange, e
|
|
|
149
150
|
const onSubmit = (data) => {
|
|
150
151
|
onSave?.(data);
|
|
151
152
|
};
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
_jsxs("div", { className: "rounded-2xl border border-gray-200 bg-gray-50/50 p-6", children: [_jsx("div", { className: "mb-4 text-xs font-bold text-gray-500 uppercase tracking-wider", children: "Giao di\u1EC7n l\u00E0m b\u00E0i th\u1EF1c t\u1EBF c\u1EE7a h\u1ECDc sinh:" }), _jsx(WordOrderAndMatchGroupClient, { questionData: watchedValues })] })) : (
|
|
159
|
-
/* Editor Mode */
|
|
160
|
-
_jsxs("form", { onSubmit: handleSubmit(onSubmit), className: "space-y-6", children: [_jsxs("div", { className: "rounded-2xl border border-gray-200 bg-white p-5 shadow-xs space-y-4", children: [_jsxs("div", { children: [_jsxs(Label, { className: "text-xs font-bold text-gray-700 uppercase tracking-wider", children: ["1. H\u01B0\u1EDBng d\u1EABn l\u00E0m b\u00E0i chung (Instruction) ", _jsx("span", { className: "text-rose-500", children: "*" })] }), _jsx(Textarea, { ...register('instruction'), rows: 2, placeholder: "Nh\u1EADp h\u01B0\u1EDBng d\u1EABn l\u00E0m b\u00E0i...", className: "mt-1.5 text-sm" }), errors.instruction && (_jsx("p", { className: "mt-1 text-xs text-rose-500", children: errors.instruction.message }))] }), _jsxs("div", { children: [_jsxs(Label, { className: "text-xs font-bold text-gray-700 uppercase tracking-wider flex items-center justify-between", children: [_jsx("span", { children: "2. Danh s\u00E1ch nh\u00E3n l\u1EF1a ch\u1ECDn (Match Options)" }), _jsx("span", { className: "text-[11px] font-normal text-gray-400", children: "V\u00ED d\u1EE5: A, B, C, D, E, F" })] }), _jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: [watchedValues.matchOptions?.map((opt) => (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-xl border border-indigo-200 bg-indigo-50/70 px-3 py-1 text-xs font-bold text-indigo-700", children: [_jsx("span", { children: opt }), watchedValues.matchOptions && watchedValues.matchOptions.length > 1 && (_jsx("button", { type: "button", onClick: () => handleRemoveMatchOption(opt), className: "text-indigo-400 hover:text-rose-600 transition-colors", title: "Xo\u00E1 nh\u00E3n", children: _jsx(Trash2, { className: "h-3 w-3" }) }))] }, opt))), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Input, { value: newOptionInput, onChange: (e) => setNewOptionInput(e.target.value), placeholder: "Nh\u00E3n m\u1EDBi (VD: G)", className: "h-8 w-28 text-xs uppercase", onKeyDown: (e) => {
|
|
153
|
+
const tabSwitcher = (_jsxs("div", { className: "flex items-center rounded-md bg-gray-100 p-0.5", children: [_jsxs("button", { type: "button", onClick: () => setActiveTab('editor'), className: cn('flex h-8 items-center gap-1.5 rounded-md px-2.5 text-xs font-semibold transition-all', activeTab === 'editor'
|
|
154
|
+
? 'bg-white text-indigo-700 shadow-xs'
|
|
155
|
+
: 'text-gray-600 hover:text-gray-900'), children: [_jsx(Edit3, { className: "h-3.5 w-3.5" }), "So\u1EA1n th\u1EA3o"] }), _jsxs("button", { type: "button", onClick: () => setActiveTab('preview'), className: cn('flex h-8 items-center gap-1.5 rounded-md px-2.5 text-xs font-semibold transition-all', activeTab === 'preview'
|
|
156
|
+
? 'bg-white text-indigo-700 shadow-xs'
|
|
157
|
+
: 'text-gray-600 hover:text-gray-900'), children: [_jsx(Eye, { className: "h-3.5 w-3.5" }), "Xem tr\u01B0\u1EDBc"] })] }));
|
|
158
|
+
return (_jsxs("div", { className: "w-full space-y-3", children: [externalErrors && externalErrors.length > 0 && (_jsx("div", { className: "rounded-md border border-rose-200 bg-rose-50 p-2 text-xs text-rose-800", children: _jsx("ul", { className: "list-disc space-y-1 pl-4", children: externalErrors.map((err, i) => (_jsx("li", { children: err }, i))) }) })), activeTab === 'preview' ? (_jsxs("div", { className: "space-y-3", children: [_jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(Layers, { className: "h-3.5 w-3.5" }), title: "S\u1EAFp x\u1EBFp t\u1EEB & N\u1ED1i nh\u00E3n", extraActions: tabSwitcher }), contentClassName: "hidden", children: null }), _jsx(CompactPreviewBanner, { onBack: () => setActiveTab('editor') }), _jsx(WordOrderAndMatchGroupClient, { questionData: watchedValues })] })) : (_jsxs("form", { onSubmit: handleSubmit(onSubmit), className: "space-y-3", children: [_jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(Layers, { className: "h-3.5 w-3.5" }), title: "S\u1EAFp x\u1EBFp t\u1EEB & N\u1ED1i nh\u00E3n", extraActions: tabSwitcher }), children: [_jsxs("div", { children: [_jsxs(Label, { className: "text-xs font-bold text-gray-700 uppercase tracking-wider", children: ["1. H\u01B0\u1EDBng d\u1EABn l\u00E0m b\u00E0i chung (Instruction) ", _jsx("span", { className: "text-rose-500", children: "*" })] }), _jsx(Textarea, { ...register('instruction'), rows: 2, placeholder: "Nh\u1EADp h\u01B0\u1EDBng d\u1EABn l\u00E0m b\u00E0i...", className: "mt-1.5 text-sm" }), errors.instruction && (_jsx("p", { className: "mt-1 text-xs text-rose-500", children: errors.instruction.message }))] }), _jsxs("div", { children: [_jsxs(Label, { className: "text-xs font-bold text-gray-700 uppercase tracking-wider flex items-center justify-between", children: [_jsx("span", { children: "2. Danh s\u00E1ch nh\u00E3n l\u1EF1a ch\u1ECDn (Match Options)" }), _jsx("span", { className: "text-[11px] font-normal text-gray-400", children: "V\u00ED d\u1EE5: A, B, C, D, E, F" })] }), _jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: [watchedValues.matchOptions?.map((opt) => (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-xl border border-indigo-200 bg-indigo-50/70 px-3 py-1 text-xs font-bold text-indigo-700", children: [_jsx("span", { children: opt }), watchedValues.matchOptions && watchedValues.matchOptions.length > 1 && (_jsx("button", { type: "button", onClick: () => handleRemoveMatchOption(opt), className: "text-indigo-400 hover:text-rose-600 transition-colors", title: "Xo\u00E1 nh\u00E3n", children: _jsx(Trash2, { className: "h-3 w-3" }) }))] }, opt))), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Input, { value: newOptionInput, onChange: (e) => setNewOptionInput(e.target.value), placeholder: "Nh\u00E3n m\u1EDBi (VD: G)", className: "h-8 w-28 text-xs uppercase", onKeyDown: (e) => {
|
|
161
159
|
if (e.key === 'Enter') {
|
|
162
160
|
e.preventDefault();
|
|
163
161
|
handleAddMatchOption();
|
|
164
162
|
}
|
|
165
|
-
} }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: handleAddMatchOption, className: "h-8 text-xs font-medium", children: [_jsx(Plus, { className: "h-3.5 w-3.5 mr-1" }), " Th\u00EAm"] })] })] }), errors.matchOptions && (_jsx("p", { className: "mt-1 text-xs text-rose-500", children: errors.matchOptions.message }))] })] }), _jsxs(
|
|
163
|
+
} }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: handleAddMatchOption, className: "h-8 text-xs font-medium", children: [_jsx(Plus, { className: "h-3.5 w-3.5 mr-1" }), " Th\u00EAm"] })] })] }), errors.matchOptions && (_jsx("p", { className: "mt-1 text-xs text-rose-500", children: errors.matchOptions.message }))] })] }), _jsxs(CompactFieldCard, { header: _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("p", { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: [_jsx(Tag, { className: "h-3.5 w-3.5 text-indigo-600" }), "Danh s\u00E1ch c\u00E2u h\u1ECFi con (", fields.length, ")"] }), _jsxs(Button, { type: "button", onClick: handleAddItem, size: "sm", className: "h-8 gap-1.5 bg-indigo-600 px-2.5 text-xs font-medium text-white hover:bg-indigo-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), " Th\u00EAm c\u00E2u h\u1ECFi"] })] }), children: [fields.map((field, index) => {
|
|
166
164
|
const itemWords = watchedValues.items?.[index]?.scrambledWords || [];
|
|
167
165
|
const isExample = watchedValues.items?.[index]?.isExample;
|
|
168
|
-
return (_jsxs("div", { className: cn('rounded-
|
|
166
|
+
return (_jsxs("div", { className: cn('space-y-2.5 rounded-md border bg-white px-3 py-2.5 transition-all', isExample ? 'border-amber-200 bg-amber-50/20' : 'border-gray-200'), children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("span", { className: "inline-flex h-7 items-center justify-center rounded-lg bg-indigo-100 px-2.5 text-xs font-bold text-indigo-800", children: ["#", index + 1] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: `example-${index}`, checked: isExample || false, onCheckedChange: (checked) => {
|
|
169
167
|
setValue(`items.${index}.isExample`, checked, { shouldDirty: true });
|
|
170
168
|
if (checked) {
|
|
171
169
|
setValue(`items.${index}.points`, 0, { shouldDirty: true });
|
|
172
170
|
}
|
|
173
|
-
} }), _jsx(Label, { htmlFor: `example-${index}`, className: "text-xs font-medium text-gray-700 cursor-pointer", children: "C\u00E2u v\u00ED d\u1EE5 m\u1EABu (0 \u0111i\u1EC3m)" })] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [!isExample && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Label, { className: "text-xs font-medium text-gray-600", children: "\u0110i\u1EC3m:" }), _jsx(Input, { type: "number", ...register(`items.${index}.points`, { valueAsNumber: true }), className: "h-8 w-16 text-center text-xs", min: 0, step: 0.5 })] })), fields.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => remove(index), className: "h-8 w-8 p-0 text-gray-400 hover:text-rose-600 hover:bg-rose-50", title: "Xo\u00E1 c\u00E2u n\u00E0y", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] })] }), _jsxs("div", { className: "grid grid-cols-1 gap-
|
|
171
|
+
} }), _jsx(Label, { htmlFor: `example-${index}`, className: "text-xs font-medium text-gray-700 cursor-pointer", children: "C\u00E2u v\u00ED d\u1EE5 m\u1EABu (0 \u0111i\u1EC3m)" })] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [!isExample && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Label, { className: "text-xs font-medium text-gray-600", children: "\u0110i\u1EC3m:" }), _jsx(Input, { type: "number", ...register(`items.${index}.points`, { valueAsNumber: true }), className: "h-8 w-16 text-center text-xs", min: 0, step: 0.5 })] })), fields.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => remove(index), className: "h-8 w-8 p-0 text-gray-400 hover:text-rose-600 hover:bg-rose-50", title: "Xo\u00E1 c\u00E2u n\u00E0y", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] })] }), _jsxs("div", { className: "grid grid-cols-1 gap-2.5 lg:grid-cols-12", children: [_jsxs("div", { className: "space-y-1.5 lg:col-span-8", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "text-xs font-bold text-gray-700", children: ["C\u00E2u \u0111\u00FAng ho\u00E0n ch\u1EC9nh (Ordered Phrase) ", _jsx("span", { className: "text-rose-500", children: "*" })] }), _jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => handleAutoScramble(index), className: "h-6 text-[11px] text-indigo-600 hover:bg-indigo-50 font-medium px-2", children: [_jsx(Shuffle, { className: "h-3 w-3 mr-1" }), " T\u1EF1 x\u00E1o tr\u1ED9n t\u1EEB"] })] }), _jsx(Input, { ...register(`items.${index}.orderedPhrase`), placeholder: "V\u00ED d\u1EE5: I'm sure everything will be OK", className: "text-sm font-medium" }), errors.items?.[index]?.orderedPhrase && (_jsx("p", { className: "text-xs text-rose-500", children: errors.items[index]?.orderedPhrase?.message }))] }), _jsxs("div", { className: "space-y-1.5 lg:col-span-4", children: [_jsxs(Label, { className: "text-xs font-bold text-gray-700", children: ["Nh\u00E3n \u0111\u00FAng (Match Label) ", _jsx("span", { className: "text-rose-500", children: "*" })] }), _jsx(Controller, { control: control, name: `items.${index}.matchLabel`, render: ({ field }) => (_jsx("div", { className: "flex flex-wrap gap-1", children: watchedValues.matchOptions?.map((opt) => (_jsx("button", { type: "button", onClick: () => field.onChange(opt), className: cn('h-9 w-9 rounded-lg font-bold text-xs transition-all flex items-center justify-center', field.value === opt
|
|
174
172
|
? 'bg-indigo-600 text-white shadow-xs'
|
|
175
|
-
: 'border border-gray-200 bg-white text-gray-700 hover:border-indigo-300'), children: opt }, opt))) })) }), errors.items?.[index]?.matchLabel && (_jsx("p", { className: "text-xs text-rose-500", children: errors.items[index]?.matchLabel?.message }))] })] }), _jsxs("div", { className: "rounded-
|
|
173
|
+
: 'border border-gray-200 bg-white text-gray-700 hover:border-indigo-300'), children: opt }, opt))) })) }), errors.items?.[index]?.matchLabel && (_jsx("p", { className: "text-xs text-rose-500", children: errors.items[index]?.matchLabel?.message }))] })] }), _jsxs("div", { className: "space-y-2 rounded-md border border-dashed border-gray-200 bg-gray-50/70 px-3 py-2.5", children: [_jsxs(Label, { className: "text-xs font-bold text-gray-600 flex items-center justify-between", children: [_jsxs("span", { children: ["C\u00E1c t\u1EEB b\u1ECB x\u00E1o tr\u1ED9n (Scrambled Words) (", itemWords.length, ")"] }), _jsx("span", { className: "text-[11px] font-normal text-gray-400", children: "Th\u1EE9 t\u1EF1 t\u1EEB \u1EDF \u0111\u00E2y s\u1EBD hi\u1EC3n th\u1ECB cho h\u1ECDc sinh" })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [itemWords.map((word, wordIdx) => (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-lg border border-gray-300 bg-white px-2.5 py-1 text-xs font-semibold text-gray-800 shadow-xs", children: [_jsx("span", { children: word }), _jsx("button", { type: "button", onClick: () => handleRemoveWord(index, wordIdx), className: "text-gray-400 hover:text-rose-600 transition-colors", children: _jsx(Trash2, { className: "h-3 w-3" }) })] }, wordIdx))), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Input, { value: newWordInputs[index] || '', onChange: (e) => setNewWordInputs((prev) => ({ ...prev, [index]: e.target.value })), placeholder: "+ Th\u00EAm t\u1EEB", className: "h-7 w-28 text-xs", onKeyDown: (e) => {
|
|
176
174
|
if (e.key === 'Enter') {
|
|
177
175
|
e.preventDefault();
|
|
178
176
|
handleAddWord(index);
|
|
179
177
|
}
|
|
180
178
|
} }), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => handleAddWord(index), className: "h-7 text-xs font-medium px-2", children: "Th\u00EAm" })] })] }), errors.items?.[index]?.scrambledWords && (_jsx("p", { className: "text-xs text-rose-500", children: errors.items[index]?.scrambledWords?.message }))] })] }, field.id));
|
|
181
|
-
})
|
|
179
|
+
}), _jsx(CompactExplanationToggle, { value: watchedValues.explanation || '', onValueChange: (value) => setValue('explanation', value, { shouldDirty: true }), defaultVisible: Boolean(initialData?.explanation) })] })] }))] }));
|
|
182
180
|
}
|
|
@@ -86,7 +86,7 @@ export function WordOrderingClient({ questionData, onSubmit, isReviewMode = fals
|
|
|
86
86
|
const isWrong = isReviewMode && selectedWords.length > 0 && !isCorrect;
|
|
87
87
|
const isComplete = selectedWords.length === words.length;
|
|
88
88
|
const displayExplanation = explanation || questionData.explanation;
|
|
89
|
-
return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-
|
|
89
|
+
return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gradient-to-br from-amber-500 to-orange-600 text-white", children: _jsx(ArrowUpDown, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "S\u1EAFp x\u1EBFp t\u1EEB" }), isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2.5 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Lightbulb, { className: "h-3 w-3" }), "V\u00ED d\u1EE5"] }))] }), isReviewMode && isComplete && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isCorrect ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'), children: [isCorrect ? _jsx(Check, { className: "h-3.5 w-3.5" }) : _jsx(X, { className: "h-3.5 w-3.5" }), isCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [(imagePreviewUrl || imageUrl) && (_jsx("div", { className: "flex justify-center", children: _jsx("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gray-50", children: isLoading ? (_jsx("div", { className: "flex h-48 w-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-amber-500" }) })) : (_jsx("img", { src: imagePreviewUrl || imageUrl, alt: "H\u00ECnh \u1EA3nh c\u00E2u h\u1ECFi", className: "max-h-56 w-auto object-contain p-2" })) }) })), _jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "\u0110\u00E1p \u00E1n c\u1EE7a b\u1EA1n" }), _jsx("div", { className: "flex flex-wrap gap-2 min-h-[48px] rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-3", children: selectedWords.length === 0 ? (_jsx("span", { className: "text-sm text-gray-400 italic", children: "Nh\u1EA5n v\u00E0o c\u00E1c t\u1EEB b\u00EAn d\u01B0\u1EDBi \u0111\u1EC3 s\u1EAFp x\u1EBFp..." })) : (selectedWords.map((word, index) => {
|
|
90
90
|
const isWordCorrect = isReviewMode && answer[index] === word;
|
|
91
91
|
const isWordWrong = isReviewMode && answer[index] !== word;
|
|
92
92
|
return (_jsxs("button", { type: "button", onClick: () => handleRemoveWord(index), disabled: isReviewMode, className: cn('inline-flex items-center gap-1 rounded-lg px-3 py-2 text-sm font-semibold transition-all shadow-sm', isReviewMode
|
|
@@ -3,15 +3,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { ImagePreview } from '../../../../components/ui/image-preview';
|
|
4
4
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
5
5
|
import { Button } from '../../../../components/ui/button';
|
|
6
|
-
import { Label } from '../../../../components/ui/label';
|
|
7
6
|
import { Input } from '../../../../components/ui/input';
|
|
8
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
9
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
10
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
11
|
-
import { Checkbox } from '../../../../components/ui/checkbox';
|
|
12
7
|
import { usePresignedFileUrl, useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
13
8
|
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
14
|
-
import {
|
|
9
|
+
import { Plus, Trash2, ArrowUpDown, GripVertical, Shuffle, ImageIcon } from 'lucide-react';
|
|
10
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
15
11
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
16
12
|
import { WordOrderingClient } from './WordOrderingClient';
|
|
17
13
|
import { wordOrderingFormSchema, } from '../../../../shared/lib/validation/word-ordering.validation';
|
|
@@ -32,6 +28,7 @@ function WordOrderingCreatorContent({ initialData, onSave, onCancel, onChange, e
|
|
|
32
28
|
// UI State
|
|
33
29
|
const [isClientMode, setIsClientMode] = useState(false);
|
|
34
30
|
const [imagePreviewUrl, setImagePreviewUrl] = useState('');
|
|
31
|
+
const [isImageInputVisible, setIsImageInputVisible] = useState(() => Boolean(apiData?.imageUrl));
|
|
35
32
|
// Refs
|
|
36
33
|
const previousInitialDataRef = useRef(initialData);
|
|
37
34
|
const isInitializingRef = useRef(false);
|
|
@@ -172,20 +169,32 @@ function WordOrderingCreatorContent({ initialData, onSave, onCancel, onChange, e
|
|
|
172
169
|
isExample: watchedIsExample,
|
|
173
170
|
explanation: watchedExplanation,
|
|
174
171
|
};
|
|
175
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsx(
|
|
172
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(WordOrderingClient, { questionData: questionData, isReviewMode: true, userAnswer: watchedAnswer })] }));
|
|
176
173
|
}
|
|
177
174
|
// Creator UI
|
|
178
|
-
return (_jsxs("div", { className: "space-y-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
175
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(ArrowUpDown, { className: "h-3.5 w-3.5" }), title: "S\u1EAFp x\u1EBFp t\u1EEB", points: {
|
|
176
|
+
value: watchedPoints,
|
|
177
|
+
onValueChange: (value) => form.setValue('points', value, { shouldValidate: true }),
|
|
178
|
+
error: form.hasError('points') ? form.getFieldError('points') : undefined,
|
|
179
|
+
}, example: {
|
|
180
|
+
checked: watchedIsExample || false,
|
|
181
|
+
onChange: (checked) => form.setValue('isExample', checked),
|
|
182
|
+
}, extraActions: _jsx("button", { type: "button", title: "Th\u00EAm h\u00ECnh \u1EA3nh", onClick: () => setIsImageInputVisible(true), className: `inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors ${watchedImageUrl || isImageInputVisible
|
|
183
|
+
? 'bg-indigo-600 text-white shadow-sm'
|
|
184
|
+
: 'bg-indigo-50 text-indigo-600 hover:bg-indigo-100'}`, children: _jsx(ImageIcon, { className: "h-4 w-4" }) }), headerExtra: headerExtra, preview: {
|
|
185
|
+
onClick: () => setIsClientMode(true),
|
|
186
|
+
disabled: watchedWords.filter((w) => w.trim()).length < 2,
|
|
187
|
+
} }), children: [isImageInputVisible && (_jsxs("div", { className: "space-y-2", children: [_jsx(FileUpload, { id: "word-ordering-image", label: "", accept: "image/*", value: watchedImageUrl || '', onChange: (url) => {
|
|
188
|
+
form.setValue('imageUrl', url, { shouldValidate: true });
|
|
189
|
+
}, onPresignedUrlChange: setImagePreviewUrl, maxSize: 5, placeholder: "Upload h\u00ECnh \u1EA3nh ho\u1EB7c paste URL", autoUpload: true, prefix: "questions" }), displayPreviewUrl && (_jsx(ImagePreview, { src: displayPreviewUrl, alt: "Preview", className: "mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200" }))] })), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("p", { className: "text-xs font-medium text-gray-600", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng ", _jsxs("span", { className: "font-normal text-gray-500", children: ["(", watchedAnswer.length, " t\u1EEB/ch\u1EEF)"] })] }), _jsxs("div", { className: "flex gap-1.5", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: shuffleWords, disabled: watchedAnswer.filter((a) => a.trim()).length < 2, className: "h-8 gap-1.5 border-amber-200 px-2.5 text-xs text-amber-600 hover:bg-amber-50", title: "X\u00E1o tr\u1ED9n th\u1EE9 t\u1EF1 \u0111\u00E1p \u00E1n \u0111\u1EC3 t\u1EA1o danh s\u00E1ch t\u1EEB", children: [_jsx(Shuffle, { className: "h-3.5 w-3.5" }), "X\u00E1o tr\u1ED9n \u2192 T\u1EEB"] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: addWord, className: "h-8 gap-1.5 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"] })] })] }), watchedAnswer.map((word, index) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md bg-gradient-to-br from-green-500 to-emerald-600 text-xs font-bold text-white", children: index + 1 }), _jsx(Input, { value: word, onChange: (e) => {
|
|
190
|
+
const newAnswer = [...watchedAnswer];
|
|
191
|
+
newAnswer[index] = e.target.value;
|
|
192
|
+
form.setValue('answer', newAnswer, { shouldValidate: true });
|
|
193
|
+
}, placeholder: `Từ/chữ cái thứ ${index + 1}`, className: "h-8 border-gray-200" }), watchedAnswer.length > 2 && (_jsx(Button, { variant: "ghost", size: "icon", onClick: () => removeWord(index), className: "h-7 w-7 text-red-500 hover:bg-red-50 hover:text-red-700", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }, index))), form.formState.errors.answer?.message && (_jsx("p", { className: "text-sm text-red-600", children: form.formState.errors.answer.message }))] }), _jsxs(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-orange-100", children: _jsx(GripVertical, { className: "h-3.5 w-3.5 text-orange-600" }) }), _jsxs("span", { className: "text-sm font-semibold text-gray-800", children: ["T\u1EEB hi\u1EC3n th\u1ECB (x\u00E1o tr\u1ED9n) ", _jsxs("span", { className: "text-xs font-normal text-gray-500", children: ["(", watchedWords.length, " t\u1EEB/ch\u1EEF)"] })] })] }), children: [watchedWords.map((word, index) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md bg-gradient-to-br from-orange-400 to-amber-500 text-xs font-bold text-white", children: index + 1 }), _jsx(Input, { value: word, onChange: (e) => {
|
|
194
|
+
const newWords = [...watchedWords];
|
|
195
|
+
newWords[index] = e.target.value;
|
|
196
|
+
form.setValue('words', newWords, { shouldValidate: true });
|
|
197
|
+
}, placeholder: `Từ/chữ cái thứ ${index + 1}`, className: "h-8 border-gray-200" })] }, index))), form.formState.errors.words?.message && (_jsx("p", { className: "text-sm text-red-600", children: form.formState.errors.words.message })), _jsx(CompactExplanationToggle, { value: watchedExplanation ?? '', onValueChange: (value) => form.setValue('explanation', value, { shouldValidate: true }), defaultVisible: Boolean(apiData?.explanation) })] })] }));
|
|
189
198
|
}
|
|
190
199
|
export function WordOrderingCreator(props) {
|
|
191
200
|
return _jsx(WordOrderingCreatorContent, { ...props });
|
|
@@ -16,11 +16,11 @@ export function WordOrderingGroupClient({ questionData, isReviewMode = false, us
|
|
|
16
16
|
});
|
|
17
17
|
onAnswerChange(next);
|
|
18
18
|
};
|
|
19
|
-
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) => (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
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) => (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || itemIndex + 1] }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsx("div", { className: "space-y-3 p-3", children: _jsx(WordOrderingClient, { questionData: {
|
|
20
|
+
words: item.words,
|
|
21
|
+
answer: item.answer,
|
|
22
|
+
isExample: item.isExample,
|
|
23
|
+
points: item.points,
|
|
24
|
+
imageUrl: item.imageUrl,
|
|
25
|
+
}, isReviewMode: isReviewMode || item.isExample, userAnswer: item.isExample ? item.answer : userAnswers[itemIndex], onSubmit: (ordered) => handleItemSubmit(itemIndex, ordered) }) })] }, `${item.questionNumber}-${itemIndex}`))), 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] }))] }));
|
|
26
26
|
}
|
|
@@ -5,7 +5,6 @@ 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';
|
|
@@ -18,7 +17,8 @@ function parsePointsValue(raw, allowZero = false) {
|
|
|
18
17
|
const min = allowZero ? 0 : 1;
|
|
19
18
|
return Math.max(min, parsed);
|
|
20
19
|
}
|
|
21
|
-
import {
|
|
20
|
+
import { ImageIcon, ListOrdered, Plus, Star, Trash2 } from 'lucide-react';
|
|
21
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
22
22
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
23
23
|
import { WordOrderingGroupClient } from './WordOrderingGroupClient';
|
|
24
24
|
import { WORD_ORDERING_GROUP_DEFAULT_INSTRUCTION } from '../../_shared/types/word-ordering-group.type';
|
|
@@ -108,15 +108,18 @@ export function WordOrderingGroupCreator({ initialData, onChange, externalErrors
|
|
|
108
108
|
};
|
|
109
109
|
const currentPayload = buildPayload();
|
|
110
110
|
const displayErrors = externalErrors && externalErrors.length > 0 ? externalErrors : errors;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
111
|
+
if (isClientMode) {
|
|
112
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(WordOrderingGroupClient, { questionData: currentPayload })] }));
|
|
113
|
+
}
|
|
114
|
+
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(ListOrdered, { className: "h-3.5 w-3.5" }), title: "S\u1EAFp x\u1EBFp t\u1EEB (Nh\u00F3m)", preview: { onClick: () => setIsClientMode(true) } }), children: _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "word-ordering-instruction", className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(Input, { id: "word-ordering-instruction", value: instruction, onChange: (e) => setInstruction(e.target.value), placeholder: WORD_ORDERING_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: ["C\u00E2u h\u1ECFi (", items.length, ")"] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setItems((prev) => [...prev, createEmptyWordOrderingGroupItem(prev.length + 1)]), 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", children: ["C\u00E2u ", item.questionNumber || index + 1] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { checked: item.isExample || false, onCheckedChange: (checked) => updateItem(index, { isExample: checked, points: checked ? 0 : 1 }) }), _jsxs(Label, { className: "flex 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, {
|
|
115
|
+
points: parsePointsValue(event.target.value, true),
|
|
116
|
+
}) }) })), items.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => setItems((prev) => prev.filter((_, i) => i !== index).map((entry, i) => ({ ...entry, questionNumber: i + 1 }))), className: "h-7 w-7 text-red-500 hover:bg-red-50", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsxs(Label, { className: "flex items-center gap-2", children: [_jsx(ImageIcon, { className: "h-3.5 w-3.5 text-amber-500" }), "H\u00ECnh \u1EA3nh (tu\u1EF3 ch\u1ECDn)"] }), _jsx(FileUpload, { id: `word-ordering-group-image-${index}`, label: "", accept: "image/*", value: item.imageUrl || '', onChange: (url) => updateItem(index, { imageUrl: url || undefined }), maxSize: 5, placeholder: "Upload h\u00ECnh \u1EA3nh ho\u1EB7c paste URL", autoUpload: true, prefix: "questions" })] }), _jsxs("div", { children: [_jsx(Label, { children: "Token x\u00E1o tr\u1ED9n" }), _jsx(TokenBoxRow, { tokens: withMinTokens(item.words), onChange: (tokenIndex, value) => updateItem(index, {
|
|
117
|
+
words: setTokenAt(item.words, tokenIndex, value),
|
|
118
|
+
}), onAdd: () => updateItem(index, { words: [...item.words, ''] }), onRemove: (tokenIndex) => updateItem(index, {
|
|
119
|
+
words: item.words.filter((_, current) => current !== tokenIndex),
|
|
120
|
+
}) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Th\u1EE9 t\u1EF1 \u0111\u00FAng" }), _jsx(TokenBoxRow, { tokens: withMinTokens(item.answer), onChange: (tokenIndex, value) => updateItem(index, {
|
|
121
|
+
answer: setTokenAt(item.answer, tokenIndex, value),
|
|
122
|
+
}), onAdd: () => updateItem(index, { answer: [...item.answer, ''] }), onRemove: (tokenIndex) => updateItem(index, {
|
|
123
|
+
answer: item.answer.filter((_, current) => current !== tokenIndex),
|
|
124
|
+
}) })] })] }, index))), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) })] })] })] }));
|
|
122
125
|
}
|
|
@@ -85,11 +85,11 @@ export function WriteAShortLetterClient({ questionData, onSubmit, isReviewMode =
|
|
|
85
85
|
const target = minWords;
|
|
86
86
|
return Math.min((wordCount / target) * 100, 100);
|
|
87
87
|
}, [wordCount, minWords]);
|
|
88
|
-
return (_jsxs("div", { className: "space-y-
|
|
88
|
+
return (_jsxs("div", { className: "space-y-3", children: [instruction && (_jsx("div", { className: "overflow-hidden rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 shadow-sm", children: _jsxs("div", { className: "flex items-center gap-3 px-3 py-2", children: [_jsx("div", { className: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-blue-500 to-indigo-600 text-white shadow-sm", children: _jsx(BookOpen, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-blue-900", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(HtmlOrText, { value: instruction, className: "text-sm text-blue-700" })] })] }) })), passageList.length > 0 && (_jsxs("div", { className: "overflow-hidden rounded-xl border border-indigo-100 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-3 border-b border-indigo-50 bg-gradient-to-r from-indigo-50 to-white px-3 py-2", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-blue-600 text-white shadow-sm", children: _jsx(BookOpen, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-indigo-800", children: "B\u00E0i \u0111\u1ECDc" })] }), _jsx("div", { className: "space-y-3 p-3", children: passageList.map((item, index) => (_jsx(HtmlOrText, { value: item, className: "text-sm leading-relaxed text-gray-800" }, index))) })] })), guideImages && guideImages.length > 0 && (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-3 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-violet-500 to-purple-600 text-white shadow-sm", children: _jsx(ImageIcon, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "\u1EA2nh h\u01B0\u1EDBng d\u1EABn" })] }), _jsx("div", { className: "p-3", children: _jsx("div", { className: "flex flex-col gap-4", children: guideImages.map((imageKey, index) => (_jsx(GuideImageDisplay, { imageKey: imageKey }, `${imageKey}-${index}`))) }) })] })), informations && informations.filter((i) => i?.trim()).length > 0 && (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-3 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-emerald-500 to-teal-600 text-white shadow-sm", children: _jsx(FileText, { className: "h-4 w-4" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["Th\u00F4ng tin c\u1EA7n bao g\u1ED3m (", informations.length, ")"] })] }), _jsx("div", { className: "p-3", children: _jsx("div", { className: "space-y-3", children: informations.filter((i) => i?.trim()).map((info, index) => (_jsxs("div", { className: "flex items-start gap-3 rounded-lg border border-gray-100 bg-gradient-to-r from-white to-gray-50 p-3 transition-all hover:border-gray-200 hover:shadow-sm", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-emerald-400 to-teal-500 text-xs font-bold text-white shadow-sm", children: index + 1 }), _jsx(HtmlOrText, { value: info, className: "text-sm text-gray-700 leading-relaxed pt-0.5" })] }, index))) }) })] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-purple-500 to-violet-600 text-white shadow-sm", children: _jsx(PenLine, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Vi\u1EBFt th\u01B0 c\u1EE7a b\u1EA1n" })] }), _jsxs("div", { className: cn('flex items-center gap-2 rounded-full px-3 py-1.5 text-xs font-semibold transition-all', isExceeded
|
|
89
89
|
? 'bg-red-100 text-red-700'
|
|
90
90
|
: isValidWordCount
|
|
91
91
|
? 'bg-green-100 text-green-700'
|
|
92
|
-
: 'bg-amber-100 text-amber-700'), children: [isValidWordCount && !isExceeded && _jsx(Check, { className: "h-3.5 w-3.5" }), isExceeded && _jsx(AlertCircle, { className: "h-3.5 w-3.5" }), _jsxs("span", { children: [wordCount, " / ", minWords, maxWords ? ` - ${maxWords}` : '+', " t\u1EEB"] })] })] }), _jsxs("div", { className: "p-
|
|
92
|
+
: 'bg-amber-100 text-amber-700'), children: [isValidWordCount && !isExceeded && _jsx(Check, { className: "h-3.5 w-3.5" }), isExceeded && _jsx(AlertCircle, { className: "h-3.5 w-3.5" }), _jsxs("span", { children: [wordCount, " / ", minWords, maxWords ? ` - ${maxWords}` : '+', " t\u1EEB"] })] })] }), _jsxs("div", { className: "p-3 space-y-3", children: [!isReviewMode && (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between text-xs text-gray-500", children: [_jsx("span", { children: "Ti\u1EBFn \u0111\u1ED9 vi\u1EBFt" }), _jsxs("span", { children: [Math.round(progressPercentage), "%"] })] }), _jsx("div", { className: "h-2 w-full overflow-hidden rounded-full bg-gray-100", children: _jsx("div", { className: cn('h-full transition-all duration-300 ease-out rounded-full', isExceeded
|
|
93
93
|
? 'bg-gradient-to-r from-red-400 to-rose-500'
|
|
94
94
|
: isValidWordCount
|
|
95
95
|
? 'bg-gradient-to-r from-green-400 to-emerald-500'
|
|
@@ -4,11 +4,10 @@ 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 { PointsInput } from '../../../../components/ui/points-input';
|
|
8
7
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
9
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
10
8
|
import { RichTextEditor } from '../../../../components/shared/RichTextEditor';
|
|
11
|
-
import {
|
|
9
|
+
import { Plus, Trash2, Mail, ImageIcon, X } from 'lucide-react';
|
|
10
|
+
import { CompactCreatorHeader, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
12
11
|
import { WriteAShortLetterClient } from './WriteAShortLetterClient';
|
|
13
12
|
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
14
13
|
import { usePresignedFileUrl } from '../../../../shared/lib/hooks/usePresignedFileUrl';
|
|
@@ -294,32 +293,39 @@ function WriteAShortLetterCreatorContent({ initialData, onChange, externalErrors
|
|
|
294
293
|
guideImages: guideImages.length > 0 ? guideImages : undefined,
|
|
295
294
|
...(passage ? { passage } : {}),
|
|
296
295
|
};
|
|
297
|
-
return (_jsxs("div", { className: "space-y-
|
|
296
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(WriteAShortLetterClient, { questionData: questionData, onSubmit: () => { }, isReviewMode: true })] }));
|
|
298
297
|
}
|
|
299
|
-
return (_jsxs("div", { className: "space-y-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
298
|
+
return (_jsxs("div", { className: "space-y-3", children: [externalErrors && externalErrors.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-red-200 bg-red-50 p-2 text-xs text-red-700", children: [_jsx("span", { className: "font-semibold text-red-800", children: "L\u1ED7i:" }), _jsx("span", { children: externalErrors.join(' · ') })] })), _jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(Mail, { className: "h-3.5 w-3.5" }), title: "Write a Short Letter", points: {
|
|
299
|
+
value: points,
|
|
300
|
+
onValueChange: setPoints,
|
|
301
|
+
error: errors.points,
|
|
302
|
+
}, preview: {
|
|
303
|
+
onClick: () => setIsClientMode(true),
|
|
304
|
+
disabled: !minWords || minWords < 1,
|
|
305
|
+
} }), children: [_jsxs("div", { className: "rounded-md border border-gray-200 bg-gray-50/50 p-3", children: [_jsx(Label, { className: "mb-3 block text-sm font-medium text-gray-700", children: "Gi\u1EDBi h\u1EA1n s\u1ED1 t\u1EEB" }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex-1", children: [_jsxs(Label, { htmlFor: "minWords", className: "text-xs text-gray-500", children: ["T\u1ED1i thi\u1EC3u ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { id: "minWords", type: "number", min: "1", step: "1", value: minWords ?? '', onChange: (e) => {
|
|
306
|
+
const value = e.target.value;
|
|
307
|
+
if (value === '') {
|
|
308
|
+
setMinWords(1);
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
const numValue = parseInt(value, 10);
|
|
312
|
+
setMinWords(numValue < 1 ? 1 : numValue);
|
|
313
|
+
}
|
|
314
|
+
form.clearErrors('minWords');
|
|
315
|
+
}, placeholder: "20", className: `mt-1 ${errors.minWords ? 'border-red-500 focus:ring-red-500' : ''}` }), errors.minWords && (_jsx("p", { className: "mt-1 text-xs text-red-600", children: errors.minWords }))] }), _jsx("span", { className: "mt-6 text-gray-400", children: "\u2014" }), _jsxs("div", { className: "flex-1", children: [_jsx(Label, { htmlFor: "maxWords", className: "text-xs text-gray-500", children: "T\u1ED1i \u0111a" }), _jsx(Input, { id: "maxWords", type: "number", min: "1", step: "1", value: maxWords ?? '', onChange: (e) => {
|
|
316
|
+
const value = e.target.value;
|
|
317
|
+
if (value === '') {
|
|
318
|
+
setMaxWords(undefined);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
const numValue = parseInt(value, 10);
|
|
322
|
+
setMaxWords(numValue < 1 ? 1 : numValue);
|
|
323
|
+
}
|
|
324
|
+
form.clearErrors('maxWords');
|
|
325
|
+
}, placeholder: "\u221E", className: `mt-1 ${errors.maxWords ? 'border-red-500 focus:ring-red-500' : ''}` }), errors.maxWords && (_jsx("p", { className: "mt-1 text-xs text-red-600", children: errors.maxWords }))] })] })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "instruction", className: "text-sm font-medium", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx("div", { className: `mt-1.5 ${errors.instruction ? 'rounded-md ring-1 ring-red-500' : ''}`, children: _jsx(RichTextEditor, { value: instruction, onChange: (html) => {
|
|
326
|
+
setInstruction(html);
|
|
327
|
+
form.clearErrors('instruction');
|
|
328
|
+
}, minHeightClassName: "min-h-[120px]" }) }), errors.instruction && (_jsx("p", { className: "mt-1 text-sm text-red-600", children: errors.instruction }))] }), _jsxs("div", { className: "space-y-3 rounded-lg border border-blue-100 bg-blue-50/30 p-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-full bg-blue-100", children: _jsx("span", { className: "text-xs font-semibold text-blue-600", children: "i" }) }), _jsx(Label, { className: "text-sm font-semibold text-gray-800", children: "Th\u00F4ng tin c\u1EA7n bao g\u1ED3m" })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addInformation, className: "h-8 gap-1.5 border-blue-200 bg-white text-blue-600 hover:bg-blue-50 hover:text-blue-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm"] })] }), errors.informations && (_jsx("div", { className: "rounded-md border border-red-200 bg-red-50 p-3", children: _jsx("p", { className: "text-sm text-red-600", children: errors.informations }) })), _jsx("div", { className: "space-y-2", children: informations.map((info, index) => (_jsx("div", { className: "space-y-1", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600", children: index + 1 }), _jsx(Input, { value: info, onChange: (e) => updateInformation(index, e.target.value), placeholder: `Thông tin ${index + 1}...`, className: "flex-1 border-gray-200" }), informations.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeInformation(index), className: "h-8 w-8 flex-shrink-0 p-0 text-gray-400 hover:bg-red-50 hover:text-red-500", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }) }, index))) }), _jsx("p", { className: "text-xs text-gray-500 italic", children: "\uD83D\uDCA1 Li\u1EC7t k\u00EA c\u00E1c th\u00F4ng tin h\u1ECDc sinh c\u1EA7n \u0111\u1EC1 c\u1EADp trong b\u00E0i vi\u1EBFt" })] }), _jsxs("div", { className: "space-y-3 rounded-lg border border-violet-100 bg-violet-50/30 p-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-full bg-violet-100", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5 text-violet-600" }) }), _jsx(Label, { className: "text-sm font-semibold text-gray-800", children: "\u1EA2nh h\u01B0\u1EDBng d\u1EABn" }), _jsx("span", { className: "text-xs text-gray-400" })] }), guideImages.length > 0 && (_jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4", children: guideImages.map((imageKey, index) => (_jsx(GuideImageItem, { imageKey: imageKey, index: index, localPreviewUrl: guideImagePreviewUrls[imageKey], onRemove: removeGuideImage }, `${imageKey}-${index}`))) })), _jsx(FileUpload, { label: "Th\u00EAm \u1EA3nh h\u01B0\u1EDBng d\u1EABn", accept: "image/*", value: "", onChange: handleGuideImageUpload, onPresignedUrlChange: handleGuideImagePresignedUrl, autoUpload: true, prefix: "write-short-letter/guide", placeholder: "Ch\u1ECDn ho\u1EB7c k\u00E9o th\u1EA3 \u1EA3nh v\u00E0o \u0111\u00E2y", icon: _jsx(ImageIcon, { className: "h-4 w-4" }) }), _jsx("p", { className: "text-xs text-gray-500 italic", children: "\uD83D\uDCF7 Upload nhi\u1EC1u \u1EA3nh \u0111\u1EC3 h\u01B0\u1EDBng d\u1EABn h\u1ECDc sinh vi\u1EBFt b\u00E0i (v\u00ED d\u1EE5: m\u1EABu th\u01B0, h\u00ECnh \u1EA3nh minh h\u1ECDa)" })] })] })] }));
|
|
323
329
|
}
|
|
324
330
|
export function WriteAShortLetterCreator(props) {
|
|
325
331
|
return _jsx(WriteAShortLetterCreatorContent, { ...props });
|
package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js
CHANGED
|
@@ -47,7 +47,7 @@ export function WriteCorrectVerbFormClient({ questionData, onSubmit, isReviewMod
|
|
|
47
47
|
}) }));
|
|
48
48
|
};
|
|
49
49
|
// ============ RENDER ============
|
|
50
|
-
return (_jsxs("div", { className: "space-y-
|
|
50
|
+
return (_jsxs("div", { className: "space-y-3", children: [questionData.instruction && (_jsx("div", { className: "overflow-hidden rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 shadow-sm", children: _jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [_jsx("div", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(FileText, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-blue-900", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx("p", { className: "text-sm text-blue-700", children: questionData.instruction })] })] }) })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: cn('flex items-center justify-between border-b px-3 py-2', isReviewMode && isCorrect && 'border-green-200 bg-gradient-to-r from-green-50 to-emerald-50', isReviewMode && isWrong && 'border-red-200 bg-gradient-to-r from-red-50 to-rose-50', (!isReviewMode || (!isCorrect && !isWrong)) && 'border-gray-100 bg-gradient-to-r from-gray-50 to-white'), children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: cn('flex h-7 w-7 items-center justify-center rounded-md text-white', isReviewMode && isCorrect && 'bg-gradient-to-br from-green-500 to-emerald-600', isReviewMode && isWrong && 'bg-gradient-to-br from-red-500 to-rose-600', (!isReviewMode || (!isCorrect && !isWrong)) && 'bg-gradient-to-br from-violet-500 to-purple-600'), children: _jsx(PenLine, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Chia \u0111\u1ED9ng t\u1EEB \u0111\u00FAng" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isCorrect ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'), children: [isCorrect ? _jsx(Check, { className: "h-3.5 w-3.5" }) : _jsx(X, { className: "h-3.5 w-3.5" }), isCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsx("div", { className: "rounded-xl bg-gradient-to-br from-slate-50 to-gray-50 border border-gray-100 p-5", children: renderSentenceWithInput() }), isReviewMode && (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: cn('rounded-xl border-2 p-4', isCorrect
|
|
51
51
|
? 'border-green-200 bg-green-50'
|
|
52
52
|
: isWrong
|
|
53
53
|
? 'border-red-200 bg-red-50'
|
|
@@ -57,5 +57,5 @@ export function WriteCorrectVerbFormClient({ questionData, onSubmit, isReviewMod
|
|
|
57
57
|
? 'bg-gradient-to-br from-green-500 to-emerald-600'
|
|
58
58
|
: 'bg-gradient-to-br from-red-500 to-rose-600'), children: isCorrect ? _jsx(Check, { className: "h-5 w-5" }) : _jsx(X, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("p", { className: cn('text-sm font-semibold', isCorrect ? 'text-green-800' : 'text-red-800'), children: isCorrect ? 'Chính xác!' : 'Chưa đúng' }), _jsx("p", { className: cn('text-xs', isCorrect ? 'text-green-600' : 'text-red-600'), children: isCorrect
|
|
59
59
|
? 'Bạn đã chia động từ đúng.'
|
|
60
|
-
: `Đáp án đúng là "${questionData.correctAnswer}".` })] })] }) }), questionData.explanation && (_jsx("div", { className: "rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-semibold text-blue-700 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-gray-700 leading-relaxed", children: questionData.explanation })] })] }) }))] })), !isReviewMode && !hasAnswered && (_jsx("div", { className: "rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", 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 bg-gradient-to-br from-amber-400 to-orange-500 text-white shadow-sm", children: _jsx("span", { className: "text-sm font-bold", children: "!" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-amber-800", children: "Nh\u1EADp d\u1EA1ng \u0111\u00FAng c\u1EE7a \u0111\u1ED9ng t\u1EEB" }), _jsx("p", { className: "text-xs text-amber-600", children: "S\u1EED d\u1EE5ng g\u1EE3i \u00FD trong ngo\u1EB7c \u0111\u1EC3 chia \u0111\u1ED9ng t\u1EEB ph\u00F9 h\u1EE3p." })] })] }) }))] })] }), !isReviewMode && questionData.caseSensitive && (_jsx("div", { className: "overflow-hidden rounded-xl border border-yellow-200 bg-gradient-to-r from-yellow-50 to-amber-50 shadow-sm", children: _jsxs("div", { className: "flex items-center gap-
|
|
60
|
+
: `Đáp án đúng là "${questionData.correctAnswer}".` })] })] }) }), questionData.explanation && (_jsx("div", { className: "rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-semibold text-blue-700 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-gray-700 leading-relaxed", children: questionData.explanation })] })] }) }))] })), !isReviewMode && !hasAnswered && (_jsx("div", { className: "rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", 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 bg-gradient-to-br from-amber-400 to-orange-500 text-white shadow-sm", children: _jsx("span", { className: "text-sm font-bold", children: "!" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-amber-800", children: "Nh\u1EADp d\u1EA1ng \u0111\u00FAng c\u1EE7a \u0111\u1ED9ng t\u1EEB" }), _jsx("p", { className: "text-xs text-amber-600", children: "S\u1EED d\u1EE5ng g\u1EE3i \u00FD trong ngo\u1EB7c \u0111\u1EC3 chia \u0111\u1ED9ng t\u1EEB ph\u00F9 h\u1EE3p." })] })] }) }))] })] }), !isReviewMode && questionData.caseSensitive && (_jsx("div", { className: "overflow-hidden rounded-xl border border-yellow-200 bg-gradient-to-r from-yellow-50 to-amber-50 shadow-sm", children: _jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [_jsx("div", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md bg-gradient-to-br from-yellow-400 to-amber-500 text-white", children: _jsx(AlertTriangle, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-yellow-900", children: "L\u01B0u \u00FD quan tr\u1ECDng" }), _jsx("p", { className: "text-sm text-yellow-700", children: "\u0110\u00E1p \u00E1n c\u00F3 ph\u00E2n bi\u1EC7t ch\u1EEF hoa/th\u01B0\u1EDDng" })] })] }) }))] }));
|
|
61
61
|
}
|