@tinyweb_dev/oe-exam-sdk 0.2.3 → 0.2.4

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.
@@ -17,8 +17,22 @@ function GuideImageDisplay({ imageKey }) {
17
17
  } }) }));
18
18
  }
19
19
  export function WriteAShortLetterClient({ questionData, onSubmit, isReviewMode = false, userAnswer, explanation, }) {
20
- const { informations, minWords, maxWords, instructions, guideImages } = questionData;
21
- const instruction = instructions?.mainInstruction || '';
20
+ const { minWords = 20, maxWords } = questionData;
21
+ const instruction = questionData.instructions?.mainInstruction ||
22
+ (typeof questionData.instructions === 'string' ? questionData.instructions : '') ||
23
+ questionData.instruction ||
24
+ questionData.prompt ||
25
+ '';
26
+ const informations = Array.isArray(questionData.informations)
27
+ ? questionData.informations
28
+ : Array.isArray(questionData.requirements)
29
+ ? questionData.requirements
30
+ : [];
31
+ const guideImages = Array.isArray(questionData.guideImages)
32
+ ? questionData.guideImages
33
+ : Array.isArray(questionData.images)
34
+ ? questionData.images
35
+ : [];
22
36
  // Initialize answer state - ensure it's always a string
23
37
  const [answer, setAnswer] = useState(typeof userAnswer === 'string' ? userAnswer : '');
24
38
  // Sync userAnswer when it changes (e.g., when loading from server in review mode)
@@ -0,0 +1,8 @@
1
+ import type { Question } from '../../../shared/types/entities/question.types';
2
+ type AnswerTheQuestionGroupViewerProps = {
3
+ question: Question;
4
+ isReviewMode?: boolean;
5
+ userAnswer?: string[];
6
+ };
7
+ export declare function AnswerTheQuestionGroupViewer({ question, isReviewMode, userAnswer, }: AnswerTheQuestionGroupViewerProps): import("react").JSX.Element;
8
+ export {};
@@ -0,0 +1,11 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { AnswerTheQuestionGroupClient } from '../types/answer-the-question-group/AnswerTheQuestionGroupClient';
4
+ import { mapQuestionToAnswerTheQuestionGroupData } from '../types/answer-the-question-group/map-answer-the-question-group-data';
5
+ export function AnswerTheQuestionGroupViewer({ question, isReviewMode = false, userAnswer, }) {
6
+ const questionData = mapQuestionToAnswerTheQuestionGroupData(question);
7
+ const reviewAnswers = isReviewMode && !userAnswer
8
+ ? questionData.items.map((item) => item.expectedAnswers[0] || '')
9
+ : userAnswer;
10
+ return (_jsx(AnswerTheQuestionGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers }));
11
+ }
@@ -20,6 +20,7 @@ import { LookPictureFillBlankChooseAnswerClient } from '../types/look-picture-fi
20
20
  import { FillMissingWordsInGridClient } from '../types/fill-missing-words-in-grid/FillMissingWordsInGridClient';
21
21
  import { WordFillStructuredFormClient } from '../types/word-fill-structured-form/WordFillStructuredFormClient';
22
22
  import { CrosswordPuzzleClient } from '../types/crossword-puzzle/CrosswordPuzzleClient';
23
+ import { FindWordsInMatrixClient } from '../types/find-words-in-matrix/FindWordsInMatrixClient';
23
24
  import { parseFillBlankStemLine } from '../_shared/utils/parseFillBlankStem';
24
25
  import { WordFillStructuredFormViewer } from './WordFillStructuredFormViewer';
25
26
  import { ChooseTheCorrectAnswerGroupViewer } from './ChooseTheCorrectAnswerGroupViewer';
@@ -27,6 +28,7 @@ import { TrueFalseGroupViewer } from './TrueFalseGroupViewer';
27
28
  import { CrossOutWordGroupViewer } from './CrossOutWordGroupViewer';
28
29
  import { SpontaneousQaGroupViewer } from './SpontaneousQaGroupViewer';
29
30
  import { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
31
+ import { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
30
32
  export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false, userAnswer, }) {
31
33
  const [selectedAnswer, setSelectedAnswer] = useState(userAnswer || null);
32
34
  const [playCount, setPlayCount] = useState(0);
@@ -314,13 +316,41 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
314
316
  }, isReviewMode: isReviewMode, userAnswer: userAnswer }));
315
317
  };
