@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
|
@@ -187,7 +187,7 @@ export function FillInBlankClient({ questionData, questionConfig, onSubmit, isRe
|
|
|
187
187
|
const explText = explanation || questionData.explanation;
|
|
188
188
|
if (!isReviewMode || !explText)
|
|
189
189
|
return null;
|
|
190
|
-
return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-blue-200 bg-gradient-to-br from-blue-50 to-indigo-50 shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2
|
|
190
|
+
return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-blue-200 bg-gradient-to-br from-blue-50 to-indigo-50 shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-blue-100 bg-white/60 px-3 py-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gradient-to-br from-amber-400 to-orange-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsx("h4", { className: "text-sm font-semibold text-slate-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("div", { className: "p-3", children: _jsx("p", { className: "text-sm text-slate-700 leading-relaxed", children: explText }) })] }));
|
|
191
191
|
};
|
|
192
192
|
/** Question-level image (non-group) */
|
|
193
193
|
const renderQuestionImage = () => {
|
|
@@ -208,11 +208,11 @@ export function FillInBlankClient({ questionData, questionConfig, onSubmit, isRe
|
|
|
208
208
|
// =====================================================
|
|
209
209
|
if (!isLegacyFormat) {
|
|
210
210
|
const hasAnyAnswer = placeholderIndices.some(idx => (answers[idx] || '').trim());
|
|
211
|
-
return (_jsxs("div", { className: "space-y-4", children: [renderGroupBlock(), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition-all hover:shadow-md", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-
|
|
211
|
+
return (_jsxs("div", { className: "space-y-4", children: [renderGroupBlock(), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition-all hover:shadow-md", 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-orange-500 to-amber-600 text-white", children: _jsx(PenLine, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-semibold text-gray-700", children: "\u0110i\u1EC1n v\u00E0o ch\u1ED7 tr\u1ED1ng" }), _jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-orange-100 px-2 py-0.5 text-xs font-medium text-orange-700 ring-1 ring-inset ring-orange-200/60", children: [_jsx(Hash, { className: "h-3 w-3" }), placeholderIndices.length] })] })] }), isReviewMode && score && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-bold', score.correct === score.total
|
|
212
212
|
? 'bg-emerald-100 text-emerald-700 ring-1 ring-emerald-200/60'
|
|
213
213
|
: score.correct >= score.total / 2
|
|
214
214
|
? 'bg-yellow-100 text-yellow-700 ring-1 ring-yellow-200/60'
|
|
215
|
-
: 'bg-red-100 text-red-700 ring-1 ring-red-200/60'), children: [score.correct === score.total && _jsx(Check, { className: "h-3 w-3" }), score.correct, "/", score.total, " \u0111\u00FAng"] }))] }), _jsxs("div", { className: "
|
|
215
|
+
: 'bg-red-100 text-red-700 ring-1 ring-red-200/60'), children: [score.correct === score.total && _jsx(Check, { className: "h-3 w-3" }), score.correct, "/", score.total, " \u0111\u00FAng"] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [showAudio && cleanAudioUrl && (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-indigo-100 bg-gradient-to-r from-indigo-50 to-blue-50 p-3", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-indigo-100", children: _jsx(Volume2, { className: "h-4 w-4 text-indigo-600" }) }), _jsx("audio", { controls: true, className: "h-9 w-full", src: audioPreviewUrl || cleanAudioUrl, preload: "metadata", children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." })] })), _jsx("div", { className: "rounded-xl bg-gradient-to-r from-orange-50/80 to-amber-50/50 p-5 border border-orange-100/80", children: renderQuestionWithBlanks() }), renderQuestionImage(), renderHints(), isReviewMode && (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white overflow-hidden shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-gray-100 bg-gradient-to-r from-slate-50 to-gray-50 px-4 py-2.5", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-slate-500 text-white", children: _jsx(ClipboardList, { className: "h-3 w-3" }) }), _jsx("h4", { className: "text-xs font-semibold text-gray-600 uppercase tracking-wider", children: "Chi ti\u1EBFt \u0111\u00E1p \u00E1n" })] }), _jsx("div", { className: "p-4 space-y-2.5", children: placeholderIndices.map((idx) => {
|
|
216
216
|
const userInput = answers[idx] || '';
|
|
217
217
|
const correctAnswer = formatBlankAnswersDisplay(correctAnswersMatrix[idx]) || '—';
|
|
218
218
|
const isCorrect = isAnswerCorrect(idx, userInput);
|
|
@@ -231,9 +231,9 @@ export function FillInBlankClient({ questionData, questionConfig, onSubmit, isRe
|
|
|
231
231
|
const userInput = answers[index] || '';
|
|
232
232
|
const isCorrect = isReviewMode && isAnswerCorrect(index, userInput);
|
|
233
233
|
const hasAnswered = userInput.trim() !== '';
|
|
234
|
-
return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition-all hover:shadow-md", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-
|
|
234
|
+
return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition-all hover:shadow-md", 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-orange-500 to-amber-600 text-white", children: _jsx(PenLine, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-semibold text-gray-700", children: ["C\u00E2u ", index + 1, ": \u0110i\u1EC1n v\u00E0o ch\u1ED7 tr\u1ED1ng"] })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-bold', isCorrect
|
|
235
235
|
? 'bg-emerald-100 text-emerald-700 ring-1 ring-emerald-200/60'
|
|
236
|
-
: 'bg-red-100 text-red-700 ring-1 ring-red-200/60'), 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: "
|
|
236
|
+
: 'bg-red-100 text-red-700 ring-1 ring-red-200/60'), 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: [index === 0 && showAudio && cleanAudioUrl && (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-indigo-100 bg-gradient-to-r from-indigo-50 to-blue-50 p-3", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-indigo-100", children: _jsx(Volume2, { className: "h-4 w-4 text-indigo-600" }) }), _jsx("audio", { controls: true, className: "h-9 w-full", src: audioPreviewUrl || cleanAudioUrl, preload: "metadata", children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." })] })), _jsx("div", { className: "rounded-xl bg-gradient-to-r from-orange-50/80 to-amber-50/50 p-5 border border-orange-100/80", children: renderLegacyBlank(blank.text, blank.id, index) }), index === 0 && renderQuestionImage(), index === 0 && renderHints(), isReviewMode && (_jsx("div", { className: cn('rounded-xl border p-3 transition-colors', isCorrect
|
|
237
237
|
? 'border-emerald-200 bg-emerald-50/50'
|
|
238
238
|
: hasAnswered
|
|
239
239
|
? 'border-red-200 bg-red-50/50'
|
|
@@ -6,10 +6,9 @@ import { Button } from '../../../../components/ui/button';
|
|
|
6
6
|
import { Label } from '../../../../components/ui/label';
|
|
7
7
|
import { Input } from '../../../../components/ui/input';
|
|
8
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
9
|
import { Switch } from '../../../../components/ui/switch';
|
|
12
|
-
import {
|
|
10
|
+
import { Eye, Type, MessageSquare, Plus, Trash2, ListChecks, ImageIcon, Volume2 } from 'lucide-react';
|
|
11
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
13
12
|
import { FillInBlankClient } from './FillInBlankClient';
|
|
14
13
|
import { blankHasAnswer, compactFillInBlankAnswers, formatBlankAnswersDisplay, extractPlaceholderIndices, getMaxPlaceholderIndex, normalizeFillInBlankAnswers, } from './answer-utils';
|
|
15
14
|
import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
@@ -151,6 +150,8 @@ function FillInBlankCreatorContent({ initialData, onChange, externalErrors, vali
|
|
|
151
150
|
// UI State
|
|
152
151
|
const [isClientMode, setIsClientMode] = useState(false);
|
|
153
152
|
const [imagePreviewUrl, setImagePreviewUrl] = useState('');
|
|
153
|
+
const [isImageInputVisible, setIsImageInputVisible] = useState(() => Boolean(initialData?.content?.imageUrl || initialData?.imageUrl));
|
|
154
|
+
const [isAudioInputVisible, setIsAudioInputVisible] = useState(() => Boolean(initialData?.audioUrl || initialData?.content?.audioUrl));
|
|
154
155
|
// Presigned URL for per-question image preview (non-grouped only)
|
|
155
156
|
const { previewUrl: generatedPreviewUrl } = usePresignedFileUrl(imageUrl);
|
|
156
157
|
const displayPreviewUrl = imagePreviewUrl || generatedPreviewUrl || '';
|
|
@@ -418,42 +419,49 @@ function FillInBlankCreatorContent({ initialData, onChange, externalErrors, vali
|
|
|
418
419
|
groupNumber: isGrouped ? currentGroupNumber : undefined,
|
|
419
420
|
...(isGrouped && isImageLabelGroup ? { imageLabels } : {}),
|
|
420
421
|
};
|
|
421
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsx(
|
|
422
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(FillInBlankClient, { questionData: questionData, questionConfig: questionConfig, autoFillCorrectAnswers: true, isReviewMode: false, explanation: explanation })] }));
|
|
422
423
|
}
|
|
423
424
|
// ============ CREATOR UI ============
|
|
424
|
-
return (_jsxs("div", { className: "space-y-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
425
|
+
return (_jsxs("div", { className: "space-y-3", children: [isGrouped && isImageLabelGroup && (_jsx(ImageLabelGroupCard, { groupNumber: currentGroupNumber, imageLabels: imageLabels, onImageLabelsChange: setImageLabels, title: groupTitle, onTitleChange: setGroupTitle, isReadOnly: !isFirstInGroup, uploadIdPrefix: `fill-in-blank-${partId ?? 'image-label'}` })), isGrouped && !isImageLabelGroup && (_jsx(BasicQuestionGroupCard, { groupNumber: currentGroupNumber, title: groupTitle, subtitle: groupSubtitle, content: groupContent, imageUrl: groupImageUrl, onTitleChange: setGroupTitle, onSubtitleChange: setGroupSubtitle, onContentChange: setGroupContent, onImageChange: setGroupImageUrl, uploadIdPrefix: `fill-in-blank-${partId ?? 'group'}` })), _jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(Type, { className: "h-3.5 w-3.5" }), title: "\u0110i\u1EC1n v\u00E0o ch\u1ED7 tr\u1ED1ng", points: { value: points, onValueChange: setPoints }, example: { checked: isExample, onChange: setIsExample }, extraActions: _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(Switch, { id: "show-suggested-words", checked: showHints, onCheckedChange: setShowHints }), _jsx(Label, { htmlFor: "show-suggested-words", className: "cursor-pointer text-xs font-medium text-gray-700", children: "T\u1EEB g\u1EE3i \u00FD" })] }), headerExtra: headerExtra, preview: {
|
|
426
|
+
onClick: () => setIsClientMode(true),
|
|
427
|
+
disabled: !question?.trim() || placeholderIndices.length === 0,
|
|
428
|
+
} }), children: [_jsxs("div", { className: "space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs(Label, { htmlFor: "question", className: "flex items-center gap-1.5 text-sm font-semibold text-gray-800", children: [_jsx(MessageSquare, { className: "h-3.5 w-3.5 text-indigo-500" }), "C\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsxs("div", { className: "flex items-center gap-1", children: [!isGrouped && (_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 ${imageUrl || isImageInputVisible
|
|
429
|
+
? 'bg-indigo-600 text-white shadow-sm'
|
|
430
|
+
: 'bg-indigo-50 text-indigo-600 hover:bg-indigo-100'}`, children: _jsx(ImageIcon, { className: "h-4 w-4" }) })), includesAudio && (_jsx("button", { type: "button", title: "Th\u00EAm audio", onClick: () => setIsAudioInputVisible(true), className: `inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors ${audioUrl || isAudioInputVisible
|
|
431
|
+
? 'bg-violet-600 text-white shadow-sm'
|
|
432
|
+
: 'bg-violet-50 text-violet-600 hover:bg-violet-100'}`, children: _jsx(Volume2, { className: "h-4 w-4" }) }))] })] }), _jsx(Textarea, { id: "question", value: question, onChange: (e) => {
|
|
433
|
+
setQuestion(e.target.value);
|
|
434
|
+
if (validationErrors.question) {
|
|
435
|
+
setValidationErrors(prev => ({ ...prev, question: undefined }));
|
|
436
|
+
}
|
|
437
|
+
}, placeholder: "V\u00ED d\u1EE5: He {0} 10 years old and she {1} 12.", rows: 3, className: `min-h-[72px] border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${validationErrors.question ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}` }), validationErrors.question && (_jsx("p", { className: "text-sm text-red-600", children: validationErrors.question })), placeholderIndices.length > 0 && (_jsx("div", { className: "flex items-center gap-2 rounded-md bg-green-50 px-2.5 py-1.5", children: _jsxs("span", { className: "text-xs text-green-700", children: ["\u2713 ", _jsx("strong", { children: placeholderIndices.length }), " ch\u1ED7 tr\u1ED1ng: ", placeholderIndices.map(i => (_jsx("code", { className: "mx-0.5 rounded bg-green-100 px-1.5 py-0.5 font-mono text-green-800", children: `{${i}}` }, i)))] }) }))] }), !isGrouped && isImageInputVisible && (_jsxs("div", { className: "space-y-2", children: [_jsx(FileUpload, { id: "fill-in-blank-image", label: "", accept: "image/*", value: imageUrl || '', onChange: (url) => {
|
|
438
|
+
setImageUrl(url);
|
|
439
|
+
}, onPresignedUrlChange: setImagePreviewUrl, maxSize: 5, placeholder: "Upload \u1EA3nh ho\u1EB7c paste URL", autoUpload: true, prefix: "questions" }), displayPreviewUrl && (_jsx(ImagePreview, { src: displayPreviewUrl, alt: "Preview", className: "mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200" }))] })), includesAudio && isAudioInputVisible && (_jsx(FileUpload, { id: "fill-in-blank-audio", label: "", accept: "audio/*", value: audioUrl || '', onChange: (url) => {
|
|
440
|
+
setAudioUrl(url);
|
|
441
|
+
}, maxSize: 20, placeholder: "Upload file audio", autoUpload: true, prefix: "questions", showPlayButton: true })), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100", children: _jsx(Type, { className: "h-3.5 w-3.5 text-indigo-600" }) }), _jsxs(Label, { className: "text-sm font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n cho c\u00E1c ch\u1ED7 tr\u1ED1ng ", _jsx("span", { className: "text-red-500", children: "*" })] })] }), _jsxs("div", { className: "grid gap-3 lg:grid-cols-2", children: [_jsx("div", { className: "space-y-2", children: (placeholderIndices.length > 0 ? placeholderIndices : [0]).map((idx) => {
|
|
442
|
+
const blankAlts = answers[idx]?.length ? answers[idx] : [''];
|
|
443
|
+
const hasAnswer = blankHasAnswer(blankAlts);
|
|
444
|
+
return (_jsxs("div", { className: `rounded-md border p-2.5 transition-all ${hasAnswer
|
|
445
|
+
? 'border-green-200 bg-gradient-to-r from-green-50 to-emerald-50'
|
|
446
|
+
: 'border-gray-200 bg-white'}`, children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx("div", { className: `flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md font-mono text-xs font-bold ${hasAnswer
|
|
447
|
+
? 'bg-gradient-to-br from-green-500 to-emerald-600 text-white'
|
|
448
|
+
: 'bg-indigo-100 text-indigo-700'}`, children: `{${idx}}` }), _jsxs("p", { className: "text-xs font-medium text-gray-700", children: ["Ch\u1ED7 tr\u1ED1ng ", idx + 1] })] }), _jsx("div", { className: "space-y-1.5", children: blankAlts.map((alt, altIdx) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { value: alt, onChange: (e) => updateBlankAnswer(idx, altIdx, e.target.value), placeholder: altIdx === 0 ? `Đáp án chính cho {${idx}}` : `Đáp án thay thế ${altIdx + 1}`, className: `h-8 border-gray-200 bg-white ${validationErrors.answers?.[idx] ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}` }), blankAlts.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeBlankAlternative(idx, altIdx), className: "h-7 w-7 flex-shrink-0 text-red-500 hover:bg-red-50 hover:text-red-600", title: "X\u00F3a \u0111\u00E1p \u00E1n n\u00E0y", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }, altIdx))) }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => addBlankAlternative(idx), className: "mt-2 h-8 gap-1.5 border-dashed border-indigo-200 px-2.5 text-xs text-indigo-600 hover:bg-indigo-50", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm \u0111\u00E1p \u00E1n \u0111\u00FAng"] }), validationErrors.answers?.[idx] && (_jsx("p", { className: "mt-2 text-sm text-red-600", children: validationErrors.answers[idx] }))] }, idx));
|
|
449
|
+
}) }), showHints && (_jsxs("div", { className: "space-y-2.5 rounded-md border border-purple-200 bg-purple-50/70 p-2.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-purple-100", children: _jsx(ListChecks, { className: "h-3.5 w-3.5 text-purple-600" }) }), _jsxs(Label, { className: "text-sm font-semibold text-gray-800", children: ["T\u1EEB g\u1EE3i \u00FD ", _jsxs("span", { className: "text-xs font-normal text-gray-500", children: ["(", hints.length, " t\u1EEB)"] })] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Input, { value: newHint, onChange: (e) => setNewHint(e.target.value), placeholder: "Nh\u1EADp t\u1EEB g\u1EE3i \u00FD m\u1EDBi...", className: "h-8 flex-1 border-gray-200 bg-white", onKeyDown: (e) => {
|
|
450
|
+
if (e.key === 'Enter' && newHint.trim()) {
|
|
451
|
+
e.preventDefault();
|
|
452
|
+
if (!hints.includes(newHint.trim())) {
|
|
453
|
+
setHints([...hints, newHint.trim()]);
|
|
454
|
+
}
|
|
455
|
+
setNewHint('');
|
|
456
|
+
}
|
|
457
|
+
} }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
|
|
458
|
+
if (newHint.trim() && !hints.includes(newHint.trim())) {
|
|
459
|
+
setHints([...hints, newHint.trim()]);
|
|
460
|
+
setNewHint('');
|
|
461
|
+
}
|
|
462
|
+
}, disabled: !newHint.trim(), 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"] })] }), hints.length > 0 ? (_jsx("div", { className: "flex flex-wrap gap-1.5", children: hints.map((word, index) => (_jsxs("div", { className: "flex items-center gap-1.5 rounded-md border border-purple-200 bg-white px-2 py-1", children: [_jsx("span", { className: "text-xs font-medium text-gray-700", children: word }), _jsx("button", { type: "button", onClick: () => {
|
|
463
|
+
setHints(hints.filter((_, i) => i !== index));
|
|
464
|
+
}, className: "rounded-full p-0.5 text-gray-400 transition-colors hover:bg-red-100 hover:text-red-500", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }, index))) })) : (_jsx("p", { className: "text-center text-xs text-gray-500 italic", children: "Ch\u01B0a c\u00F3 t\u1EEB g\u1EE3i \u00FD n\u00E0o. Th\u00EAm t\u1EEB \u0111\u1EC3 hi\u1EC3n th\u1ECB cho h\u1ECDc sinh." }))] }))] })] }), question && (_jsxs("div", { className: "rounded-md border border-gray-200 bg-gray-50 p-2.5", children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 text-xs font-semibold text-gray-600", children: [_jsx(Eye, { className: "h-3.5 w-3.5" }), "Xem tr\u01B0\u1EDBc c\u00E2u ho\u00E0n ch\u1EC9nh"] }), _jsx("p", { className: "text-sm text-gray-700", children: getPreviewText() })] })), externalErrors && externalErrors.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-red-200 bg-red-50 p-2 text-xs text-red-700", children: [_jsx("span", { className: "font-semibold text-red-800", children: "L\u1ED7i:" }), _jsx("span", { children: externalErrors.join(' · ') })] })), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) })] })] }));
|
|
457
465
|
}
|
|
458
466
|
export function FillInBlankCreator(props) {
|
|
459
467
|
return _jsx(FillInBlankCreatorContent, { ...props });
|
|
@@ -10,7 +10,8 @@ import { Card, CardContent, CardHeader, CardTitle } from '../../../../components
|
|
|
10
10
|
import { Switch } from '../../../../components/ui/switch';
|
|
11
11
|
import { RichTextEditor } from '../../../../components/shared/RichTextEditor';
|
|
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 { FillInBlankGroupClient } from './FillInBlankGroupClient';
|
|
15
16
|
import { FillInBlankGroupMediaField } from './FillInBlankGroupMediaField';
|
|
16
17
|
import { createEmptyFillInBlankGroupItem } from './map-fill-in-blank-group-data';
|
|
@@ -168,23 +169,26 @@ export function FillInBlankGroupCreator({ initialData, onChange, externalErrors,
|
|
|
168
169
|
};
|
|
169
170
|
const currentPayload = buildPayload();
|
|
170
171
|
const displayErrors = externalErrors && externalErrors.length > 0 ? externalErrors : errors;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
172
|
+
if (isClientMode) {
|
|
173
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(FillInBlankGroupClient, { questionData: currentPayload })] }));
|
|
174
|
+
}
|
|
175
|
+
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))) }) })), _jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(HelpCircle, { className: "h-3.5 w-3.5" }), title: "\u0110i\u1EC1n ch\u1ED7 tr\u1ED1ng (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: FILL_IN_BLANK_GROUP_DEFAULT_INSTRUCTION, className: "h-10 border-gray-200 bg-white" })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { className: "text-sm font-semibold text-gray-800", children: "N\u1ED9i dung b\u00E0i \u0111\u1ECDc (Passage)" }), _jsx(RichTextEditor, { value: passage, onChange: setPassage, minHeightClassName: "min-h-[160px]" })] }), _jsxs("div", { className: "grid gap-2.5 md:grid-cols-2", children: [_jsx(FillInBlankGroupMediaField, { id: "fib-group-audio", label: "Audio chung (optional)", accept: "audio/*", value: audioUrl, onChange: setAudioUrl }), _jsx(FillInBlankGroupMediaField, { id: "fib-group-image", label: "\u1EA2nh chung (optional)", accept: "image/*", value: toFillInBlankImageUrls(imageUrl)[0] || '', onChange: setImageUrl })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: "show-hints", checked: showHints, onCheckedChange: setShowHints }), _jsx(Label, { htmlFor: "show-hints", children: "Hi\u1EC7n h\u1ED9p t\u1EEB g\u1EE3i \u00FD chung" })] }), showHints && (_jsx(Input, { value: hintsText, onChange: (e) => setHintsText(e.target.value), placeholder: "am / is / are", 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: "flex items-center gap-2 text-sm font-medium text-gray-700", 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 }), "C\u00E2u ", index + 1] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: `example-${index}`, checked: item.isExample || false, onCheckedChange: (checked) => updateItem(index, { isExample: checked, points: checked ? 0 : 1 }) }), _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, {
|
|
176
|
+
points: parsePointsValue(event.target.value, true),
|
|
177
|
+
}) }) })), 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" }) }))] })] }), _jsxs("div", { children: [_jsxs(Label, { htmlFor: `question-${index}`, children: ["Stem (d\u00F9ng ", '{0}', ", ", '{1}', "...)"] }), _jsx(Textarea, { id: `question-${index}`, value: item.question, onChange: (e) => updateItem(index, { question: e.target.value }), placeholder: "{0}", className: "mt-1 min-h-[56px] border-gray-200 bg-white" })] }), extractPlaceholderIndices(item.question).map((blankIndex) => (_jsxs("div", { className: "grid gap-2.5 md:grid-cols-2", children: [_jsxs("div", { children: [_jsxs(Label, { htmlFor: `answer-${index}-${blankIndex}`, children: ["\u0110\u00E1p \u00E1n blank ", '{' + blankIndex + '}', " (c\u00E1ch nhau b\u1EDFi /)"] }), _jsx(Input, { id: `answer-${index}-${blankIndex}`, value: joinSlashAnswers(item.answers[blankIndex]), onChange: (e) => {
|
|
178
|
+
const next = [...item.answers];
|
|
179
|
+
next[blankIndex] = parseSlashAnswers(e.target.value);
|
|
180
|
+
updateItem(index, { answers: next });
|
|
181
|
+
}, placeholder: "is / 's", className: "mt-1 h-10 border-gray-200 bg-white" })] }), _jsxs("div", { children: [_jsxs(Label, { htmlFor: `options-${index}-${blankIndex}`, children: ["Options blank ", '{' + blankIndex + '}', " (dropdown, c\u00E1ch nhau /)"] }), _jsx(Input, { id: `options-${index}-${blankIndex}`, value: joinSlashAnswers(item.wordBank?.[blankIndex]?.options), onChange: (e) => {
|
|
182
|
+
const requiredLength = blankSlotCount(item.question);
|
|
183
|
+
const next = [...(item.wordBank || [])];
|
|
184
|
+
while (next.length < requiredLength)
|
|
185
|
+
next.push(null);
|
|
186
|
+
const options = parseSlashAnswers(e.target.value);
|
|
187
|
+
next[blankIndex] = options.length >= 2 ? { options } : null;
|
|
188
|
+
updateItem(index, {
|
|
189
|
+
wordBank: next.some((entry) => entry && entry.options.length >= 2)
|
|
190
|
+
? next
|
|
191
|
+
: undefined,
|
|
192
|
+
});
|
|
193
|
+
}, placeholder: "can / can't", className: "mt-1 h-10 border-gray-200 bg-white" })] })] }, blankIndex))), _jsxs("div", { className: "grid gap-2.5 md:grid-cols-2", children: [_jsx(FillInBlankGroupMediaField, { id: `fib-item-image-${index}`, label: "\u1EA2nh item (optional)", accept: "image/*", value: Array.isArray(item.imageUrl) ? (item.imageUrl[0] || '') : (item.imageUrl || ''), onChange: (url) => updateItem(index, { imageUrl: url }) }), !audioUrl && (_jsx(FillInBlankGroupMediaField, { id: `fib-item-audio-${index}`, label: "Audio item (khi kh\u00F4ng c\u00F3 audio nh\u00F3m)", accept: "audio/*", value: item.audioUrl || '', onChange: (url) => updateItem(index, { audioUrl: url }) }))] })] }, index))), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) })] })] })] }));
|
|
190
194
|
}
|
package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js
CHANGED
|
@@ -78,13 +78,13 @@ function FillMissingWordsInGridClientContent({ questionData, onSubmit, isReviewM
|
|
|
78
78
|
}, [missingPositions, userAnswers, correctAnswers, caseSensitive]);
|
|
79
79
|
const score = isReviewMode ? calculateScore() : null;
|
|
80
80
|
const scorePercentage = score ? Math.round((score.correct / score.total) * 100) : 0;
|
|
81
|
-
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-
|
|
81
|
+
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-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-teal-500 to-cyan-600 text-white shadow-sm", children: _jsx(Grid3X3, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-base font-semibold text-gray-900", children: "\u0110i\u1EC1n t\u1EEB c\u00F2n thi\u1EBFu v\u00E0o b\u1EA3ng" }), _jsx("p", { className: "text-xs text-gray-500", children: "\u0110i\u1EC1n c\u00E1c t\u1EEB c\u00F2n thi\u1EBFu v\u00E0o c\u00E1c \u00F4 \u0111\u01B0\u1EE3c \u0111\u00E1nh d\u1EA5u m\u00E0u xanh" })] })] }), isReviewMode && score && (_jsxs("div", { className: cn('flex items-center gap-3 rounded-xl px-4 py-2', scorePercentage >= 80 ? 'bg-green-50 border border-green-200' :
|
|
82
82
|
scorePercentage >= 50 ? 'bg-yellow-50 border border-yellow-200' :
|
|
83
83
|
'bg-red-50 border border-red-200'), children: [_jsx(Trophy, { className: cn('h-5 w-5', scorePercentage >= 80 ? 'text-green-500' :
|
|
84
84
|
scorePercentage >= 50 ? 'text-yellow-500' :
|
|
85
85
|
'text-red-500') }), _jsxs("div", { className: "text-right", children: [_jsxs("div", { className: cn('text-lg font-bold', scorePercentage >= 80 ? 'text-green-600' :
|
|
86
86
|
scorePercentage >= 50 ? 'text-yellow-600' :
|
|
87
|
-
'text-red-600'), children: [score.correct, "/", score.total] }), _jsxs("div", { className: "text-xs text-gray-500", children: [scorePercentage, "% ch\u00EDnh x\u00E1c"] })] })] }))] }), _jsxs("div", { className: "p-
|
|
87
|
+
'text-red-600'), children: [score.correct, "/", score.total] }), _jsxs("div", { className: "text-xs text-gray-500", children: [scorePercentage, "% ch\u00EDnh x\u00E1c"] })] })] }))] }), _jsxs("div", { className: "p-3 space-y-3", children: [_jsx("div", { className: "overflow-x-auto rounded-lg border border-gray-200 bg-gradient-to-br from-slate-50 to-gray-100 p-4", children: _jsxs("div", { className: "inline-block min-w-full", children: [_jsxs("div", { className: "grid gap-1.5 mb-2", style: { gridTemplateColumns: `48px repeat(${columns}, minmax(80px, 1fr))` }, children: [_jsx("div", { className: "h-8" }), Array.from({ length: columns }, (_, i) => (_jsx("div", { className: "flex h-8 items-center justify-center rounded-lg bg-gradient-to-br from-gray-100 to-gray-200 text-xs font-bold text-gray-600 shadow-sm", children: i + 1 }, `col-header-${i}`)))] }), _jsx("div", { className: "grid gap-1.5", children: Array.from({ length: rows }, (_, rowIndex) => (_jsxs("div", { className: "grid gap-1.5", style: { gridTemplateColumns: `48px repeat(${columns}, minmax(80px, 1fr))` }, children: [_jsx("div", { className: "flex h-14 items-center justify-center rounded-lg bg-gradient-to-br from-gray-100 to-gray-200 text-xs font-bold text-gray-600 shadow-sm", children: rowIndex + 1 }), Array.from({ length: columns }, (_, colIndex) => {
|
|
88
88
|
const isMissing = isMissingCell(rowIndex, colIndex);
|
|
89
89
|
const isDisabled = isDisabledCell(rowIndex, colIndex);
|
|
90
90
|
const cellValue = grid[rowIndex]?.[colIndex] || '';
|
package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js
CHANGED
|
@@ -4,10 +4,8 @@ import { useState, useRef, useEffect, useCallback } 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 {
|
|
8
|
-
import {
|
|
9
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
10
|
-
import { Pencil, Eye, Trash2, Grid3X3, Lightbulb, CheckCircle2, BookOpen } from 'lucide-react';
|
|
7
|
+
import { Trash2, Grid3X3, CheckCircle2 } from 'lucide-react';
|
|
8
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
11
9
|
import { toast } from 'sonner';
|
|
12
10
|
import { t } from '../../../../shared/lib/i18n';
|
|
13
11
|
import { FillMissingWordsInGridClient } from './FillMissingWordsInGridClient';
|
|
@@ -594,74 +592,79 @@ onChange, onUnsavedChangesChange, externalErrors = [], }) {
|
|
|
594
592
|
acc[`${item.row + 1}-${item.column + 1}`] = item.correctAnswer;
|
|
595
593
|
return acc;
|
|
596
594
|
}, {});
|
|
597
|
-
return (
|
|
595
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(FillMissingWordsInGridClient, { questionData: questionData, isReviewMode: true, userAnswer: correctAnswers, explanation: explanation })] }));
|
|
598
596
|
}
|
|
599
|
-
return (_jsxs("div", { className: "space-y-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
597
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(Grid3X3, { className: "h-3.5 w-3.5" }), title: "\u0110i\u1EC1n t\u1EEB c\u00F2n thi\u1EBFu v\u00E0o grid", points: {
|
|
598
|
+
value: points,
|
|
599
|
+
onValueChange: setPoints,
|
|
600
|
+
}, preview: {
|
|
601
|
+
onClick: handlePreview,
|
|
602
|
+
} }), children: [_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100", children: _jsx(Grid3X3, { className: "h-3.5 w-3.5 text-indigo-600" }) }), _jsx(Label, { className: "text-base font-semibold text-gray-800", children: "Grid - Nh\u1EADp t\u1EEB v\u00E0o c\u00E1c \u00F4" })] }) }), _jsx("div", { className: "overflow-x-auto rounded-xl border-2 border-indigo-200 bg-gradient-to-br from-indigo-50/50 to-blue-50/50 p-5 shadow-sm", children: _jsxs("div", { className: "inline-block min-w-full", children: [_jsxs("div", { className: "grid gap-1 mb-1", style: { gridTemplateColumns: `auto repeat(${columns}, minmax(0, 1fr))` }, children: [_jsx("div", { className: "h-8" }), Array.from({ length: columns }, (_, i) => (_jsx("div", { className: "flex h-8 items-center justify-center text-xs font-semibold text-gray-600", children: i + 1 }, `col-header-${i}`)))] }), _jsx("div", { className: "grid gap-1", children: grid.map((row, rowIndex) => (_jsxs("div", { className: "grid gap-1", style: { gridTemplateColumns: `auto repeat(${columns}, minmax(0, 1fr))` }, children: [_jsx("div", { className: "flex h-12 items-center justify-center text-xs font-semibold text-gray-600 border-2 border-transparent", children: rowIndex + 1 }), row.map((cell, colIndex) => {
|
|
603
|
+
const isMissing = missingLetters.some((item) => item.row === rowIndex && item.column === colIndex);
|
|
604
|
+
const isDisabled = disabledCells.includes(`${rowIndex + 1}-${colIndex + 1}`); // NEW
|
|
605
|
+
const isInvalid = shouldShowValidation() && isCellInvalid(rowIndex, colIndex);
|
|
606
|
+
return (_jsxs("div", { className: `relative flex h-12 items-center justify-center border-2 rounded transition-colors ${isDisabled
|
|
607
|
+
? 'border-red-400 bg-red-100 hover:bg-red-150 cursor-pointer' // NEW: Red styling for disabled
|
|
608
|
+
: isMissing
|
|
609
|
+
? 'border-blue-500 bg-blue-50 hover:bg-blue-100 cursor-pointer'
|
|
610
|
+
: isInvalid
|
|
611
|
+
? 'border-red-500 bg-red-50 hover:bg-red-100'
|
|
612
|
+
: cell
|
|
613
|
+
? 'border-gray-400 bg-white hover:bg-gray-50'
|
|
614
|
+
: 'border-gray-300 bg-gray-100 hover:bg-gray-200'}`, onClick: (e) => {
|
|
615
|
+
// Triple-click to toggle disabled
|
|
616
|
+
if (e.detail === 3) {
|
|
617
|
+
e.preventDefault();
|
|
618
|
+
e.stopPropagation();
|
|
619
|
+
toggleDisabledCell(rowIndex, colIndex);
|
|
620
|
+
}
|
|
621
|
+
}, onDoubleClick: (e) => {
|
|
622
|
+
e.preventDefault();
|
|
623
|
+
e.stopPropagation();
|
|
624
|
+
if (!isDisabled) {
|
|
625
|
+
toggleMissingLetter(rowIndex, colIndex);
|
|
626
|
+
}
|
|
627
|
+
}, children: [_jsx(Input, { type: "text", maxLength: 10, value: cell || '', onChange: (e) => {
|
|
628
|
+
if (!isMissing && !isDisabled) { // NEW: Also check disabled
|
|
629
|
+
// Trim whitespace and limit length
|
|
630
|
+
const value = e.target.value.trim().slice(0, 10);
|
|
631
|
+
updateGridCell(rowIndex, colIndex, value);
|
|
617
632
|
}
|
|
618
633
|
}, onDoubleClick: (e) => {
|
|
619
634
|
e.preventDefault();
|
|
620
635
|
e.stopPropagation();
|
|
621
|
-
if (!isDisabled) {
|
|
636
|
+
if (!isDisabled) { // NEW: Only toggle missing if not disabled
|
|
622
637
|
toggleMissingLetter(rowIndex, colIndex);
|
|
623
638
|
}
|
|
624
|
-
},
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
setValidationErrors((prev) => ({
|
|
654
|
-
...prev,
|
|
655
|
-
[letter.id]: 'Đáp án không được vượt quá 100 ký tự',
|
|
656
|
-
}));
|
|
657
|
-
}
|
|
658
|
-
}, placeholder: "Nh\u1EADp \u0111\u00E1p \u00E1n \u0111\u00FAng...", className: `mt-1.5 border-gray-200 bg-white transition-all focus:border-blue-300 focus:ring-blue-200 ${validationErrors[letter.id]
|
|
659
|
-
? 'border-red-400 focus:border-red-500 focus:ring-red-200'
|
|
660
|
-
: ''}` }), validationErrors[letter.id] && (_jsx("p", { className: "mt-1.5 text-sm text-red-600", children: validationErrors[letter.id] }))] })] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeMissingLetter(letter.id), className: "h-8 w-8 text-red-400 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-4 w-4" }) })] }) }, letter.id));
|
|
661
|
-
}) })] }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-amber-400 to-orange-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-sm text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch cho \u0111\u00E1p \u00E1n \u0111\u00FAng \u2014 hi\u1EC3n th\u1ECB sau khi h\u1ECDc sinh ho\u00E0n th\u00E0nh c\u00E2u h\u1ECFi" })] })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { id: "explanation", value: explanation, onChange: (e) => {
|
|
662
|
-
setExplanation(e.target.value);
|
|
663
|
-
onUnsavedChangesChange?.(true);
|
|
664
|
-
}, placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0... v\u00EC...", rows: 3, className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none" }) })] })] }));
|
|
639
|
+
}, disabled: isMissing || isDisabled, readOnly: isMissing || isDisabled, className: `h-full border-0 bg-transparent text-center text-lg font-semibold focus:ring-2 focus:ring-blue-500 ${isMissing || isDisabled ? 'cursor-not-allowed pointer-events-none' : ''} ${isDisabled ? 'text-gray-500' : isMissing ? 'text-blue-600' : ''}`, placeholder: "" }), isMissing && (_jsx("div", { className: "absolute -top-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-blue-500 text-xs font-bold text-white", children: "?" })), isDisabled && (_jsx("div", { className: "absolute -top-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-red-500 text-xs font-bold text-white", children: "\uD83D\uDD12" }))] }, `${rowIndex}-${colIndex}`));
|
|
640
|
+
})] }, `row-${rowIndex}`))) })] }) })] }), missingLetters.length > 0 && (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-blue-100", children: _jsx(CheckCircle2, { className: "h-3.5 w-3.5 text-blue-600" }) }), _jsxs(Label, { className: "text-base font-semibold text-gray-800", children: ["Danh s\u00E1ch \u00F4 c\u00F2n thi\u1EBFu", _jsx("span", { className: "ml-2 rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700", children: missingLetters.length })] })] }), _jsx("div", { className: "space-y-3", children: [...missingLetters]
|
|
641
|
+
.sort((a, b) => {
|
|
642
|
+
if (a.row !== b.row)
|
|
643
|
+
return a.row - b.row;
|
|
644
|
+
return a.column - b.column;
|
|
645
|
+
})
|
|
646
|
+
.map((letter, index) => {
|
|
647
|
+
const cellValue = grid[letter.row]?.[letter.column];
|
|
648
|
+
return (_jsx("div", { className: "rounded-xl border-2 border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-4 shadow-sm transition-all hover:border-blue-300 hover:shadow-md", children: _jsxs("div", { className: "flex items-start gap-4", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 text-sm font-bold text-white shadow-sm", children: index + 1 }), _jsxs("div", { className: "flex-1 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "text-sm font-semibold text-gray-700", children: ["V\u1ECB tr\u00ED (", letter.row + 1, ", ", letter.column + 1, ")"] }), cellValue && (_jsxs("span", { className: "rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-500", children: ["Hi\u1EC7n t\u1EA1i: \"", cellValue, "\""] }))] }), _jsxs("div", { children: [_jsxs(Label, { htmlFor: `answer-${letter.id}`, className: "text-sm font-medium text-gray-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { id: `answer-${letter.id}`, type: "text", maxLength: 100, value: letter.correctAnswer, onChange: (e) => updateMissingLetter(letter.id, 'correctAnswer', e.target.value.trim()), onBlur: () => {
|
|
649
|
+
if (!letter.correctAnswer || letter.correctAnswer.trim() === '') {
|
|
650
|
+
setValidationErrors((prev) => ({
|
|
651
|
+
...prev,
|
|
652
|
+
[letter.id]: 'Vui lòng nhập đáp án đúng',
|
|
653
|
+
}));
|
|
654
|
+
}
|
|
655
|
+
else if (letter.correctAnswer.length > 100) {
|
|
656
|
+
setValidationErrors((prev) => ({
|
|
657
|
+
...prev,
|
|
658
|
+
[letter.id]: 'Đáp án không được vượt quá 100 ký tự',
|
|
659
|
+
}));
|
|
660
|
+
}
|
|
661
|
+
}, placeholder: "Nh\u1EADp \u0111\u00E1p \u00E1n \u0111\u00FAng...", className: `mt-1.5 border-gray-200 bg-white transition-all focus:border-blue-300 focus:ring-blue-200 ${validationErrors[letter.id]
|
|
662
|
+
? 'border-red-400 focus:border-red-500 focus:ring-red-200'
|
|
663
|
+
: ''}` }), validationErrors[letter.id] && (_jsx("p", { className: "mt-1.5 text-sm text-red-600", children: validationErrors[letter.id] }))] })] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeMissingLetter(letter.id), className: "h-8 w-8 text-red-400 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-4 w-4" }) })] }) }, letter.id));
|
|
664
|
+
}) })] }))] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: (value) => {
|
|
665
|
+
setExplanation(value);
|
|
666
|
+
onUnsavedChangesChange?.(true);
|
|
667
|
+
} })] }));
|
|
665
668
|
}
|
|
666
669
|
// Export with both new and legacy names for backward compatibility
|
|
667
670
|
export function FillMissingWordsInGridCreator(props) {
|