@tinyweb_dev/oe-exam-sdk 1.0.9 → 1.0.11
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/take/ExamTakingPageContainer.js +6 -0
- package/dist/components/exams/take/components/QuestionRenderer.js +7 -7
- package/dist/components/exams/take/components/question-renderers/MoversCrossOutWordGroupRenderer.js +24 -10
- package/dist/components/exams/take/components/question-renderers/MoversCrosswordPuzzleRenderer.js +1 -1
- package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.js +2 -1
- package/dist/components/exams/take/components/question-renderers/MoversMatchWordToPictureGroupRenderer.d.ts +14 -0
- package/dist/components/exams/take/components/question-renderers/MoversMatchWordToPictureGroupRenderer.js +36 -0
- package/dist/components/exams/take/components/question-renderers/MoversMatchingWithLinesGroupRenderer.js +4 -0
- package/dist/components/exams/take/components/question-renderers/index.d.ts +1 -0
- package/dist/components/exams/take/components/question-renderers/index.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQaGroupRenderer.js +12 -2
- package/dist/components/exams/take/hooks/useSpontaneousQaGroupRendererSetup.js +2 -1
- package/dist/components/exams/take/utils/answer-transformers.js +17 -1
- package/dist/components/exams/take/utils/cross-out-word-group.transformer.js +4 -6
- package/dist/components/exams/take/utils/question-transformers.d.ts +32 -3
- package/dist/components/exams/take/utils/question-transformers.js +51 -2
- package/dist/components/questions/_shared/components/compact/KidChoiceRow.d.ts +12 -0
- package/dist/components/questions/_shared/components/compact/KidChoiceRow.js +33 -0
- package/dist/components/questions/_shared/components/compact/KidInstruction.d.ts +5 -0
- package/dist/components/questions/_shared/components/compact/KidInstruction.js +9 -0
- package/dist/components/questions/_shared/components/compact/KidPrompt.d.ts +5 -0
- package/dist/components/questions/_shared/components/compact/KidPrompt.js +9 -0
- package/dist/components/questions/_shared/components/compact/KidQuestionCard.d.ts +12 -0
- package/dist/components/questions/_shared/components/compact/KidQuestionCard.js +7 -0
- package/dist/components/questions/_shared/components/compact/index.d.ts +4 -0
- package/dist/components/questions/_shared/components/compact/index.js +4 -0
- package/dist/components/questions/_shared/types/crossword-puzzle.type.d.ts +5 -1
- package/dist/components/questions/_shared/types/match-word-to-picture-group.type.d.ts +1 -0
- package/dist/components/questions/_shared/types/match-word-to-picture-group.type.js +14 -0
- package/dist/components/questions/_shared/types/matching-with-lines-group.type.d.ts +2 -2
- package/dist/components/questions/_shared/types/spontaneous-qa-group.type.d.ts +17 -0
- package/dist/components/questions/components/QuestionSearchDropdown.js +1 -0
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +18 -19
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +8 -7
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +5 -4
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +17 -11
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +15 -42
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +15 -36
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +9 -30
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +8 -7
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +50 -32
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupCreator.js +24 -44
- package/dist/components/questions/types/cross-out-word-group/index.d.ts +1 -0
- package/dist/components/questions/types/cross-out-word-group/index.js +1 -0
- package/dist/components/questions/types/cross-out-word-group/map-cross-out-word-group-data.js +9 -9
- package/dist/components/questions/types/cross-out-word-group/tokenize.d.ts +2 -0
- package/dist/components/questions/types/cross-out-word-group/tokenize.js +11 -0
- package/dist/components/questions/types/cross-out-word-group/transform.js +2 -4
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.d.ts +2 -1
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +8 -6
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleCreator.js +68 -11
- package/dist/components/questions/types/crossword-puzzle/transform.js +59 -30
- package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +26 -31
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankAnswerKey.d.ts +6 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankAnswerKey.js +17 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.js +32 -28
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +109 -26
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.js +21 -11
- package/dist/components/questions/types/fill-in-blank-group/blank-utils.d.ts +1 -0
- package/dist/components/questions/types/fill-in-blank-group/blank-utils.js +4 -1
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +32 -31
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +71 -28
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.js +6 -2
- package/dist/components/questions/types/find-words-in-matrix/transform.js +5 -0
- package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +4 -3
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +11 -30
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +7 -6
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +2 -1
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +14 -13
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +4 -3
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +4 -3
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +11 -10
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +4 -3
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +13 -11
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +17 -14
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +16 -12
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureGroupClient.js +14 -14
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +24 -35
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +16 -3
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupImageField.d.ts +2 -1
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupImageField.js +25 -6
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesBoard.js +1 -1
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.js +22 -10
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +60 -18
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.d.ts +2 -1
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.js +24 -5
- package/dist/components/questions/types/matching-with-lines-group/map-matching-with-lines-group-data.js +8 -4
- package/dist/components/questions/types/matching-with-lines-group/transform.js +5 -4
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +8 -26
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +2 -1
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +6 -5
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +2 -1
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +5 -4
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.d.ts +1 -1
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +54 -11
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +11 -7
- package/dist/components/questions/types/spontaneous-qa-group/creator-helpers.js +8 -2
- package/dist/components/questions/types/spontaneous-qa-group/flatten.d.ts +1 -0
- package/dist/components/questions/types/spontaneous-qa-group/flatten.js +4 -2
- package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.js +5 -3
- package/dist/components/questions/types/spontaneous-qa-group/register.js +5 -2
- package/dist/components/questions/types/spontaneous-qa-group/transform.js +6 -2
- package/dist/components/questions/types/true-false/TrueFalseClient.js +5 -54
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +21 -23
- package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +8 -17
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +76 -81
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +17 -16
- package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +31 -36
- package/dist/components/questions/types/word-ordering/WordOrderingClient.js +15 -14
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +9 -8
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +16 -13
- package/dist/components/questions/viewer/CrossOutWordGroupViewer.js +1 -4
- package/dist/components/questions/viewer/FillInBlankGroupViewer.d.ts +6 -2
- package/dist/components/questions/viewer/FillInBlankGroupViewer.js +13 -7
- package/dist/components/questions/viewer/QuestionViewer.js +2 -2
- package/dist/components/questions/viewer/SpontaneousQaGroupViewer.d.ts +7 -1
- package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +14 -2
- package/dist/components/results/renderers/review-question-body-movers.js +7 -3
- package/dist/components/results/review-data.js +20 -1
- package/dist/shared/lib/utils/question-reverse-transform.js +48 -7
- package/dist/shared/types/questions/crossword-puzzle.d.ts +1 -0
- package/dist/shared/types/questions/find-words-in-matrix.d.ts +1 -0
- package/dist/shared/types/questions/matching-with-lines-group.d.ts +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from '../../../../../shared/lib/utils';
|
|
4
|
+
export function KidInstruction({ children, className, }) {
|
|
5
|
+
if (!children) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
return (_jsx("div", { className: cn('rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800', className), children: children }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from '../../../../../shared/lib/utils';
|
|
4
|
+
export function KidPrompt({ children, className, }) {
|
|
5
|
+
if (!children) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
return (_jsx("div", { className: cn('rounded-md border border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 px-3 py-2 text-sm font-semibold text-gray-800', className), children: children }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export declare function KidQuestionCard({ icon, title, points, isExample, review, headerExtra, children, className, contentClassName, }: {
|
|
3
|
+
icon?: ReactNode;
|
|
4
|
+
title: string;
|
|
5
|
+
points?: number;
|
|
6
|
+
isExample?: boolean;
|
|
7
|
+
review?: 'correct' | 'wrong' | null;
|
|
8
|
+
headerExtra?: ReactNode;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
contentClassName?: string;
|
|
12
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Check, CircleHelp, Star, X } from 'lucide-react';
|
|
4
|
+
import { cn } from '../../../../../shared/lib/utils';
|
|
5
|
+
export function KidQuestionCard({ icon, title, points, isExample = false, review, headerExtra, children, className, contentClassName, }) {
|
|
6
|
+
return (_jsxs("div", { className: cn('overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm', className), children: [_jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: icon ?? _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "truncate text-sm font-medium text-gray-700", children: title }), isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "C\u00E2u v\u00ED d\u1EE5"] }))] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [headerExtra, typeof points === 'number' && (_jsxs("span", { className: "text-xs text-gray-500", children: [points, " \u0111i\u1EC3m"] })), review === 'correct' && (_jsxs("div", { className: "flex items-center gap-1 rounded-full bg-green-100 px-2 py-0.5 text-xs font-semibold text-green-700", children: [_jsx(Check, { className: "h-3 w-3" }), "\u0110\u00FAng"] })), review === 'wrong' && (_jsxs("div", { className: "flex items-center gap-1 rounded-full bg-red-100 px-2 py-0.5 text-xs font-semibold text-red-700", children: [_jsx(X, { className: "h-3 w-3" }), "Sai"] }))] })] }), _jsx("div", { className: cn('space-y-3 p-3', contentClassName), children: children })] }));
|
|
7
|
+
}
|
|
@@ -4,3 +4,7 @@ export { CompactPreviewButton } from './CompactPreviewButton';
|
|
|
4
4
|
export { CompactCreatorHeader } from './CompactCreatorHeader';
|
|
5
5
|
export { CompactFieldCard } from './CompactFieldCard';
|
|
6
6
|
export { CompactExplanationToggle } from './CompactExplanationToggle';
|
|
7
|
+
export { KidInstruction } from './KidInstruction';
|
|
8
|
+
export { KidPrompt } from './KidPrompt';
|
|
9
|
+
export { KidQuestionCard } from './KidQuestionCard';
|
|
10
|
+
export { KidChoiceRow, optionLetter } from './KidChoiceRow';
|
|
@@ -4,3 +4,7 @@ export { CompactPreviewButton } from './CompactPreviewButton';
|
|
|
4
4
|
export { CompactCreatorHeader } from './CompactCreatorHeader';
|
|
5
5
|
export { CompactFieldCard } from './CompactFieldCard';
|
|
6
6
|
export { CompactExplanationToggle } from './CompactExplanationToggle';
|
|
7
|
+
export { KidInstruction } from './KidInstruction';
|
|
8
|
+
export { KidPrompt } from './KidPrompt';
|
|
9
|
+
export { KidQuestionCard } from './KidQuestionCard';
|
|
10
|
+
export { KidChoiceRow, optionLetter } from './KidChoiceRow';
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import type { CrosswordClue, CrosswordPuzzleContent, CrosswordPuzzleCorrectAnswer } from '../../../../shared/types/questions/crossword-puzzle';
|
|
1
|
+
import type { CrosswordClue, CrosswordPuzzleContent, CrosswordPuzzleCorrectAnswer, CrosswordSubtype } from '../../../../shared/types/questions/crossword-puzzle';
|
|
2
2
|
export interface CrosswordClueFormItem extends CrosswordClue {
|
|
3
3
|
id: string;
|
|
4
4
|
answer: string;
|
|
5
5
|
direction: 'across' | 'down';
|
|
6
6
|
}
|
|
7
7
|
export interface CrosswordPuzzleFormState {
|
|
8
|
+
type?: CrosswordSubtype;
|
|
8
9
|
title?: string;
|
|
10
|
+
instruction?: string;
|
|
9
11
|
rows: number;
|
|
10
12
|
cols: number;
|
|
13
|
+
markedColumn?: number;
|
|
14
|
+
hiddenWord?: string;
|
|
11
15
|
clues: CrosswordClueFormItem[];
|
|
12
16
|
caseSensitive: boolean;
|
|
13
17
|
points: number;
|
|
@@ -3,6 +3,7 @@ export declare const MATCH_WORD_TO_PICTURE_GROUP_DEFAULT_INSTRUCTION = "Look and
|
|
|
3
3
|
export declare const MATCH_WORD_TO_PICTURE_ITEM_TYPE: "MATCH_WORD_TO_PICTURE";
|
|
4
4
|
export declare function toMatchWordToPictureImageUrls(value?: unknown): string[];
|
|
5
5
|
export declare function fromMatchWordToPictureImageUrls(value?: unknown): string | string[] | undefined;
|
|
6
|
+
export declare function toMatchWordToPictureStorageKey(value: string): string;
|
|
6
7
|
export interface MatchWordToPictureGroupItemData {
|
|
7
8
|
imageUrl?: string | string[];
|
|
8
9
|
correctAnswer: string;
|
|
@@ -19,3 +19,17 @@ export function fromMatchWordToPictureImageUrls(value) {
|
|
|
19
19
|
}
|
|
20
20
|
return urls.length === 1 ? urls[0] : urls;
|
|
21
21
|
}
|
|
22
|
+
export function toMatchWordToPictureStorageKey(value) {
|
|
23
|
+
const trimmed = value.trim();
|
|
24
|
+
if (!trimmed)
|
|
25
|
+
return '';
|
|
26
|
+
if (trimmed.startsWith('http://')
|
|
27
|
+
|| trimmed.startsWith('https://')
|
|
28
|
+
|| trimmed.startsWith('blob:')
|
|
29
|
+
|| trimmed.startsWith('data:')
|
|
30
|
+
|| trimmed.startsWith('/images/')
|
|
31
|
+
|| trimmed.startsWith('/_next/')) {
|
|
32
|
+
return trimmed;
|
|
33
|
+
}
|
|
34
|
+
return trimmed.replace(/^\/+/, '');
|
|
35
|
+
}
|
|
@@ -18,9 +18,9 @@ export interface MatchingWithLinesGroupData {
|
|
|
18
18
|
instruction: string;
|
|
19
19
|
/** Shared reading passage (HTML) above the matching board. */
|
|
20
20
|
passage?: string;
|
|
21
|
-
audioUrl?: string;
|
|
21
|
+
audioUrl?: string | string[];
|
|
22
22
|
/** Shared cover / portrait image (not a left/right match item). */
|
|
23
|
-
imageUrl?: string;
|
|
23
|
+
imageUrl?: string | string[];
|
|
24
24
|
rightItems: MatchingWithLinesRightItem[];
|
|
25
25
|
items: MatchingWithLinesGroupItemData[];
|
|
26
26
|
explanation?: string;
|
|
@@ -6,6 +6,7 @@ export interface SpontaneousQaGroupQuestionData {
|
|
|
6
6
|
expectedAnswers: string[];
|
|
7
7
|
points: number;
|
|
8
8
|
questionNumber: number;
|
|
9
|
+
isExample?: boolean;
|
|
9
10
|
}
|
|
10
11
|
export interface SpontaneousQaGroupPartData {
|
|
11
12
|
partNumber: number;
|
|
@@ -31,7 +32,23 @@ export interface SpontaneousQaGroupCreatorProps {
|
|
|
31
32
|
getFormData?: () => SpontaneousQaGroupData;
|
|
32
33
|
} | null>;
|
|
33
34
|
}
|
|
35
|
+
export interface SpontaneousQaGroupStudentAnswer {
|
|
36
|
+
audioKeys: Array<string | null>;
|
|
37
|
+
}
|
|
38
|
+
export interface SpontaneousQaGroupGradeDetail {
|
|
39
|
+
itemId: string;
|
|
40
|
+
isCorrect?: boolean;
|
|
41
|
+
userAnswer?: unknown;
|
|
42
|
+
correctAnswer?: unknown;
|
|
43
|
+
partialScore?: number;
|
|
44
|
+
feedback?: string;
|
|
45
|
+
}
|
|
34
46
|
export interface SpontaneousQaGroupClientProps {
|
|
35
47
|
questionData: SpontaneousQaGroupData;
|
|
36
48
|
isReviewMode?: boolean;
|
|
49
|
+
userAnswer?: SpontaneousQaGroupStudentAnswer;
|
|
50
|
+
onItemRecorded?: (flattenIndex: number, blob: Blob) => void;
|
|
51
|
+
uploadingIndex?: number | null;
|
|
52
|
+
gradeDetails?: SpontaneousQaGroupGradeDetail[];
|
|
53
|
+
disabled?: boolean;
|
|
37
54
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Card, CardContent } from '../../../../components/ui/card';
|
|
4
3
|
import { Input } from '../../../../components/ui/input';
|
|
5
|
-
import {
|
|
6
|
-
import { CheckCircle2, XCircle, BookOpen } from 'lucide-react';
|
|
4
|
+
import { CheckCircle2, XCircle, PenLine } from 'lucide-react';
|
|
7
5
|
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
8
6
|
import { toAnswerTheQuestionImageUrls, } from '../../_shared/types/answer-the-question.type';
|
|
7
|
+
import { KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
|
|
9
8
|
import { useState } from 'react';
|
|
10
9
|
function isDisplayableMediaUrl(value) {
|
|
11
10
|
return (value.startsWith('http://')
|
|
@@ -33,7 +32,7 @@ export function AnswerTheQuestionClient({ questionData, onSubmit, isReviewMode =
|
|
|
33
32
|
const [answer, setAnswer] = useState(userAnswer || '');
|
|
34
33
|
const imageUrls = toAnswerTheQuestionImageUrls(questionData.imageUrl);
|
|
35
34
|
if (!questionData.question && imageUrls.length === 0) {
|
|
36
|
-
return (_jsx(
|
|
35
|
+
return (_jsx(KidQuestionCard, { icon: _jsx(PenLine, { className: "h-3.5 w-3.5" }), title: "Tr\u1EA3 l\u1EDDi c\u00E2u h\u1ECFi", children: _jsx("p", { className: "text-sm text-gray-500", children: "Kh\u00F4ng c\u00F3 d\u1EEF li\u1EC7u c\u00E2u h\u1ECFi" }) }));
|
|
37
36
|
}
|
|
38
37
|
const isExample = questionData.isExample || false;
|
|
39
38
|
const correctAnswer = questionData.correctAnswer || '';
|
|
@@ -43,19 +42,19 @@ export function AnswerTheQuestionClient({ questionData, onSubmit, isReviewMode =
|
|
|
43
42
|
const isCorrect = isReviewMode && userAnswer !== undefined
|
|
44
43
|
? userAnswer.trim().toLowerCase() === correctAnswer.trim().toLowerCase()
|
|
45
44
|
: undefined;
|
|
46
|
-
return (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
45
|
+
return (_jsxs("div", { className: "space-y-3", children: [(questionData.title || questionData.groupContent) && (_jsxs("div", { className: "rounded-xl border border-indigo-200 bg-gradient-to-r from-indigo-50 to-blue-50 p-4 space-y-2", children: [questionData.title && (_jsx("h3", { className: "text-base font-bold text-indigo-900", children: questionData.title })), questionData.groupContent && (_jsx("p", { className: "text-sm text-indigo-700 leading-relaxed whitespace-pre-wrap", children: questionData.groupContent }))] })), _jsxs(KidQuestionCard, { icon: _jsx(PenLine, { className: "h-3.5 w-3.5" }), title: "Tr\u1EA3 l\u1EDDi c\u00E2u h\u1ECFi", points: questionData.points, isExample: isExample, review: isReviewMode && isCorrect !== undefined ? (isCorrect ? 'correct' : 'wrong') : null, children: [imageUrls.length > 0 && (_jsx("div", { className: "flex flex-wrap justify-center gap-3", children: imageUrls.map((url, imageIndex) => (_jsx("div", { className: "relative max-w-lg overflow-hidden rounded-md border border-gray-200 bg-gray-50", children: _jsx(MediaPreview, { src: url, alt: `Question image ${imageIndex + 1}` }) }, `atq-image-${imageIndex}`))) })), questionData.question ? (_jsx(KidPrompt, { children: questionData.question })) : null, questionData.suggestedAnswers && questionData.suggestedAnswers.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2 mt-2 mb-1", children: questionData.suggestedAnswers.map((suggestion, index) => (_jsx("button", { type: "button", disabled: isReviewMode || isExample, onClick: () => {
|
|
46
|
+
if (!isReviewMode && !isExample) {
|
|
47
|
+
setAnswer(suggestion);
|
|
48
|
+
onSubmit?.(suggestion);
|
|
49
|
+
}
|
|
50
|
+
}, className: `inline-flex items-center rounded-full border px-3 py-1 text-sm font-medium transition-colors ${answer === suggestion
|
|
51
|
+
? 'border-teal-400 bg-teal-50 text-teal-700 ring-1 ring-teal-200'
|
|
52
|
+
: 'border-gray-200 bg-white text-gray-600 hover:border-teal-300 hover:bg-teal-50/50 hover:text-teal-600'} ${isReviewMode || isExample ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'}`, children: suggestion }, index))) })), _jsxs("div", { className: "flex items-center gap-3 mt-1", children: [questionData.prefixText && (_jsx("span", { className: "text-sm font-medium text-gray-700", children: questionData.prefixText })), _jsx(Input, { value: displayedAnswer, onChange: (e) => {
|
|
53
|
+
setAnswer(e.target.value);
|
|
54
|
+
onSubmit?.(e.target.value);
|
|
55
|
+
}, placeholder: "Vi\u1EBFt \u0111\u00E1p \u00E1n...", disabled: isReviewMode || isExample, className: `max-w-xs border text-sm px-3 py-1.5 rounded-md font-medium ${isReviewMode
|
|
56
|
+
? isCorrect
|
|
57
|
+
? 'border-green-400 bg-green-50 text-green-800'
|
|
58
|
+
: 'border-red-400 bg-red-50 text-red-800'
|
|
59
|
+
: 'border-teal-300 focus:border-teal-500 focus:ring-teal-200'}` }), questionData.suffixText && (_jsx("span", { className: "text-sm font-medium text-gray-700", children: questionData.suffixText })), isReviewMode && isCorrect !== undefined && (_jsx("span", { className: "flex items-center gap-1", children: isCorrect ? (_jsx(CheckCircle2, { className: "h-5 w-5 text-green-500" })) : (_jsx(XCircle, { className: "h-5 w-5 text-red-500" })) }))] }), isReviewMode && isCorrect === false && (_jsxs("div", { className: "mt-3 flex items-center gap-2 rounded-lg bg-green-50 border border-green-200 px-4 py-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: correctAnswer })] })] })), autoFillCorrectAnswers && (_jsxs("div", { className: "mt-3 flex items-center gap-2 rounded-lg bg-teal-50 border border-teal-200 px-4 py-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-teal-600" }), _jsxs("span", { className: "text-sm text-teal-700", children: ["\u0110\u00E1p \u00E1n: ", _jsx("strong", { children: correctAnswer })] })] })), explanation && isReviewMode && (_jsxs("div", { className: "rounded-md border border-blue-200 bg-blue-50 px-3 py-2", children: [_jsx("p", { className: "text-sm font-semibold text-blue-800 mb-1", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "text-sm text-blue-700", children: explanation })] }))] })] }));
|
|
61
60
|
}
|
package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { BookOpen, CircleHelp, Lightbulb, Sparkles,
|
|
3
|
+
import { BookOpen, CircleHelp, Lightbulb, Sparkles, Volume2 } from 'lucide-react';
|
|
4
4
|
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
5
5
|
import { toAnswerTheQuestionGroupImageUrls } from '../../_shared/types/answer-the-question-group.type';
|
|
6
|
+
import { KidInstruction, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
|
|
6
7
|
function isAbsoluteMediaUrl(value) {
|
|
7
8
|
return (value.startsWith('http://')
|
|
8
9
|
|| value.startsWith('https://')
|
|
@@ -41,15 +42,15 @@ function MediaPreview({ src, kind = 'image', alt }) {
|
|
|
41
42
|
}
|
|
42
43
|
export function AnswerTheQuestionGroupClient({ questionData, isReviewMode = false, userAnswers = [], onAnswerChange, }) {
|
|
43
44
|
const items = questionData.items || [];
|
|
44
|
-
return (_jsxs("div", { className: "space-y-4", children: [
|
|
45
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(KidInstruction, { children: questionData.instruction }), questionData.audioUrl && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-slate-200 bg-white px-3 py-2", children: [_jsx(Volume2, { className: "h-4 w-4 text-slate-500" }), _jsx(MediaPreview, { src: questionData.audioUrl, kind: "audio", alt: "Group audio" })] })), toAnswerTheQuestionGroupImageUrls(questionData.imageUrl).length > 0 && (_jsx("div", { className: "flex flex-wrap justify-center gap-3", children: toAnswerTheQuestionGroupImageUrls(questionData.imageUrl).map((url, imageIndex) => (_jsx(MediaPreview, { src: url, alt: `Hình ảnh (${imageIndex + 1})` }, `group-image-${imageIndex}`))) })), questionData.passage && (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white p-5 shadow-xs", children: [_jsxs("div", { className: "mb-2.5 flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-indigo-700", children: [_jsx(BookOpen, { className: "h-4 w-4" }), _jsx("span", { children: "B\u00E0i \u0111\u1ECDc (Reading Passage)" })] }), _jsx("div", { className: "prose prose-sm max-w-none leading-relaxed text-gray-800", dangerouslySetInnerHTML: { __html: questionData.passage } })] })), items.map((item, itemIndex) => {
|
|
45
46
|
const userValue = userAnswers[itemIndex] || '';
|
|
46
47
|
const expectedAnswers = Array.isArray(item.expectedAnswers)
|
|
47
48
|
? item.expectedAnswers.filter((answer) => answer.trim() !== '')
|
|
48
49
|
: [];
|
|
49
|
-
return (_jsxs(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
return (_jsxs(KidQuestionCard, { icon: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }), title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, headerExtra: _jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-violet-100 px-2 py-0.5 text-xs font-semibold text-violet-700", children: [_jsx(Sparkles, { className: "h-3 w-3" }), "AI"] }), children: [toAnswerTheQuestionGroupImageUrls(item.imageUrl).length > 0 && (_jsx("div", { className: "flex flex-wrap justify-center gap-3", children: toAnswerTheQuestionGroupImageUrls(item.imageUrl).map((url, imageIndex) => (_jsx(MediaPreview, { src: url, alt: `Hình ảnh câu ${item.questionNumber || itemIndex + 1} (${imageIndex + 1})` }, `${item.questionNumber}-${imageIndex}`))) })), _jsx(KidPrompt, { children: item.question || 'Câu hỏi chưa được nhập' }), isReviewMode || item.isExample || !onAnswerChange ? (userValue ? (_jsx("div", { className: "rounded-md border border-gray-200 bg-gray-50 px-3 py-1.5 text-sm text-gray-700", children: userValue })) : null) : (_jsx("textarea", { value: userValue, onChange: (event) => {
|
|
51
|
+
const next = items.map((_, index) => userAnswers[index] || '');
|
|
52
|
+
next[itemIndex] = event.target.value;
|
|
53
|
+
onAnswerChange(next);
|
|
54
|
+
}, placeholder: "Nh\u1EADp c\u00E2u tr\u1EA3 l\u1EDDi...", rows: 2, className: "w-full rounded-md border border-slate-200 px-3 py-1.5 text-sm text-slate-800 outline-none ring-blue-200 focus:border-blue-400 focus:ring-2" })), isReviewMode && expectedAnswers.length > 0 ? (_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-xs font-medium text-gray-500", children: "\u0110\u00E1p \u00E1n" }), _jsx("ul", { className: "list-disc space-y-1 pl-5 text-sm text-gray-700", children: expectedAnswers.map((answer) => (_jsx("li", { children: answer }, answer))) })] })) : isReviewMode && !userValue ? (_jsx("div", { className: "rounded-md border border-gray-200 bg-gray-50 px-3 py-1.5 text-sm text-gray-500", children: "Ch\u01B0a c\u00F3 \u0111\u00E1p \u00E1n" })) : null] }, `${item.questionNumber}-${itemIndex}`));
|
|
54
55
|
}), 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] }))] }));
|
|
55
56
|
}
|
package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js
CHANGED
|
@@ -10,7 +10,7 @@ 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 { MessageSquare, Plus, Star, Trash2 } from 'lucide-react';
|
|
13
|
+
import { BookOpen, MessageSquare, Plus, Star, Trash2 } from 'lucide-react';
|
|
14
14
|
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
15
15
|
import { AnswerTheQuestionGroupClient } from './AnswerTheQuestionGroupClient';
|
|
16
16
|
import { toAnswerTheQuestionGroupImageUrls } from '../../_shared/types/answer-the-question-group.type';
|
|
@@ -57,6 +57,7 @@ export function AnswerTheQuestionGroupCreator({ initialData, onChange, externalE
|
|
|
57
57
|
const [items, setItems] = useState(() => normalizeItems(initialData?.items));
|
|
58
58
|
const [explanation, setExplanation] = useState(initialData?.explanation || '');
|
|
59
59
|
const [isClientMode, setIsClientMode] = useState(false);
|
|
60
|
+
const [showPassage, setShowPassage] = useState(false);
|
|
60
61
|
const [errors, setErrors] = useState([]);
|
|
61
62
|
const onChangeRef = useRef(onChange);
|
|
62
63
|
const previousPayloadRef = useRef('');
|
|
@@ -103,7 +104,7 @@ export function AnswerTheQuestionGroupCreator({ initialData, onChange, externalE
|
|
|
103
104
|
.map((answer) => answer.trim())
|
|
104
105
|
.filter((answer) => answer !== '');
|
|
105
106
|
if (!item.isExample && filledAnswers.length === 0) {
|
|
106
|
-
nextErrors.push(`Câu ${index + 1}: cần ít nhất một đáp án
|
|
107
|
+
nextErrors.push(`Câu ${index + 1}: cần ít nhất một đáp án.`);
|
|
107
108
|
}
|
|
108
109
|
});
|
|
109
110
|
return nextErrors;
|
|
@@ -185,8 +186,8 @@ export function AnswerTheQuestionGroupCreator({ initialData, onChange, externalE
|
|
|
185
186
|
if (isClientMode) {
|
|
186
187
|
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(AnswerTheQuestionGroupClient, { questionData: buildPayload(), isReviewMode: true })] }));
|
|
187
188
|
}
|
|
188
|
-
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: displayErrors.map((error) => (_jsx("p", { children: error }, error))) })), _jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(MessageSquare, { className: "h-3.5 w-3.5" }), title: "\u0110\u1ECDc v\u00E0 tr\u1EA3 l\u1EDDi c\u00E2u h\u1ECFi (AI)", preview: { onClick: () => setIsClientMode(true) } }), children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "group-instruction", className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(Input, { id: "group-instruction", value: instruction, onChange: (event) => setInstruction(event.target.value), placeholder: 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: "rounded-md border border-gray-200 bg-gray-50 px-3 py-2.5", children: [_jsx(PointsInput, { value: sumPoints(items), allowZero: true, readOnly: true }), _jsx("p", { className: "mt-1.5 text-xs text-gray-500", children: "T\u1ED5ng \u0111i\u1EC3m \u0111\u01B0\u1EE3c c\u1ED9ng t\u1EEB c\u00E1c c\u00E2u kh\u00F4ng ph\u1EA3i example." })] })] }), _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 h\u1ECFi (", 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 h\u1ECFi"] })] }) }), _jsxs(CardContent, { className: "space-y-2.5 px-3 pb-3", children: [items.map((item, itemIndex) => (_jsxs("div", { className: "space-y-2.5 rounded-md border border-gray-200 bg-white px-3 py-2.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", itemIndex + 1] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { checked: item.isExample === true, onCheckedChange: (checked) => updateItem(itemIndex, {
|
|
189
|
+
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: displayErrors.map((error) => (_jsx("p", { children: error }, error))) })), _jsxs(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(MessageSquare, { className: "h-3.5 w-3.5" }), title: "\u0110\u1ECDc v\u00E0 tr\u1EA3 l\u1EDDi c\u00E2u h\u1ECFi (AI)", preview: { onClick: () => setIsClientMode(true) } }), children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "group-instruction", className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(Input, { id: "group-instruction", value: instruction, onChange: (event) => setInstruction(event.target.value), placeholder: DEFAULT_INSTRUCTION, className: "h-10 border-gray-200 bg-white" })] }), showPassage ? (_jsxs("div", { className: "relative space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx(Label, { className: "text-sm font-semibold text-gray-800", children: "N\u1ED9i dung b\u00E0i \u0111\u1ECDc (Passage)" }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setShowPassage(false), className: "h-7 px-2 text-xs text-gray-500 hover:text-gray-700", children: "\u1EA8n" })] }), _jsx(RichTextEditor, { value: passage, onChange: setPassage, minHeightClassName: "min-h-[160px]" })] })) : (_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setShowPassage(true), className: "h-8 gap-1.5 border-indigo-200 px-2.5 text-xs text-indigo-700 hover:bg-indigo-50 hover:text-indigo-800", children: [_jsx(BookOpen, { className: "h-3.5 w-3.5" }), isContentEmpty(passage) ? 'Thêm bài đọc' : 'Hiện bài đọc'] })), _jsxs("div", { className: "rounded-md border border-gray-200 bg-gray-50 px-3 py-2.5", children: [_jsx(PointsInput, { value: sumPoints(items), allowZero: true, readOnly: true }), _jsx("p", { className: "mt-1.5 text-xs text-gray-500", children: "T\u1ED5ng \u0111i\u1EC3m \u0111\u01B0\u1EE3c c\u1ED9ng t\u1EEB c\u00E1c c\u00E2u kh\u00F4ng ph\u1EA3i example." })] })] }), _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 h\u1ECFi (", 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 h\u1ECFi"] })] }) }), _jsxs(CardContent, { className: "space-y-2.5 px-3 pb-3", children: [items.map((item, itemIndex) => (_jsxs("div", { className: "space-y-2.5 rounded-md border border-gray-200 bg-white px-3 py-2.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", itemIndex + 1] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { checked: item.isExample === true, onCheckedChange: (checked) => updateItem(itemIndex, {
|
|
189
190
|
isExample: checked,
|
|
190
191
|
points: checked ? 0 : (item.points || 1),
|
|
191
|
-
}) }), _jsxs(Label, { className: "flex cursor-pointer items-center gap-1 text-xs", children: [_jsx(Star, { className: "h-3 w-3 text-amber-500" }), "C\u00E2u v\u00ED d\u1EE5"] })] }), !item.isExample && (_jsx(PointsInput, { value: item.points, allowZero: true, onChange: (e) => updateItem(itemIndex, { points: Number(e.target.value) || 0 }) })), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeItem(itemIndex), disabled: items.length <= 1, className: "h-7 w-7 text-red-500 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "C\u00E2u h\u1ECFi" }), _jsx(Textarea, { value: item.question, onChange: (event) => updateItem(itemIndex, { question: event.target.value }), rows: 2, placeholder: "What is autism?", className: "min-h-[56px] border-gray-200 bg-white" })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "\u0110\u00E1p \u00E1n
|
|
192
|
+
}) }), _jsxs(Label, { className: "flex cursor-pointer items-center gap-1 text-xs", children: [_jsx(Star, { className: "h-3 w-3 text-amber-500" }), "C\u00E2u v\u00ED d\u1EE5"] })] }), !item.isExample && (_jsx(PointsInput, { value: item.points, allowZero: true, onChange: (e) => updateItem(itemIndex, { points: Number(e.target.value) || 0 }) })), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeItem(itemIndex), disabled: items.length <= 1, className: "h-7 w-7 text-red-500 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "C\u00E2u h\u1ECFi" }), _jsx(Textarea, { value: item.question, onChange: (event) => updateItem(itemIndex, { question: event.target.value }), rows: 2, placeholder: "What is autism?", className: "min-h-[56px] border-gray-200 bg-white" })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "\u0110\u00E1p \u00E1n" }), item.expectedAnswers.map((answer, answerIndex) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { value: answer, onChange: (event) => updateExpectedAnswer(itemIndex, answerIndex, event.target.value), placeholder: `Đáp án ${answerIndex + 1}`, className: "h-10 border-gray-200 bg-white" }), item.expectedAnswers.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeExpectedAnswer(itemIndex, answerIndex), className: "h-7 w-7 text-red-500 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }, `expected-${itemIndex}-${answerIndex}`))), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => addExpectedAnswer(itemIndex), className: "h-8 gap-1.5 px-2.5 text-xs", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm \u0111\u00E1p \u00E1n"] })] })] }, `item-${itemIndex}`))), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) })] })] })] }));
|
|
192
193
|
}
|
package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect, useRef } from 'react';
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
5
4
|
import { Check, X } from 'lucide-react';
|
|
5
|
+
import { KidQuestionCard } from '../../_shared/components/compact';
|
|
6
6
|
// Fisher-Yates shuffle algorithm
|
|
7
7
|
function shuffleArray(array) {
|
|
8
8
|
const shuffled = [...array];
|
|
@@ -128,14 +128,20 @@ export function ArrangeLettersIntoWordsClient({ questionData, onSubmit, isReview
|
|
|
128
128
|
const available = availableLetters[subQuestion.id] || [];
|
|
129
129
|
const arranged = arrangedLetters[subQuestion.id] || [];
|
|
130
130
|
const imagePreviewUrl = getImagePreviewUrl(subQuestion);
|
|
131
|
-
return (_jsxs(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
return (_jsxs(KidQuestionCard, { title: `Câu ${sqIndex + 1}`, review: isReviewMode
|
|
132
|
+
? isCorrect
|
|
133
|
+
? 'correct'
|
|
134
|
+
: userAnswerValue.trim() !== ''
|
|
135
|
+
? 'wrong'
|
|
136
|
+
: null
|
|
137
|
+
: null, children: [imagePreviewUrl && (_jsx("div", { className: "flex justify-center", children: _jsx("div", { className: "relative h-48 w-full max-w-md overflow-hidden rounded-lg", children: _jsx("img", { src: imagePreviewUrl, alt: `Hint for question ${sqIndex + 1}`, className: "h-full w-full object-contain", onError: () => {
|
|
138
|
+
console.error('Failed to load image:', subQuestion.imageUrl);
|
|
139
|
+
} }) }) })), _jsxs("div", { children: [_jsx("p", { className: "mb-2 text-sm font-medium text-gray-700", children: "Ch\u1ECDn c\u00E1c ch\u1EEF c\u00E1i:" }), _jsx("div", { className: "flex flex-wrap gap-2", children: available.map((letter, index) => (_jsx("button", { onClick: () => handleLetterClick(subQuestion.id, index), disabled: isReviewMode, className: "flex h-12 w-12 items-center justify-center rounded-full border-2 border-blue-500 bg-white text-xl font-bold text-blue-600 transition-all hover:bg-blue-50 hover:shadow-md disabled:cursor-not-allowed disabled:opacity-50", children: letter }, `${letter}-${index}`))) })] }), _jsxs("div", { children: [_jsx("p", { className: "mb-2 text-sm font-medium text-gray-700", children: "S\u1EAFp x\u1EBFp th\u00E0nh t\u1EEB:" }), _jsx("div", { className: `min-h-[60px] rounded-lg border-2 p-4 ${isReviewMode
|
|
140
|
+
? isCorrect
|
|
141
|
+
? 'border-green-500 bg-green-50'
|
|
142
|
+
: userAnswerValue.trim() !== ''
|
|
143
|
+
? 'border-red-500 bg-red-50'
|
|
144
|
+
: 'border-gray-300 bg-gray-50'
|
|
145
|
+
: 'border-gray-300 bg-gray-50'}`, children: _jsx("div", { className: "flex flex-wrap items-center gap-2", children: arranged.length === 0 ? (_jsx("span", { className: "text-gray-400", children: "G\u1EA1ch ngang \u0111\u1EC3 s\u1EAFp x\u1EBFp..." })) : (arranged.map((letter, index) => (_jsx("button", { onClick: () => handleArrangedLetterClick(subQuestion.id, index), disabled: isReviewMode, className: "flex h-12 w-12 items-center justify-center rounded-full border-2 border-gray-400 bg-white text-xl font-bold text-gray-700 transition-all hover:bg-gray-100 hover:shadow-md disabled:cursor-not-allowed disabled:opacity-50", children: letter }, `arranged-${letter}-${index}`)))) }) })] }), isReviewMode && (_jsx("div", { className: "flex items-center gap-2", children: isCorrect ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-5 w-5 text-green-600" }), _jsx("span", { className: "text-sm text-green-600", children: "\u0110\u00FAng!" })] })) : userAnswerValue.trim() !== '' ? (_jsxs(_Fragment, { children: [_jsx(X, { className: "h-5 w-5 text-red-600" }), _jsxs("span", { className: "text-sm text-red-600", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: subQuestion.correctWord })] })] })) : null }))] }, subQuestion.id));
|
|
140
146
|
}) }));
|
|
141
147
|
}
|
package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { cn } from '../../../../shared/lib/utils';
|
|
5
|
-
import { Check, X,
|
|
5
|
+
import { Check, X, Lightbulb } from 'lucide-react';
|
|
6
|
+
import { KidChoiceRow, KidQuestionCard, optionLetter } from '../../_shared/components/compact';
|
|
6
7
|
export function ChooseCorrectAdjectiveClient({ questionData, onSubmit, isReviewMode = false, userAnswer, }) {
|
|
7
8
|
const [selectedAnswer, setSelectedAnswer] = useState(userAnswer);
|
|
8
9
|
const handleAnswerChange = (index) => {
|
|
@@ -30,45 +31,17 @@ export function ChooseCorrectAdjectiveClient({ questionData, onSubmit, isReviewM
|
|
|
30
31
|
};
|
|
31
32
|
// Get the correct answer text
|
|
32
33
|
const correctAnswerText = getOptionText(questionData.options[questionData.correctAnswer]);
|
|
33
|
-
return (_jsx("div", { className: "space-y-3", children: _jsxs(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
? 'border-green-400 bg-green-50'
|
|
47
|
-
: selected
|
|
48
|
-
? 'border-red-400 bg-red-50'
|
|
49
|
-
: 'border-gray-200 bg-gray-50'
|
|
50
|
-
: selected
|
|
51
|
-
? 'border-indigo-500 bg-indigo-50 shadow-sm ring-2 ring-indigo-100'
|
|
52
|
-
: 'border-gray-200 bg-white hover:border-gray-300 hover:bg-gray-50 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg text-sm font-bold transition-all', isReviewMode
|
|
53
|
-
? correct
|
|
54
|
-
? 'bg-green-500 text-white'
|
|
55
|
-
: selected
|
|
56
|
-
? 'bg-red-500 text-white'
|
|
57
|
-
: 'bg-gray-200 text-gray-600'
|
|
58
|
-
: selected
|
|
59
|
-
? 'bg-gradient-to-br from-indigo-500 to-purple-600 text-white shadow-sm'
|
|
60
|
-
: 'bg-gray-100 text-gray-600 group-hover:bg-gray-200'), children: letter }), _jsx("span", { className: cn('flex-1 text-sm font-semibold', isReviewMode && correct
|
|
61
|
-
? 'text-green-800'
|
|
62
|
-
: isReviewMode && selected && !correct
|
|
63
|
-
? 'text-red-800'
|
|
64
|
-
: 'text-gray-700'), children: optText }), isReviewMode && (correct || selected) && (_jsx("div", { className: cn('flex h-5 w-5 items-center justify-center rounded-full', correct ? 'bg-green-500' : 'bg-red-500'), children: correct
|
|
65
|
-
? _jsx(Check, { className: "h-3 w-3 text-white" })
|
|
66
|
-
: _jsx(X, { className: "h-3 w-3 text-white" }) }))] }, index));
|
|
67
|
-
}) })] }), isReviewMode && (_jsxs("div", { className: "space-y-4 pt-2", children: [_jsxs("div", { className: "rounded-xl border border-green-200 bg-gradient-to-r from-green-50 to-emerald-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-lg bg-green-200", children: _jsx(Check, { className: "h-3.5 w-3.5 text-green-700" }) }), _jsx("span", { className: "text-sm font-medium text-green-800", children: "C\u00E2u ho\u00E0n ch\u1EC9nh:" })] }), _jsx("p", { className: "text-base font-medium text-green-900 pl-8", children: questionData.sentence.replace('{0}', correctAnswerText) })] }), _jsx("div", { className: cn('rounded-xl p-4', userAnswerCorrect
|
|
68
|
-
? 'bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200'
|
|
69
|
-
: 'bg-gradient-to-r from-red-50 to-rose-50 border border-red-200'), children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: cn('flex h-10 w-10 items-center justify-center rounded-xl text-white shadow-sm', userAnswerCorrect
|
|
70
|
-
? 'bg-gradient-to-br from-green-500 to-emerald-600'
|
|
71
|
-
: 'bg-gradient-to-br from-red-500 to-rose-600'), children: userAnswerCorrect ? _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', userAnswerCorrect ? 'text-green-800' : 'text-red-800'), children: userAnswerCorrect ? 'Chính xác!' : 'Chưa đúng' }), _jsx("p", { className: cn('text-xs', userAnswerCorrect ? 'text-green-600' : 'text-red-600'), children: userAnswerCorrect
|
|
72
|
-
? 'Bạn đã chọn đúng tính từ.'
|
|
73
|
-
: `Đáp án đúng là "${correctAnswerText}".` })] })] }) }), questionData.explanation && (_jsx("div", { className: "rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-4", children: _jsxs("div", { className: "flex items-start 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-blue-500 to-indigo-600 text-white shadow-sm", children: _jsx(Lightbulb, { className: "h-4 w-4" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-semibold text-blue-800", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "text-sm text-blue-700 mt-1", 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: "Ch\u1ECDn t\u00EDnh t\u1EEB ph\u00F9 h\u1EE3p" }), _jsx("p", { className: "text-xs text-amber-600", children: "Nh\u1EA5n v\u00E0o m\u1ED9t trong c\u00E1c l\u1EF1a ch\u1ECDn b\u00EAn tr\u00EAn." })] })] }) }))] })] }) }));
|
|
34
|
+
return (_jsx("div", { className: "space-y-3", children: _jsxs(KidQuestionCard, { title: "Ch\u1ECDn t\u00EDnh t\u1EEB \u0111\u00FAng", review: isReviewMode && hasAnswered ? (userAnswerCorrect ? 'correct' : 'wrong') : null, children: [_jsxs("div", { className: "rounded-xl bg-gradient-to-br from-slate-50 to-gray-50 border border-gray-100 p-5", children: [_jsx("div", { className: "text-lg leading-relaxed flex flex-wrap items-center gap-1", children: questionData.sentence.split('{0}').map((part, index, arr) => (_jsxs("span", { children: [_jsx("span", { className: "text-gray-800", children: part }), index < arr.length - 1 && (_jsx("span", { className: "inline-flex items-center mx-1", children: _jsx("span", { className: cn('inline-block min-w-[100px] px-3 py-1 text-center font-bold rounded-lg border-2 border-dashed', hasAnswered
|
|
35
|
+
? isReviewMode
|
|
36
|
+
? userAnswerCorrect
|
|
37
|
+
? 'border-green-400 bg-green-100 text-green-800'
|
|
38
|
+
: 'border-red-400 bg-red-100 text-red-800'
|
|
39
|
+
: 'border-indigo-400 bg-indigo-100 text-indigo-800'
|
|
40
|
+
: 'border-gray-300 bg-gray-100 text-gray-400'), children: hasAnswered ? getOptionText(questionData.options[selectedAnswer]) : '_______' }) }))] }, index))) }), _jsxs("div", { className: "mt-3 flex items-center gap-2", children: [_jsx("span", { className: "text-sm text-gray-500", children: "L\u1EF1a ch\u1ECDn:" }), _jsxs("span", { className: "text-sm font-medium text-gray-600", children: ["(", questionData.options.map(getOptionText).join(' / '), ")"] })] })] }), _jsx("div", { className: "space-y-1.5", children: questionData.options.map((option, index) => (_jsx(KidChoiceRow, { letter: optionLetter(index), selected: isSelected(index), isReviewMode: isReviewMode, isCorrect: isReviewMode && isCorrect(index), onClick: () => handleAnswerChange(index), children: getOptionText(option) }, index))) }), isReviewMode && (_jsxs("div", { className: "space-y-4 pt-2", children: [_jsxs("div", { className: "rounded-xl border border-green-200 bg-gradient-to-r from-green-50 to-emerald-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-lg bg-green-200", children: _jsx(Check, { className: "h-3.5 w-3.5 text-green-700" }) }), _jsx("span", { className: "text-sm font-medium text-green-800", children: "C\u00E2u ho\u00E0n ch\u1EC9nh:" })] }), _jsx("p", { className: "text-base font-medium text-green-900 pl-8", children: questionData.sentence.replace('{0}', correctAnswerText) })] }), _jsx("div", { className: cn('rounded-xl p-4', userAnswerCorrect
|
|
41
|
+
? 'bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200'
|
|
42
|
+
: 'bg-gradient-to-r from-red-50 to-rose-50 border border-red-200'), children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: cn('flex h-10 w-10 items-center justify-center rounded-xl text-white shadow-sm', userAnswerCorrect
|
|
43
|
+
? 'bg-gradient-to-br from-green-500 to-emerald-600'
|
|
44
|
+
: 'bg-gradient-to-br from-red-500 to-rose-600'), children: userAnswerCorrect ? _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', userAnswerCorrect ? 'text-green-800' : 'text-red-800'), children: userAnswerCorrect ? 'Chính xác!' : 'Chưa đúng' }), _jsx("p", { className: cn('text-xs', userAnswerCorrect ? 'text-green-600' : 'text-red-600'), children: userAnswerCorrect
|
|
45
|
+
? 'Bạn đã chọn đúng tính từ.'
|
|
46
|
+
: `Đáp án đúng là "${correctAnswerText}".` })] })] }) }), questionData.explanation && (_jsx("div", { className: "rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-4", children: _jsxs("div", { className: "flex items-start 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-blue-500 to-indigo-600 text-white shadow-sm", children: _jsx(Lightbulb, { className: "h-4 w-4" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-semibold text-blue-800", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "text-sm text-blue-700 mt-1", 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: "Ch\u1ECDn t\u00EDnh t\u1EEB ph\u00F9 h\u1EE3p" }), _jsx("p", { className: "text-xs text-amber-600", children: "Nh\u1EA5n v\u00E0o m\u1ED9t trong c\u00E1c l\u1EF1a ch\u1ECDn b\u00EAn tr\u00EAn." })] })] }) }))] }) }));
|
|
74
47
|
}
|