316
318
  const renderWriteAShortLetter = () => {
317
- const answer = question.answer;
319
+ const content = (question.content || {});
320
+ const answer = (question.answer || {});
321
+ const mainInstruction = content?.instructions?.mainInstruction ||
322
+ content?.instruction ||
323
+ content?.prompt ||
324
+ answer?.instructions?.mainInstruction ||
325
+ answer?.instruction ||
326
+ (typeof content?.instructions === 'string' ? content.instructions : '') ||
327
+ (typeof content === 'string' ? content : '') ||
328
+ '';
329
+ const informations = (Array.isArray(content?.informations)
330
+ ? content.informations
331
+ : Array.isArray(content?.requirements)
332
+ ? content.requirements
333
+ : Array.isArray(answer?.informations)
334
+ ? answer.informations
335
+ : []).map((info) => typeof info === 'string' ? info : (info.text || info.content || ''));
336
+ const guideImages = Array.isArray(content?.guideImages)
337
+ ? content.guideImages
338
+ : Array.isArray(answer?.guideImages)
339
+ ? answer.guideImages
340
+ : [];
341
+ const contexts = content?.contexts || answer?.contexts || [];
342
+ const minWords = content?.minWords ?? answer?.minWords ?? 20;
343
+ const maxWords = content?.maxWords ?? answer?.maxWords ?? null;
318
344
  const questionData = {
319
- contexts: answer?.contexts || [],
320
- informations: answer?.informations || [],
321
- minWords: answer?.minWords || 20,
322
- maxWords: answer?.maxWords,
323
- instruction: answer?.instruction || '',
345
+ contexts,
346
+ informations,
347
+ guideImages,
348
+ minWords,
349
+ maxWords,
350
+ instructions: {
351
+ mainInstruction,
352
+ },
353
+ instruction: mainInstruction,
324
354
  };
325
355
  return (_jsx(WriteAShortLetterClient, { questionData: questionData, onSubmit: (answerText) => {
326
356
  setSelectedAnswer(answerText);
@@ -600,6 +630,8 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
600
630
  return (_jsx(TrueFalseGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
601
631
  case 'CROSS_OUT_WORD_GROUP':
602
632
  return (_jsx(CrossOutWordGroupViewer, { question: question, isReviewMode: isReviewMode }));
633
+ case 'ANSWER_THE_QUESTION_GROUP':
634
+ return (_jsx(AnswerTheQuestionGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
603
635
  case 'SPONTANEOUS_QA_GROUP':
604
636
  return (_jsx(SpontaneousQaGroupViewer, { question: question, isReviewMode: isReviewMode }));
605
637
  case 'WORD_ORDER_AND_MATCH_GROUP':
@@ -646,6 +678,8 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
646
678
  return (_jsx(WordFillStructuredFormViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
647
679
  case 'CROSSWORD_PUZZLE':
648
680
  return (_jsx(CrosswordPuzzleClient, { content: question.content, correctAnswer: question.answer, isReviewMode: isReviewMode, userAnswers: userAnswer }));
681
+ case 'FIND_WORDS_IN_MATRIX':
682
+ return (_jsx(FindWordsInMatrixClient, { content: question.content, correctAnswer: question.answer, showSolution: isReviewMode }));
649
683
  default:
650
684
  return _jsxs("div", { children: ["Lo\u1EA1i c\u00E2u h\u1ECFi kh\u00F4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3: ", question.type] });
651
685
  }
@@ -6,3 +6,4 @@ export { TrueFalseGroupViewer } from './TrueFalseGroupViewer';
6
6
  export { CrossOutWordGroupViewer } from './CrossOutWordGroupViewer';
7
7
  export { SpontaneousQaGroupViewer } from './SpontaneousQaGroupViewer';
8
8
  export { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
9
+ export { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
@@ -6,3 +6,4 @@ export { TrueFalseGroupViewer } from './TrueFalseGroupViewer';
6
6
  export { CrossOutWordGroupViewer } from './CrossOutWordGroupViewer';
7
7
  export { SpontaneousQaGroupViewer } from './SpontaneousQaGroupViewer';
8
8
  export { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
9
+ export { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
package/dist/index.d.ts CHANGED
@@ -26,4 +26,4 @@ export { validateQuestion } from './components/questions/_shared/utils/question-
26
26
  export { buildAnswerPayload } from './components/questions/_shared/utils/answer-builder';
27
27
  export type { QuestionFormState } from './components/questions/_shared/hooks/useQuestionFormState';
28
28
  export * from './components/questions/_shared/types';
29
- export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, } from './components/questions/viewer';
29
+ export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, } from './components/questions/viewer';
package/dist/index.js CHANGED
@@ -21,4 +21,4 @@ export { QUESTION_TYPE_CONFIG, QUESTION_TYPES_WITH_DEDICATED_COMPONENTS } from '
21
21
  export { validateQuestion } from './components/questions/_shared/utils/question-validation';
22
22
  export { buildAnswerPayload } from './components/questions/_shared/utils/answer-builder';
23
23
  export * from './components/questions/_shared/types';
24
- export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, } from './components/questions/viewer';
24
+ export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, } from './components/questions/viewer';
@@ -424,8 +424,8 @@ const handlers = {
424
424
  WORD_ORDERING: reverseSimpleAnswer,
425
425
  WORD_FILL_PARAGRAPH: reverseSimpleAnswer,
426
426
  MATCH_BY_WRITING_ANSWER: reverseGroupPayload,
427
- WRITE_SENTENCES: reverseGroupPayload,
428
- WRITE_A_SHORT_LETTER: reverseSimpleAnswer,
427
+ WRITE_SHORT_PARAGRAPH: reverseWriteShortParagraph,
428
+ WRITE_A_SHORT_LETTER: reverseWriteAShortLetter,
429
429
  WRITE_CORRECT_VERB_FORM: reverseSimpleAnswer,
430
430
  CHOOSE_CORRECT_ADJECTIVE: reverseSimpleAnswer,
431
431
  WORD_FILL_STRUCTURED_FORM: reverseGroupPayload,
@@ -449,6 +449,59 @@ const handlers = {
449
449
  CROSSWORD_PUZZLE: reverseCrosswordPuzzle,
450
450
  FIND_WORDS_IN_MATRIX: reverseFindWordsInMatrix,
451
451
  };
452
+ function reverseWriteAShortLetter(apiQuestion) {
453
+ const content = asRecord(apiQuestion.content);
454
+ const answer = readAnswer(apiQuestion);
455
+ const informations = asArray(content.informations ?? answer.informations).map((item) => asString(item)).filter(Boolean);
456
+ const guideImages = asArray(content.guideImages ?? answer.guideImages).map((item) => asString(item)).filter(Boolean);
457
+ const contexts = asArray(content.contexts ?? answer.contexts);
458
+ const instructions = isRecord(content.instructions)
459
+ ? content.instructions
460
+ : isRecord(answer.instructions)
461
+ ? answer.instructions
462
+ : { mainInstruction: asString(content.instruction ?? content.prompt ?? answer.instruction ?? answer.prompt) };
463
+ const minWords = asNumber(content.minWords ?? answer.minWords, 20);
464
+ const maxWords = content.maxWords !== undefined && content.maxWords !== null
465
+ ? asNumber(content.maxWords, 0) || null
466
+ : answer.maxWords !== undefined && answer.maxWords !== null
467
+ ? asNumber(answer.maxWords, 0) || null
468
+ : null;
469
+ const resolved = {
470
+ instructions,
471
+ instruction: asString(instructions.mainInstruction || instructions.instruction),
472
+ informations,
473
+ guideImages,
474
+ contexts,
475
+ minWords,
476
+ maxWords,
477
+ };
478
+ return {
479
+ content: resolved,
480
+ answer: resolved,
481
+ };
482
+ }
483
+ function reverseWriteShortParagraph(apiQuestion) {
484
+ const content = asRecord(apiQuestion.content);
485
+ const answer = readAnswer(apiQuestion);
486
+ const contexts = asArray(content.contexts ?? answer.contexts);
487
+ const instruction = asString(content.instruction ?? content.instructions?.mainInstruction ?? answer.instruction);
488
+ const minWords = asNumber(content.minWords ?? answer.minWords, 20);
489
+ const maxWords = content.maxWords !== undefined && content.maxWords !== null
490
+ ? asNumber(content.maxWords, 0) || null
491
+ : answer.maxWords !== undefined && answer.maxWords !== null
492
+ ? asNumber(answer.maxWords, 0) || null
493
+ : null;
494
+ const resolved = {
495
+ instruction,
496
+ contexts,
497
+ minWords,
498
+ maxWords,
499
+ };
500
+ return {
501
+ content: resolved,
502
+ answer: resolved,
503
+ };
504
+ }
452
505
  function reverseCrosswordPuzzle(apiQuestion) {
453
506
  const apiContent = (apiQuestion.content || {});
454
507
  const apiAnswer = (apiQuestion.correctAnswer || apiQuestion.correct_answer || apiQuestion.answer || {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinyweb_dev/oe-exam-sdk",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Reusable OceanEdu question components.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",