@tinyweb_dev/oe-exam-sdk 0.1.4 → 0.1.6

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.
Files changed (97) hide show
  1. package/README.md +56 -11
  2. package/dist/api/result-review.d.ts +2 -1
  3. package/dist/api/result-review.js +1 -1
  4. package/dist/api/result-review.types.d.ts +69 -3
  5. package/dist/api/result-review.types.js +1 -1
  6. package/dist/components/exams/take/ExamTakingPageContainer.js +1 -1
  7. package/dist/components/exams/take/components/speaking/renderers/SpeakingDescribeImageRenderer.js +1 -1
  8. package/dist/components/exams/take/components/speaking/renderers/SpeakingReadDisplayedContentRenderer.d.ts +0 -17
  9. package/dist/components/exams/take/components/speaking/renderers/SpeakingReadDisplayedContentRenderer.js +1 -1
  10. package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -1
  11. package/dist/components/results/ResultReviewPageContainer.d.ts +15 -3
  12. package/dist/components/results/ResultReviewPageContainer.js +1 -1
  13. package/dist/components/results/ReviewQuestionRenderer.d.ts +11 -1
  14. package/dist/components/results/ReviewQuestionRenderer.js +1 -1
  15. package/dist/components/results/hooks/useResultReviewPolling.d.ts +12 -2
  16. package/dist/components/results/hooks/useResultReviewPolling.js +1 -1
  17. package/dist/components/results/index.d.ts +34 -2
  18. package/dist/components/results/index.js +1 -1
  19. package/dist/components/results/renderers/QuestionExplanations.d.ts +22 -0
  20. package/dist/components/results/renderers/QuestionExplanations.js +1 -0
  21. package/dist/components/results/renderers/ReviewChooseAdjectiveRenderer.d.ts +13 -0
  22. package/dist/components/results/renderers/ReviewChooseAdjectiveRenderer.js +1 -0
  23. package/dist/components/results/renderers/ReviewChooseBestAnswerRenderer.d.ts +22 -0
  24. package/dist/components/results/renderers/ReviewChooseBestAnswerRenderer.js +1 -0
  25. package/dist/components/results/renderers/ReviewColoringRenderer.d.ts +16 -0
  26. package/dist/components/results/renderers/ReviewColoringRenderer.js +1 -0
  27. package/dist/components/results/renderers/ReviewListenAndWriteRenderer.d.ts +18 -0
  28. package/dist/components/results/renderers/ReviewListenAndWriteRenderer.js +1 -0
  29. package/dist/components/results/renderers/ReviewListenChooseAnswerGroupRenderer.d.ts +25 -0
  30. package/dist/components/results/renderers/ReviewListenChooseAnswerGroupRenderer.js +1 -0
  31. package/dist/components/results/renderers/ReviewListenChooseObjectsRenderer.d.ts +25 -0
  32. package/dist/components/results/renderers/ReviewListenChooseObjectsRenderer.js +1 -0
  33. package/dist/components/results/renderers/ReviewListenDragObjectsRenderer.d.ts +25 -0
  34. package/dist/components/results/renderers/ReviewListenDragObjectsRenderer.js +1 -0
  35. package/dist/components/results/renderers/ReviewListeningHeader.d.ts +44 -0
  36. package/dist/components/results/renderers/ReviewListeningHeader.js +1 -0
  37. package/dist/components/results/renderers/ReviewSpeakingAIPanel.d.ts +17 -0
  38. package/dist/components/results/renderers/ReviewSpeakingAIPanel.js +1 -0
  39. package/dist/components/results/renderers/ReviewSpeakingMedia.d.ts +39 -0
  40. package/dist/components/results/renderers/ReviewSpeakingMedia.js +1 -0
  41. package/dist/components/results/renderers/ReviewSpeakingQuestionItem.d.ts +5 -0
  42. package/dist/components/results/renderers/ReviewSpeakingQuestionItem.js +1 -0
  43. package/dist/components/results/renderers/ReviewSpeakingRenderer.d.ts +3 -0
  44. package/dist/components/results/renderers/ReviewSpeakingRenderer.js +1 -0
  45. package/dist/components/results/renderers/ReviewSpeakingRenderer.types.d.ts +80 -0
  46. package/dist/components/results/renderers/ReviewSpeakingRenderer.types.js +1 -0
  47. package/dist/components/results/renderers/review-question-body-dedicated.d.ts +15 -0
  48. package/dist/components/results/renderers/review-question-body-dedicated.js +1 -0
  49. package/dist/components/results/renderers/review-question-body-movers.d.ts +17 -0
  50. package/dist/components/results/renderers/review-question-body-movers.js +1 -0
  51. package/dist/components/results/renderers/review-speaking-map.d.ts +7 -0
  52. package/dist/components/results/renderers/review-speaking-map.js +1 -0
  53. package/dist/components/results/report/BadgeTitle.d.ts +5 -0
  54. package/dist/components/results/report/BadgeTitle.js +1 -0
  55. package/dist/components/results/report/ExamSkillRow.d.ts +14 -0
  56. package/dist/components/results/report/ExamSkillRow.js +1 -0
  57. package/dist/components/results/report/OceanEduExamReport.d.ts +31 -0
  58. package/dist/components/results/report/OceanEduExamReport.js +1 -0
  59. package/dist/components/results/report/OceanEduSpeakingReport.d.ts +26 -0
  60. package/dist/components/results/report/OceanEduSpeakingReport.js +1 -0
  61. package/dist/components/results/report/OverallAssessmentSection.d.ts +24 -0
  62. package/dist/components/results/report/OverallAssessmentSection.js +1 -0
  63. package/dist/components/results/report/ResultErrorState.d.ts +14 -0
  64. package/dist/components/results/report/ResultErrorState.js +1 -0
  65. package/dist/components/results/report/ResultLoadingState.d.ts +11 -0
  66. package/dist/components/results/report/ResultLoadingState.js +1 -0
  67. package/dist/components/results/report/ResultNotPublicState.d.ts +16 -0
  68. package/dist/components/results/report/ResultNotPublicState.js +1 -0
  69. package/dist/components/results/report/SpeakingReportParts.d.ts +4 -0
  70. package/dist/components/results/report/SpeakingReportParts.js +1 -0
  71. package/dist/components/results/report/SpeakingReportShared.d.ts +36 -0
  72. package/dist/components/results/report/SpeakingReportShared.js +1 -0
  73. package/dist/components/results/report/SpeakingRubricSection.d.ts +21 -0
  74. package/dist/components/results/report/SpeakingRubricSection.js +1 -0
  75. package/dist/components/results/report/assets/dofu2.png +0 -0
  76. package/dist/components/results/report/assets/dofu4.png +0 -0
  77. package/dist/components/results/report/assets/logo-ocean.png +0 -0
  78. package/dist/components/results/report/assets/penguin-main.png +0 -0
  79. package/dist/components/results/report/assets/penguin-small.png +0 -0
  80. package/dist/components/results/report/speaking-report.utils.d.ts +37 -0
  81. package/dist/components/results/report/speaking-report.utils.js +1 -0
  82. package/dist/components/results/review-data.d.ts +1 -0
  83. package/dist/components/results/review-data.js +1 -1
  84. package/dist/components/results/review-types.d.ts +14 -0
  85. package/dist/components/results/speaking/SpeakingResultAudioPlayer.d.ts +17 -0
  86. package/dist/components/results/speaking/SpeakingResultAudioPlayer.js +1 -0
  87. package/dist/components/results/speaking/SpeakingReviewResult.d.ts +30 -0
  88. package/dist/components/results/speaking/SpeakingReviewResult.js +1 -0
  89. package/dist/components/results/utils/review-score.utils.d.ts +21 -0
  90. package/dist/components/results/utils/review-score.utils.js +1 -0
  91. package/dist/components/results/utils/speaking-summary.d.ts +41 -0
  92. package/dist/components/results/utils/speaking-summary.js +1 -0
  93. package/dist/components/results/utils/transform-speaking-review.d.ts +62 -0
  94. package/dist/components/results/utils/transform-speaking-review.js +1 -0
  95. package/dist/shared/lib/hooks/usePresignedFileUrl.d.ts +9 -1
  96. package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -1
  97. package/package.json +4 -2
@@ -1 +1 @@
1
- "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Check as n,X as t}from"lucide-react";import{Badge as i}from"../../components/ui/badge";import{ResolvedImage as a}from"../../components/common/ResolvedImage";import{asRecord as s,normalizeRecord as o,normalizeSelected as c,readQuestionText as l,readString as d,transformAdjectiveQuestions as u,transformChoiceQuestions as m,transformColoring as p,transformFillQuestions as h}from"./review-renderer.utils";export function ReviewQuestionRenderer({part:r,reviewData:n}){return["CHOOSE_THE_CORRECT_ANSWER","LOOK_PICTURE_CHOOSE_CORRECT_ANSWER","TRUE_FALSE","CHOOSE_CORRECT_ADJECTIVE"].includes(r.questionType)?e(b,{part:r,reviewData:n}):["FILL_IN_BLANK","WRITE_CORRECT_VERB_FORM","ANSWER_THE_QUESTION","WRITE_SENTENCES"].includes(r.questionType)?e(f,{part:r,reviewData:n}):"READ_AND_COLOR_OBJECTS"===r.questionType||"IMAGE_OBJECT_MATCHING"===r.questionType?e(N,{part:r,reviewData:n}):e(g,{part:r,reviewData:n})}function b({part:n,reviewData:t}){const i="CHOOSE_CORRECT_ADJECTIVE"===n.questionType?u(n.questions):m(n.questions);return e("div",{className:"space-y-4",children:i.map(i=>{const s=c(t.studentAnswers[i.id]),o=c(t.correctOptionIdMap[i.id]);return r(w,{question:n.questions.find(e=>e.id===i.id),reviewData:t,children:[i.imageUrl&&e(a,{src:i.imageUrl,alt:"Question",className:"mb-3 max-h-64 rounded object-contain"}),e("p",{className:"mb-3 font-medium",children:"questionText"in i?i.questionText:`${i.sentenceBefore} ____ ${i.sentenceAfter}`}),e("div",{className:"grid gap-2 md:grid-cols-2",children:i.options.map(n=>r("div",{className:"rounded-lg border p-3 "+(o.includes(n.id)?"border-green-400 bg-green-50":s.includes(n.id)?"border-red-300 bg-red-50":"border-slate-200"),children:[r("span",{className:"font-bold",children:[n.label,"."]})," ",n.imageUrl?e(a,{src:n.imageUrl,alt:n.label,className:"mt-2 max-h-24 rounded object-contain"}):n.text]},n.id))})]},i.id)})})}function f({part:n,reviewData:t}){const i=h(n.questions);return e("div",{className:"space-y-4",children:i.map(i=>r(w,{question:n.questions.find(e=>e.id===i.id),reviewData:t,children:[r("p",{className:"text-base",children:[e("span",{children:i.textBefore})," ",e(v,{value:t.studentAnswers[i.id],correct:t.correctAnswerTextMap[i.id]})," ",e("span",{children:i.textAfter})]}),i.imageUrl&&e(a,{src:i.imageUrl,alt:"Question",className:"mt-3 max-h-64 rounded object-contain"})]},i.id))})}function N({part:n,reviewData:t}){const i=p(n.questions);return r("div",{className:"space-y-4",children:[r("div",{className:"rounded-xl border bg-white p-4",children:[e("p",{className:"font-semibold",children:i.instruction||n.instructions}),i.imageUrl&&e(a,{src:i.imageUrl,alt:"Coloring",className:"mt-3 max-h-96 rounded object-contain"}),e("div",{className:"mt-3 flex flex-wrap gap-2",children:i.hintItems.map(r=>e("span",{className:"rounded-full border px-3 py-1 text-sm",style:{backgroundColor:r.color||void 0},children:r.label||r.id},r.id))})]}),n.questions.map(r=>e(w,{question:r,reviewData:t,children:e("pre",{className:"overflow-auto rounded bg-slate-50 p-3 text-xs",children:JSON.stringify({studentAnswer:t.rawStudentAnswerMap[r.id],correctAnswer:t.rawCorrectAnswerMap[r.id]},null,2)})},r.id))]})}function g({part:n,reviewData:t}){return e("div",{className:"space-y-4",children:n.questions.map(n=>r(w,{question:n,reviewData:t,children:[e("p",{className:"mb-3 whitespace-pre-wrap text-sm text-slate-700",children:l(n)}),e("pre",{className:"overflow-auto rounded bg-slate-50 p-3 text-xs",children:JSON.stringify({content:n.content,studentAnswer:t.rawStudentAnswerMap[n.id],correctAnswer:t.rawCorrectAnswerMap[n.id]},null,2)})]},n.id))})}function w({question:n,reviewData:t,children:a}){if(!n)return null;const s=t.questionScoreMap[n.id],o=t.isCorrectMap[n.id];return r("div",{className:"rounded-xl border bg-white p-4",children:[r("div",{className:"mb-3 flex flex-wrap items-center justify-between gap-2",children:[r("div",{className:"flex items-center gap-2",children:[r("strong",{children:["Câu ",n.question_number]}),e(x,{isCorrect:o})]}),s&&r(i,{variant:"outline",children:[_(s.score)," / ",_(s.maxScore)]})]}),a,e(E,{question:n})]})}function x({isCorrect:a}){return!0===a?r(i,{className:"bg-green-600",children:[e(n,{className:"mr-1 h-3 w-3"}),"Đúng"]}):!1===a?r(i,{variant:"destructive",children:[e(t,{className:"mr-1 h-3 w-3"}),"Sai"]}):e(i,{variant:"outline",children:"Chưa chấm"})}function v({value:e,correct:n}){return r("span",{className:"inline-flex rounded-md border border-blue-200 bg-blue-50 px-2 py-1 font-semibold text-blue-700",children:[C(e),n?r("span",{className:"ml-2 text-green-700",children:["(",n,")"]}):null]})}function E({question:n}){const t=d(n,"explanation")||d(s(n.content),"explanation");return t?r("div",{className:"mt-3 rounded-lg bg-amber-50 p-3 text-sm text-amber-900",children:[e("strong",{children:"Giải thích:"})," ",t]}):null}function C(e){if(null==e||""===e)return"—";if("string"==typeof e||"number"==typeof e||"boolean"==typeof e)return String(e);const r=o(e);return Object.values(r).join(", ")||JSON.stringify(e)}function _(e){return null!=e&&Number.isFinite(e)?e<10?e.toFixed(1):String(Math.round(e)):"0"}
1
+ "use client";import{jsx as e,Fragment as r,jsxs as t}from"react/jsx-runtime";import{QuestionExplanations as s}from"./renderers/QuestionExplanations";import{renderDedicatedReviewBody as n}from"./renderers/review-question-body-dedicated";import{renderMoversReviewBody as a}from"./renderers/review-question-body-movers";const o=new Set(["READ_DISPLAYED_CONTENT","SPEAKING_DESCRIBE_IMAGE","SPEAKING_CUE_CARD","SPONTANEOUS_QA","LISTEN_AND_SPEAK_ANSWER","LISTEN_AND_SPEAK_IMAGE_GROUP","LISTEN_AND_SPEAK_QUESTION_LIST","LISTEN_AND_SPEAK_COMPARE_IMAGES","LISTEN_AND_SPEAK_WITH_STORY_IMAGES","LISTEN_AND_SPEAK_INFO_EXCHANGE","LISTEN_AND_SPEAK_ODD_ONE_OUT"]);export function ReviewQuestionRenderer(n){const{part:a}=n,c=a.questions.map(e=>({id:e.id,questionNumber:e.question_number,explanation:e.explanation??null}));return t(r,{children:[i(n),!o.has(a.questionType)&&e(s,{items:c})]})}function i({part:r,reviewData:s,hidePerQuestionGrading:o=!1}){const i=n({part:r,answers:s.studentAnswers,isCorrectMap:s.isCorrectMap,correctAnswerTextMap:s.correctAnswerTextMap,correctOptionIdMap:s.correctOptionIdMap,rawCorrectAnswerMap:s.rawCorrectAnswerMap,questionScoreMap:s.questionScoreMap,hidePerQuestionGrading:o});if(i)return i;const c=a({part:r,answers:s.studentAnswers,isCorrectMap:s.isCorrectMap,correctAnswerTextMap:s.correctAnswerTextMap,correctOptionIdMap:s.correctOptionIdMap,cellAnswersMap:s.cellAnswersMap,essayGradingMap:s.essayGradingMap,rawCorrectAnswerMap:s.rawCorrectAnswerMap});return c||e("div",{className:"flex h-full items-center justify-center",children:t("div",{className:"text-center",children:[e("p",{className:"text-lg font-semibold text-red-500",children:"Unsupported question type"}),e("p",{className:"mt-2 text-gray-500",children:r.questionType})]})})}
@@ -8,20 +8,30 @@ export interface UseResultReviewPollingOptions {
8
8
  isLoading: boolean;
9
9
  /** Poll interval in milliseconds. */
10
10
  intervalMs?: number;
11
+ /** How long to wait for the LLM assessment before treating it as failed. */
12
+ llmTimeoutMs?: number;
11
13
  /** Called on each tick to refetch the result; should be stable. */
12
14
  onRefetch: () => void;
13
15
  }
14
16
  export interface UseResultReviewPollingResult {
15
17
  /** `true` while some answers are still being auto/LLM graded. */
16
18
  hasPendingQuestions: boolean;
17
- /** `true` while the LLM overall assessment hasn't arrived yet. */
19
+ /** `true` while the LLM overall assessment is still expected to arrive. */
18
20
  hasPendingLlm: boolean;
21
+ /** `true` once the LLM assessment has not arrived within `llmTimeoutMs`. */
22
+ hasFailedLlm: boolean;
19
23
  /** `true` while polling is active. */
20
24
  isPolling: boolean;
25
+ /** Restarts the LLM wait window and refetches immediately. */
26
+ retryLlm: () => void;
21
27
  }
22
28
  /**
23
29
  * Polls the result endpoint while any answer is still being graded or the LLM
24
30
  * assessment hasn't been generated yet. Ports the host app's behaviour: polling
25
31
  * is suppressed in print mode so the headless PDF renderer reaches an idle state.
32
+ *
33
+ * A missing assessment is indistinguishable from a failed one — the API exposes
34
+ * no status for it — so the wait is bounded by `llmTimeoutMs` and reported as
35
+ * `hasFailedLlm` once it elapses, instead of polling forever.
26
36
  */
27
- export declare function useResultReviewPolling({ detail, isPrintMode, isLoading, intervalMs, onRefetch, }: UseResultReviewPollingOptions): UseResultReviewPollingResult;
37
+ export declare function useResultReviewPolling({ detail, isPrintMode, isLoading, intervalMs, llmTimeoutMs, onRefetch, }: UseResultReviewPollingOptions): UseResultReviewPollingResult;
@@ -1 +1 @@
1
- "use client";import{useEffect as e,useRef as n}from"react";const s=new Set(["PENDING","PROCESSING","QUEUED","IN_PROGRESS"]);export function useResultReviewPolling({detail:t,isPrintMode:l,isLoading:r,intervalMs:i=5e3,onRefetch:a}){const o=(u=t?.answers,!!u&&u.some(e=>null!=e.gradingStatus&&s.has(e.gradingStatus)));var u;const c=Boolean(t?.llmOverallAssessment||t?.llmSkillAssessment),g=Boolean(t)&&!c,m=!l&&!r&&(o||g),P=n(a);return P.current=a,e(()=>{if(!m)return;const e=setInterval(()=>P.current(),i);return()=>clearInterval(e)},[i,m]),{hasPendingQuestions:o,hasPendingLlm:g,isPolling:m}}
1
+ "use client";import{useCallback as e,useEffect as n,useRef as t,useState as r}from"react";const s=new Set(["PENDING","QUEUED","PROCESSING"]);export function useResultReviewPolling({detail:l,isPrintMode:o,isLoading:i,intervalMs:a=5e3,llmTimeoutMs:u=12e4,onRefetch:c}){const m=(g=l?.answers,!!g&&g.some(e=>null!=e.gradingStatus&&s.has(e.gradingStatus)));var g;const d=Boolean(l)&&!function(e){if(!e)return!1;if(e.llmOverallAssessment)return!0;const n=e.llmSkillAssessment;return Boolean(n&&Object.values(n).some(e=>Boolean(e)))}(l),[f,v]=r(!1),[P,S]=r(0),h=t(c);h.current=c,n(()=>{if(v(!1),!d||o||i)return;const e=setTimeout(()=>v(!0),u);return()=>clearTimeout(e)},[d,o,i,u,P]);const E=d&&!f,I=d&&f,L=!o&&!i&&(m||E);n(()=>{if(!L)return;const e=setInterval(()=>h.current(),a);return()=>clearInterval(e)},[a,L]);const R=e(()=>{S(e=>e+1),h.current()},[]);return{hasPendingQuestions:m,hasPendingLlm:E,hasFailedLlm:I,isPolling:L,retryLlm:R}}
@@ -1,7 +1,39 @@
1
- export { ResultReviewPageContainer } from './ResultReviewPageContainer';
1
+ export { PartRubricTemplateCode, ResultReviewPageContainer, } from './ResultReviewPageContainer';
2
2
  export type { ResultReviewPageContainerProps } from './ResultReviewPageContainer';
3
+ export { transformDetailedResultForReview, transformResultAnswersForReview, } from './review-data';
4
+ export type { PartScoreInfo, ReviewQuestionPart, ReviewTransformResult, } from './review-types';
3
5
  export { ReviewQuestionRenderer } from './ReviewQuestionRenderer';
4
6
  export type { ReviewQuestionRendererProps } from './ReviewQuestionRenderer';
5
7
  export { useResultReviewPolling } from './hooks/useResultReviewPolling';
6
- export type { UseResultReviewPollingOptions, UseResultReviewPollingResult } from './hooks/useResultReviewPolling';
8
+ export type { UseResultReviewPollingOptions, UseResultReviewPollingResult, } from './hooks/useResultReviewPolling';
7
9
  export { isResultNotPublic, setPdfReady } from './review-status.utils';
10
+ export { ResultTemplateType, ReviewGradingStatus } from '../../api/result-review';
11
+ export type { ResultReviewAnswer, ResultReviewAttempt, ResultReviewDetail, ResultReviewSdkApi, ResultReviewTemplatePart, SpeakingRubric, } from '../../api/result-review';
12
+ export { transformSpeakingResultForReview, isSpeakingTemplateType, SPEAKING_ANSWER_COMPLETED, SPEAKING_STORY_IMAGES_TYPE, } from './utils/transform-speaking-review';
13
+ export type { SpeakingReviewData, SpeakingReviewPart, SpeakingReviewQuestion, } from './utils/transform-speaking-review';
14
+ export { buildSkillMetrics, deriveFeedback, deriveQuestionSkillMetrics, deriveSkillMetrics, deriveSkillScores, deriveSummaryPoints, getOverallStatusText, getScoreLabel, SPEAKING_SKILL_COLORS, } from './utils/speaking-summary';
15
+ export type { SkillMetric, SkillScores, SpeakingSkillKey, SummaryPoint, SummaryPointIcon, } from './utils/speaking-summary';
16
+ export { SpeakingResultAudioPlayer } from './speaking/SpeakingResultAudioPlayer';
17
+ export type { SpeakingResultAudioPlayerProps, SpeakingResultAudioPlayerVariant, } from './speaking/SpeakingResultAudioPlayer';
18
+ export { SpeakingReviewResult, SPEAKING_REVIEW_POLL_INTERVAL_MS, } from './speaking/SpeakingReviewResult';
19
+ export type { SpeakingReviewResultProps } from './speaking/SpeakingReviewResult';
20
+ export { OceanEduSpeakingReport } from './report/OceanEduSpeakingReport';
21
+ export type { OceanEduSpeakingReportProps } from './report/OceanEduSpeakingReport';
22
+ export { OceanEduExamReport } from './report/OceanEduExamReport';
23
+ export type { OceanEduExamReportProps } from './report/OceanEduExamReport';
24
+ export { OverallAssessmentSection } from './report/OverallAssessmentSection';
25
+ export type { OverallAssessmentSectionProps, LlmSkillAssessmentItem, } from './report/OverallAssessmentSection';
26
+ export { ResultNotPublicState } from './report/ResultNotPublicState';
27
+ export type { ResultNotPublicStateProps } from './report/ResultNotPublicState';
28
+ export { ResultLoadingState } from './report/ResultLoadingState';
29
+ export type { ResultLoadingStateProps } from './report/ResultLoadingState';
30
+ export { ResultErrorState } from './report/ResultErrorState';
31
+ export type { ResultErrorStateProps } from './report/ResultErrorState';
32
+ export { SpeakingRubricSection } from './report/SpeakingRubricSection';
33
+ export type { SpeakingRubricSectionProps } from './report/SpeakingRubricSection';
34
+ export { SpeakingRubricScoringVersion, KidsRubricSectionCode, } from './report/SpeakingRubricSection';
35
+ export { BadgeTitle } from './report/BadgeTitle';
36
+ export type { BadgeTitleProps } from './report/BadgeTitle';
37
+ export { ReviewScoreBand, formatScore, formatTotalScore,
38
+ /** General exam score label (distinct from speaking {@link getScoreLabel}). */
39
+ getScoreLabel as getExamScoreLabel, getScoreMessage as getExamScoreMessage, getScoreStroke as getExamScoreStroke, } from './utils/review-score.utils';
@@ -1 +1 @@
1
- export{ResultReviewPageContainer}from"./ResultReviewPageContainer";export{ReviewQuestionRenderer}from"./ReviewQuestionRenderer";export{useResultReviewPolling}from"./hooks/useResultReviewPolling";export{isResultNotPublic,setPdfReady}from"./review-status.utils";
1
+ export{PartRubricTemplateCode,ResultReviewPageContainer}from"./ResultReviewPageContainer";export{transformDetailedResultForReview,transformResultAnswersForReview}from"./review-data";export{ReviewQuestionRenderer}from"./ReviewQuestionRenderer";export{useResultReviewPolling}from"./hooks/useResultReviewPolling";export{isResultNotPublic,setPdfReady}from"./review-status.utils";export{ResultTemplateType,ReviewGradingStatus}from"../../api/result-review";export{transformSpeakingResultForReview,isSpeakingTemplateType,SPEAKING_ANSWER_COMPLETED,SPEAKING_STORY_IMAGES_TYPE}from"./utils/transform-speaking-review";export{buildSkillMetrics,deriveFeedback,deriveQuestionSkillMetrics,deriveSkillMetrics,deriveSkillScores,deriveSummaryPoints,getOverallStatusText,getScoreLabel,SPEAKING_SKILL_COLORS}from"./utils/speaking-summary";export{SpeakingResultAudioPlayer}from"./speaking/SpeakingResultAudioPlayer";export{SpeakingReviewResult,SPEAKING_REVIEW_POLL_INTERVAL_MS}from"./speaking/SpeakingReviewResult";export{OceanEduSpeakingReport}from"./report/OceanEduSpeakingReport";export{OceanEduExamReport}from"./report/OceanEduExamReport";export{OverallAssessmentSection}from"./report/OverallAssessmentSection";export{ResultNotPublicState}from"./report/ResultNotPublicState";export{ResultLoadingState}from"./report/ResultLoadingState";export{ResultErrorState}from"./report/ResultErrorState";export{SpeakingRubricSection}from"./report/SpeakingRubricSection";export{SpeakingRubricScoringVersion,KidsRubricSectionCode}from"./report/SpeakingRubricSection";export{BadgeTitle}from"./report/BadgeTitle";export{ReviewScoreBand,formatScore,formatTotalScore,getScoreLabel as getExamScoreLabel,getScoreMessage as getExamScoreMessage,getScoreStroke as getExamScoreStroke}from"./utils/review-score.utils";
@@ -0,0 +1,22 @@
1
+ interface ExplanationItem {
2
+ /** Question id (used as React key) */
3
+ id: string;
4
+ /** Display number shown before the explanation */
5
+ questionNumber?: number | string | null;
6
+ /** Explanation text from the questions.explanation column */
7
+ explanation?: string | null;
8
+ }
9
+ interface QuestionExplanationsProps {
10
+ /** All questions in the current part (each may carry an explanation). */
11
+ items: ExplanationItem[];
12
+ }
13
+ /**
14
+ * QuestionExplanations - shared review block that renders the per-question
15
+ * explanation (questions.explanation) for every question that has one.
16
+ *
17
+ * Rendered centrally by ReviewQuestionRenderer after the type-specific
18
+ * renderer output, so ALL question types show explanations uniformly without
19
+ * each renderer having to implement it.
20
+ */
21
+ export declare function QuestionExplanations({ items }: QuestionExplanationsProps): import("react").JSX.Element;
22
+ export {};
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{Lightbulb as a}from"lucide-react";export function QuestionExplanations({items:n}){const s=n.filter(e=>"string"==typeof e.explanation&&e.explanation.trim().length>0);if(0===s.length)return null;const r=s.length>1;return t("div",{className:"mt-4 rounded-xl border border-amber-200 bg-amber-50/80 p-4 shadow-sm",children:[t("div",{className:"mb-2 flex items-center gap-1.5 text-sm font-bold text-amber-700",children:[e(a,{className:"h-4 w-4"}),"Giải thích"]}),e("div",{className:"space-y-3",children:s.map(a=>t("div",{className:"text-sm leading-relaxed text-amber-900",children:[r&&null!=a.questionNumber&&t("span",{className:"mr-1.5 font-bold",children:["Câu ",a.questionNumber,"."]}),e("span",{className:"whitespace-pre-wrap",children:a.explanation})]},a.id))})]})}
@@ -0,0 +1,13 @@
1
+ import type { ChooseAdjectiveQuestion } from '../../../components/exams/take/utils/question-transformers';
2
+ interface ReviewChooseAdjectiveRendererProps {
3
+ partNumber: number;
4
+ questionCount: number;
5
+ instruction: string;
6
+ partName?: string;
7
+ questions: ChooseAdjectiveQuestion[];
8
+ answers: Record<string, string>;
9
+ isCorrectMap: Record<string, boolean | null>;
10
+ correctOptionIdMap: Record<string, string>;
11
+ }
12
+ export declare function ReviewChooseAdjectiveRenderer({ partNumber, questionCount, instruction, partName, questions, answers, isCorrectMap, correctOptionIdMap, }: ReviewChooseAdjectiveRendererProps): import("react").JSX.Element;
13
+ export {};
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{Check as r,X as n}from"lucide-react";import i from"../../../components/themes/cambridge-yle/CambridgeYlePartBanner";import s from"../../../components/themes/cambridge-yle/CambridgeYleInstructionBanner";function l({question:i,selectedOptionId:s,isCorrect:l,correctOptionId:c}){const a=function(e,t){if(!t)return null;const r=e.options.find(e=>e.id===t);return r?.text||null}(i,s),o=function(e,t){if(!t)return null;const r=e.options.find(e=>e.id===t);return r?.text||null}(i,c),d=!!s,m=e=>{const t=s===e.id,r=c===e.id;return t&&!0===l?"border-green-500 bg-green-50 text-green-700":t&&!1===l?"border-red-500 bg-red-50 text-red-700":!r||!1!==l&&d?"border-gray-300 bg-white text-gray-500":"border-blue-500 bg-blue-50 text-blue-700"},u=d?!0===l?{borderColor:"#22c55e"}:{borderColor:"#ef4444"}:{borderColor:"#9ca3af"},p=d?!0===l?"correct":"incorrect":"unanswered";return t("div",{className:"mb-6",children:[t("div",{className:"mb-3 flex items-start gap-2",children:[e("span",{className:"flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-sm font-bold text-white",children:i.questionNumber}),e("div",{className:"flex-1 text-base leading-relaxed text-gray-800",children:t("div",{className:"flex flex-wrap items-end gap-1",children:[i.sentenceBefore&&e("span",{children:i.sentenceBefore}),t("span",{className:"inline-block min-w-[120px]",style:{borderBottom:`2px solid ${u.borderColor}`},children:["correct"===p&&e("span",{className:"block w-full text-center",children:a}),"unanswered"===p&&e("span",{className:"block w-full text-center text-gray-400",children:"_______"}),"incorrect"===p&&t("span",{className:"flex items-center justify-center gap-2",children:[e("span",{className:"text-red-500 line-through",children:a}),e("span",{className:"text-blue-600",children:o})]})]}),i.sentenceAfter&&e("span",{children:i.sentenceAfter}),t("span",{className:"ml-2 text-sm italic text-gray-500",children:["(",i.options.map(e=>e.text).join(" / "),")"]})]})})]}),e("div",{className:"flex flex-wrap gap-2 pl-9",children:i.options.map(i=>{const a=s===i.id,o=c===i.id,u=a&&!0===l,p=a&&!1===l,f=o&&(!1===l||!d);return t("div",{className:`flex items-center gap-2 rounded-lg border-2 px-4 py-2 text-sm font-medium ${m(i)}`,children:[t("span",{children:[i.label,". ",i.text]}),u&&e("div",{className:"flex h-5 w-5 items-center justify-center rounded-full bg-green-500",children:e(r,{className:"h-3 w-3 text-white",strokeWidth:3})}),p&&e("div",{className:"flex h-5 w-5 items-center justify-center rounded-full bg-red-500",children:e(n,{className:"h-3 w-3 text-white",strokeWidth:3})}),f&&e("div",{className:"flex h-5 w-5 items-center justify-center rounded-full bg-blue-500",children:e(r,{className:"h-3 w-3 text-white",strokeWidth:3})})]},i.id)})})]})}export function ReviewChooseAdjectiveRenderer({partNumber:r,questionCount:n,instruction:c,partName:a,questions:o,answers:d,isCorrectMap:m,correctOptionIdMap:u}){return t("div",{className:"flex flex-col gap-6 h-full",children:[t("div",{className:"flex flex-col gap-4 pb-0 pt-4",children:[e(i,{partNumber:r,questionCount:n,partName:a,isReviewMode:!0}),e(s,{instruction:c,isReviewMode:!0})]}),e("div",{className:"flex flex-col gap-4 pb-6 pl-2 pt-2",children:o.map(t=>e(l,{question:t,selectedOptionId:d[t.id],isCorrect:m[t.id],correctOptionId:u[t.id]},t.id))})]})}
@@ -0,0 +1,22 @@
1
+ import type { ChooseBestAnswerQuestion, ChooseBestAnswerExample } from '../../../components/exams/take/types';
2
+ interface ReviewChooseBestAnswerRendererProps {
3
+ partNumber: number;
4
+ questionCount: number;
5
+ instruction: string;
6
+ partName?: string;
7
+ imageUrl?: string;
8
+ audioUrl?: string;
9
+ example?: ChooseBestAnswerExample;
10
+ groupContent?: string;
11
+ articles?: Array<{
12
+ name: string;
13
+ content: string;
14
+ imageUrl?: string;
15
+ }>;
16
+ questions: ChooseBestAnswerQuestion[];
17
+ answers: Record<string, string | string[]>;
18
+ isCorrectMap: Record<string, boolean | null>;
19
+ correctOptionIdMap: Record<string, string>;
20
+ }
21
+ export declare function ReviewChooseBestAnswerRenderer({ partNumber, questionCount, instruction, partName, imageUrl, audioUrl, groupContent, articles, questions, answers, isCorrectMap, correctOptionIdMap, }: ReviewChooseBestAnswerRendererProps): import("react").JSX.Element;
22
+ export {};
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as t,Fragment as r}from"react/jsx-runtime";import{Check as l,X as a,ZoomIn as i}from"lucide-react";import{useState as s}from"react";import{ResolvedImage as n}from"../../../components/common/ResolvedImage";import o from"../../../components/themes/cambridge-yle/CambridgeYlePartBanner";import c from"../../../components/themes/cambridge-yle/CambridgeYleInstructionBanner";import d from"../../../components/themes/cambridge-yle/CambridgeYleAudioPlayer";function m({imageUrl:l}){const[a,o]=s(!1);return t(r,{children:[t("div",{className:"relative flex items-center justify-center",children:[e(n,{src:l,alt:"Question illustration",className:"max-h-[250px] cursor-pointer rounded-lg object-contain shadow-md",onClick:()=>o(!0)}),e("button",{type:"button",onClick:()=>o(!0),className:"absolute right-2 top-2 rounded-full bg-white/80 p-1.5 shadow-md hover:bg-white","aria-label":"Zoom image",children:e(i,{className:"h-4 w-4 text-gray-600"})})]}),a&&e("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/70",onClick:()=>o(!1),children:e(n,{src:l,alt:"Question illustration (zoomed)",className:"max-h-[90vh] max-w-[90vw] rounded-lg object-contain",showLoadingState:!1})})]})}function u({question:r,selectedOptionIds:i,isCorrect:s,correctOptionIds:o}){const c=i.length>0,d="image"===r.optionType,u=Boolean(r.multipleAnswers||o.length>1),p=e=>{const t=i.includes(e),r=o.includes(e);return t&&r?"border-green-500 bg-green-50":t&&!r?"border-red-500 bg-red-50":!r||c&&!1!==s&&t?"border-gray-300 bg-white":"border-blue-500 bg-blue-50"},h=t=>{const r=i.includes(t),n=o.includes(t),d=r&&!n,m=n&&(!c||!1===s||!r),p=u?"rounded-md":"rounded-full";return e("div",r&&n?{className:`flex h-6 w-6 items-center justify-center bg-green-500 ${p}`,children:e(l,{className:"h-4 w-4 text-white",strokeWidth:3})}:d?{className:`flex h-6 w-6 items-center justify-center bg-red-500 ${p}`,children:e(a,{className:"h-4 w-4 text-white",strokeWidth:3})}:m?{className:`flex h-6 w-6 items-center justify-center bg-blue-500 ${p}`,children:e(l,{className:"h-4 w-4 text-white",strokeWidth:3})}:{className:`flex h-5 w-5 items-center justify-center border-2 border-gray-300 bg-white ${p}`})};return t("div",{className:"mb-4",children:[t("p",{className:"mb-2 text-lg font-bold text-gray-900",children:[r.questionNumber,". ",r.questionText]}),u&&e("p",{className:"mb-3 pl-2 text-sm font-medium text-blue-600",children:"Chọn nhiều đáp án"}),r.imageUrl&&e("div",{className:"mb-3",children:e(m,{imageUrl:r.imageUrl})}),e("div",d?{className:"grid gap-3 px-2",style:{gridTemplateColumns:`repeat(${r.options.length}, minmax(0, 1fr))`},children:r.options.map(r=>t("div",{className:`relative flex flex-col items-center gap-1 rounded-lg border-2 p-2 cursor-default ${p(r.id)}`,children:[e("span",{className:"text-xs font-medium text-gray-600",children:r.label}),e(n,{src:r.text,alt:`Option ${r.label}`,className:"h-28 w-28 rounded object-contain"}),h(r.id)]},r.id))}:{className:"flex flex-col gap-2 pl-2",children:r.options.map(r=>t("div",{className:`flex items-center gap-3 rounded-lg border-2 px-3 py-2 cursor-default ${p(r.id)}`,children:[t("span",{className:"text-sm font-medium text-gray-600",children:[r.label,"."]}),e("span",{className:"flex-1 text-sm text-gray-700",children:r.text}),h(r.id)]},r.id))})]})}function p({article:r,label:l}){return t("div",{className:"rounded-lg border border-gray-200 bg-white p-4 shadow-sm",children:[t("div",{className:"mb-2 flex items-center gap-2",children:[e("span",{className:"flex h-7 w-7 items-center justify-center rounded-full bg-teal-500 text-xs font-bold text-white",children:l}),e("h3",{className:"text-sm font-bold text-gray-900",children:r.name})]}),r.imageUrl&&e("div",{className:"mb-2",children:e(n,{src:r.imageUrl,alt:r.name,className:"h-24 w-full rounded-md object-cover"})}),e("p",{className:"text-sm leading-relaxed text-gray-700 whitespace-pre-line",children:r.content})]})}export function ReviewChooseBestAnswerRenderer({partNumber:r,questionCount:l,instruction:a,partName:i,imageUrl:s,audioUrl:m,groupContent:h,articles:g,questions:x,answers:b,isCorrectMap:f,correctOptionIdMap:N}){return t("div",{className:"flex flex-col gap-6 h-full",children:[t("div",{className:"flex flex-col gap-4 pb-0 pt-4",children:[e(o,{partNumber:r,questionCount:l,partName:i,isReviewMode:!0}),e(c,{instruction:a,isReviewMode:!0}),m&&e(d,{audioSrc:m,compact:!0}),s&&e("div",{className:"flex items-center justify-center",children:e(n,{src:s,alt:"Shared illustration",className:"max-h-[300px] rounded-lg object-contain shadow-md"})}),g&&g.length>0?e("div",{className:"flex flex-col gap-3",children:g.map((t,r)=>e(p,{article:t,label:String.fromCharCode(65+r)},r))}):h?e("div",{className:"rounded-lg bg-amber-50 p-4 text-sm leading-relaxed text-gray-700 max-h-[500px] overflow-y-auto pr-2 custom-scrollbar",children:h}):null]}),e("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-6 pb-6 pl-2 pt-2",children:x.map(t=>{const r=b[t.id],l=Array.isArray(r)?r:"string"==typeof r&&r?[r]:[],a=N[t.id]?N[t.id].split(",").map(e=>e.trim()).filter(Boolean):[],i=t.correctOptionIds?.length?t.correctOptionIds:a;return e(u,{question:t,selectedOptionIds:l,isCorrect:f[t.id],correctOptionIds:i},t.id)})})]})}
@@ -0,0 +1,16 @@
1
+ import type { ColoringHintItem, ColoringRegion } from '../../../components/exams/take/types';
2
+ interface ReviewColoringRendererProps {
3
+ partNumber: number;
4
+ questionCount: number;
5
+ instruction: string;
6
+ instructions?: string[];
7
+ partName?: string;
8
+ imageUrl: string;
9
+ audioUrl?: string;
10
+ hintItems: ColoringHintItem[];
11
+ regions: ColoringRegion[];
12
+ assignments: Record<string, string>;
13
+ correctAnswers: Record<string, string>;
14
+ }
15
+ export declare function ReviewColoringRenderer({ partNumber, questionCount, instruction, instructions, partName, imageUrl, audioUrl, hintItems, regions, assignments, correctAnswers, }: ReviewColoringRendererProps): import("react").JSX.Element;
16
+ export {};
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Check as t,X as s}from"lucide-react";import{useMemo as o,useState as n}from"react";import{usePresignedFileUrl as a}from"../../../shared/lib/hooks/usePresignedFileUrl";import i from"../../../components/themes/cambridge-yle/CambridgeYleInstructionBanner";import l from"../../../components/themes/cambridge-yle/CambridgeYleAudioPlayer";import c from"../../../components/themes/cambridge-yle/CambridgeYlePartBanner";function d({imageUrl:o,regions:i,hintItems:l,assignments:c,correctAnswers:d}){const{previewUrl:m}=a(o),[u,p]=n(null),h=e=>{if(!e)return"#333";if(!e.startsWith("#"))return"#fff";const r=e.replace("#","");if(r.length<6)return"#fff";return(.299*parseInt(r.substring(0,2),16)+.587*parseInt(r.substring(2,4),16)+.114*parseInt(r.substring(4,6),16))/255>.5?"#000":"#fff"};return r("div",{className:"relative inline-block",children:[e("img",{src:m||o,alt:"Coloring question",className:"max-h-[600px] w-full max-w-[900px] rounded-lg object-contain shadow-sm",onLoad:e=>{const r=e.currentTarget;p({width:r.naturalWidth,height:r.naturalHeight})}}),u&&i.map(o=>{const n=c[o.id],a=n?l.find(e=>e.color?.toUpperCase()===n.toUpperCase()):void 0,i=d[o.id],m=i?l.find(e=>e.color?.toUpperCase()===i.toUpperCase()):void 0,u=function(e,r,t){const s=t[e];return s?s.toUpperCase()===r.toUpperCase()?"correct":"incorrect":"unanswered"}(o.id,i||"",c),p=function(e){switch(e){case"correct":return"border-green-500";case"incorrect":return"border-red-500";case"unanswered":return"border-blue-500";default:return"border-gray-400"}}(u);return r("div",{className:`absolute rounded border-2 ${p} flex items-center justify-center`,style:{left:`${o.x}%`,top:`${o.y}%`,width:`${o.width}%`,height:`${o.height}%`,backgroundColor:a?.color||"transparent"},children:[a?.label&&e("span",{className:"z-10 text-[8px] sm:text-[10px] font-bold uppercase tracking-wide drop-shadow-sm font-[family-name:var(--font-nunito)]",style:{color:a.color?h(a.color):"#333"},children:a.label}),r("div",{className:"absolute -right-2 -top-2 z-20",children:["correct"===u&&e("div",{className:"flex h-5 w-5 items-center justify-center rounded-full bg-green-500 shadow-sm",children:e(t,{className:"h-3 w-3 text-white",strokeWidth:3})}),"incorrect"===u&&e("div",{className:"flex h-5 w-5 items-center justify-center rounded-full bg-red-500 shadow-sm",children:e(s,{className:"h-3 w-3 text-white",strokeWidth:3})})]}),"correct"!==u&&m&&r("div",{className:"absolute left-1/2 -translate-x-1/2 z-30 flex items-center gap-1 rounded shadow-md px-1.5 py-0.5 border-2 border-green-500",style:{bottom:"-22px",backgroundColor:m.color||"#fff"},children:[e(t,{className:"h-2 w-2 sm:h-2.5 sm:w-2.5",style:{color:m.color?h(m.color):"#333"},strokeWidth:4}),e("span",{className:"text-[7px] sm:text-[9px] font-bold uppercase tracking-wide whitespace-nowrap drop-shadow-sm font-[family-name:var(--font-nunito)]",style:{color:m.color?h(m.color):"#333"},children:m.label})]})]},o.id)})]})}function m({hintItems:t}){return e("div",{className:"flex flex-wrap gap-2",children:t.map(t=>r("div",{className:"flex items-center gap-2 rounded-lg border border-gray-200 px-3 py-1.5",children:[t.color&&e("div",{className:"h-5 w-5 rounded border border-gray-300",style:{backgroundColor:t.color}}),t.label&&e("span",{className:"text-sm text-gray-700",children:t.label})]},t.id))})}function u(){return r("div",{className:"flex items-center gap-2 rounded-lg bg-green-50 px-4 py-3 text-green-700",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-full bg-green-500",children:e(t,{className:"h-4 w-4 text-white",strokeWidth:3})}),e("span",{className:"font-medium",children:"All answers are correct!"})]})}export function ReviewColoringRenderer({partNumber:t,questionCount:s,instruction:n,instructions:a,partName:p,imageUrl:h,audioUrl:f,hintItems:g,regions:b,assignments:w,correctAnswers:x}){const y=o(()=>b.every(e=>{const r=w[e.id],t=x[e.id];return!(!r||!t)&&r.toUpperCase()===t.toUpperCase()}),[b,w,x]);return r("div",{className:"flex h-full flex-col gap-4 overflow-y-auto p-6",children:[e(c,{partNumber:t,questionCount:s,partName:p,isReviewMode:!0}),e(i,{instruction:n,instructions:a,isReviewMode:!0,children:f&&e(l,{audioSrc:f,compact:!0})}),y&&e(u,{}),e(m,{hintItems:g}),e("div",{className:"flex justify-center mt-4",children:e(d,{imageUrl:h,regions:b,hintItems:g,assignments:w,correctAnswers:x})})]})}
@@ -0,0 +1,18 @@
1
+ import type { FillBlankQuestion } from '../../../components/exams/take/types';
2
+ interface ReviewListenAndWriteRendererProps {
3
+ partNumber: number;
4
+ questionCount: number;
5
+ instruction: string;
6
+ partName?: string;
7
+ imageUrl?: string;
8
+ audioUrl?: string;
9
+ title: string;
10
+ exampleText?: string;
11
+ exampleHighlight?: string;
12
+ questions: FillBlankQuestion[];
13
+ answers: Record<string, string>;
14
+ isCorrectMap: Record<string, boolean | null>;
15
+ correctAnswerTextMap: Record<string, string>;
16
+ }
17
+ export declare function ReviewListenAndWriteRenderer({ partNumber, questionCount, instruction, partName, audioUrl, title, exampleText, questions, answers, isCorrectMap, correctAnswerTextMap, }: ReviewListenAndWriteRendererProps): import("react").JSX.Element;
18
+ export {};
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{Check as r,X as s}from"lucide-react";import{ResolvedImage as l}from"../../../components/common/ResolvedImage";import n from"../../../components/themes/cambridge-yle/CambridgeYlePartBanner";import a from"../../../components/themes/cambridge-yle/CambridgeYleInstructionBanner";import c from"../../../components/themes/cambridge-yle/CambridgeYleAudioPlayer";export function ReviewListenAndWriteRenderer({partNumber:i,questionCount:o,instruction:d,partName:m,audioUrl:u,title:x,exampleText:f,questions:h,answers:p,isCorrectMap:N,correctAnswerTextMap:b}){const g=e=>{const t=N[e],r=p[e];return null==r||""===r?"unanswered":!0===t?"correct":!1===t?"incorrect":"unanswered"},w=e=>{switch(g(e)){case"correct":return"#22c55e";case"incorrect":return"#ef4444";case"unanswered":return"#3b82f6";default:return"#9ca3af"}};return t("div",{className:"flex flex-col gap-6 h-full",children:[t("div",{className:"flex flex-col gap-4 pb-0 pt-4",children:[e(n,{partNumber:i,questionCount:o,partName:m,isReviewMode:!0}),e(a,{instruction:d,hasExample:!!f,isReviewMode:!0}),u&&e(c,{audioSrc:u,compact:!0})]}),t("div",{className:"flex flex-col gap-6 pb-6 pl-2 pt-2",children:[e("h2",{className:"text-center text-3xl font-bold uppercase text-coral-500",children:x}),e("div",{className:"flex flex-col gap-4",children:h.map(n=>{const a=g(n.id),c=p[n.id]||"";return t("div",{className:"flex flex-col gap-2",children:[n.imageUrl&&e("div",{className:"flex items-center justify-center",children:e(l,{src:n.imageUrl,alt:`Question ${n.questionNumber} illustration`,className:"max-h-[150px] rounded-lg object-contain shadow-md"})}),t("div",{className:"flex items-start gap-2",children:[e("span",{className:"flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-sm font-bold text-white",children:n.questionNumber}),e("div",{className:"flex-1 text-base leading-relaxed text-gray-800",children:t("div",{className:"flex items-center gap-2",children:[n.textBefore&&e("span",{children:n.textBefore}),t("span",{className:"inline-block min-w-[120px]",style:{borderBottom:`2px solid ${w(n.id)}`},children:["correct"===a&&e("span",{className:"block w-full text-center",children:c}),"unanswered"===a&&e("span",{className:"block w-full text-center text-blue-600",children:b[n.id]}),"incorrect"===a&&t("span",{className:"flex items-center justify-center gap-2",children:[e("span",{className:"text-red-500 line-through",children:c}),e("span",{className:"text-gray-400",children:"→"}),e("span",{className:"text-blue-600",children:b[n.id]})]})]}),n.textAfter&&e("span",{children:n.textAfter}),"correct"===a&&e("div",{className:"flex h-6 w-6 items-center justify-center rounded-full bg-green-500",children:e(r,{className:"h-4 w-4 text-white",strokeWidth:3})}),"incorrect"===a&&e("div",{className:"flex h-6 w-6 items-center justify-center rounded-full bg-red-500",children:e(s,{className:"h-4 w-4 text-white",strokeWidth:3})}),"unanswered"===a&&e("div",{className:"flex h-6 w-6 items-center justify-center rounded-full bg-blue-500",children:e(r,{className:"h-4 w-4 text-white",strokeWidth:3})})]})})]})]},n.id)})})]})]})}
@@ -0,0 +1,25 @@
1
+ export interface ReviewAnswerGroupQuestion {
2
+ id: string;
3
+ question_number: number;
4
+ content?: Record<string, unknown>;
5
+ questionGroup?: {
6
+ payload?: unknown;
7
+ } | null;
8
+ total_points?: string | number;
9
+ }
10
+ interface Props {
11
+ partNumber: number;
12
+ questionCount: number;
13
+ partName?: string;
14
+ instruction?: string;
15
+ questions: ReviewAnswerGroupQuestion[];
16
+ studentAnswers: Record<string, unknown>;
17
+ rawCorrectAnswerMap: Record<string, unknown>;
18
+ isCorrectMap: Record<string, boolean | null>;
19
+ questionScoreMap: Record<string, {
20
+ score: number | null;
21
+ maxScore: number;
22
+ }>;
23
+ }
24
+ export declare function ReviewListenChooseAnswerGroupRenderer({ partNumber, questionCount, partName, questions, studentAnswers, rawCorrectAnswerMap, isCorrectMap, questionScoreMap, }: Props): import("react").JSX.Element;
25
+ export {};
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Check as t,X as i}from"lucide-react";import{ResolvedImage as l}from"../../../components/common/ResolvedImage";import a from"../../../components/themes/cambridge-yle/CambridgeYlePartBanner";import{cn as s}from"../../../shared/lib/utils";import{ReviewListeningHeader as n,ListeningLegend as o,getListeningStatus as d}from"./ReviewListeningHeader";function c(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function m(e,r){return c(e)&&"string"==typeof e[r]?e[r]:""}function u({option:a,isCorrect:n,isStudent:o}){let d="border-dashed border-slate-300 bg-white",c=null,m="",u=a.label;return n&&o?(d="border-solid border-emerald-500 bg-emerald-50 ring-2 ring-emerald-500/30",u=`${a.label} – Đáp án đúng (bạn đã chọn)`,c=e("span",{className:"absolute right-1 top-1 flex h-7 w-7 items-center justify-center rounded-full bg-emerald-500 text-white shadow-md ring-2 ring-white",children:e(t,{className:"h-4 w-4",strokeWidth:3})})):n?(d="border-solid border-emerald-500 bg-emerald-50 ring-2 ring-emerald-500/30",u=`${a.label} – Đáp án đúng`,c=e("span",{className:"absolute right-1 top-1 flex h-7 w-7 items-center justify-center rounded-full bg-emerald-500 text-white shadow-md ring-2 ring-white",children:e(t,{className:"h-4 w-4",strokeWidth:3})})):o?(d="border-solid border-red-500 bg-red-50 ring-2 ring-red-500/30",u=`${a.label} – Lựa chọn của bạn (sai)`,c=e("span",{className:"absolute right-1 top-1 flex h-7 w-7 items-center justify-center rounded-full bg-red-500 text-white shadow-md ring-2 ring-white",children:e(i,{className:"h-4 w-4",strokeWidth:3})})):m="opacity-60",r("div",{"aria-label":u,className:s("relative flex flex-col items-center rounded-xl border-2 p-2 transition",d,m),children:[e("div",{className:"aspect-square w-full overflow-hidden rounded-lg bg-white",children:a.imageUrl?e(l,{src:a.imageUrl,alt:a.label,className:"h-full w-full object-contain",draggable:!1}):e("div",{className:"flex h-full w-full items-center justify-center text-xs text-slate-400",children:a.label})}),e("p",{className:"mt-2 line-clamp-1 w-full text-center text-xs font-medium text-slate-700",children:a.label}),c]})}export function ReviewListenChooseAnswerGroupRenderer({partNumber:t,questionCount:i,partName:l,questions:s,studentAnswers:p,rawCorrectAnswerMap:b,isCorrectMap:g,questionScoreMap:h}){return r("div",{className:"flex flex-col gap-6 pt-4",children:[e(a,{partNumber:t,questionCount:i,partName:l,isReviewMode:!0}),s.map(t=>{const i=function(e){let r=e?.payload;if("string"==typeof r)try{r=JSON.parse(r)}catch{return{}}return c(r)&&Array.isArray(r.options)?{options:r.options.flatMap(e=>c(e)&&"string"==typeof e.id?[{id:e.id,label:"string"==typeof e.label?e.label:"",imageUrl:"string"==typeof e.imageUrl?e.imageUrl:""}]:[])}:{}}(t.questionGroup).options??[],l=m(t.content,"audioUrl")||m(t.content,"audio"),a=m(p[t.id],"answer"),s=m(b[t.id],"answer"),f=g[t.id]??null,w=d(f,!!a),x=h[t.id]??{score:null,maxScore:Number(t.total_points||0)};return r("div",{className:"rounded-2xl border border-slate-100 bg-slate-50/60 p-4 md:p-5",children:[e(n,{questionNumber:t.question_number,typeLabel:"Listen & choose answer",status:w,score:x.score,maxScore:x.maxScore,audioKey:l}),e(o,{showCorrect:!0,showStudent:!!a&&!f}),i.length>0?e("div",{className:"mt-3 grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4",children:i.map(r=>e(u,{option:r,isCorrect:r.id===s,isStudent:r.id===a},r.id))}):e("div",{className:"mt-3 rounded-lg border border-slate-200 bg-white p-4 text-center text-sm text-slate-400",children:"No options available"}),!a&&e("p",{className:"mt-3 text-xs italic text-slate-500",children:"Bạn chưa trả lời câu này."})]},t.id)})]})}
@@ -0,0 +1,25 @@
1
+ export interface ReviewChooseObjectsQuestion {
2
+ id: string;
3
+ question_number: number;
4
+ content?: Record<string, unknown>;
5
+ questionGroup?: {
6
+ payload?: unknown;
7
+ } | null;
8
+ total_points?: string | number;
9
+ }
10
+ interface Props {
11
+ partNumber: number;
12
+ questionCount: number;
13
+ partName?: string;
14
+ instruction?: string;
15
+ questions: ReviewChooseObjectsQuestion[];
16
+ studentAnswers: Record<string, unknown>;
17
+ rawCorrectAnswerMap: Record<string, unknown>;
18
+ isCorrectMap: Record<string, boolean | null>;
19
+ questionScoreMap: Record<string, {
20
+ score: number | null;
21
+ maxScore: number;
22
+ }>;
23
+ }
24
+ export declare function ReviewListenChooseObjectsRenderer({ partNumber, questionCount, partName, questions, studentAnswers, rawCorrectAnswerMap, isCorrectMap, questionScoreMap, }: Props): import("react").JSX.Element;
25
+ export {};
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Check as t,X as l}from"lucide-react";import{ResolvedImage as a}from"../../../components/common/ResolvedImage";import i from"../../../components/themes/cambridge-yle/CambridgeYlePartBanner";import{cn as d}from"../../../shared/lib/utils";import{ReviewListeningHeader as s,ListeningLegend as n,getListeningStatus as o}from"./ReviewListeningHeader";function c(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function m(e,r){return c(e)&&"string"==typeof e[r]?e[r]:""}export function ReviewListenChooseObjectsRenderer({partNumber:h,questionCount:p,partName:u,questions:b,studentAnswers:g,rawCorrectAnswerMap:f,isCorrectMap:x,questionScoreMap:w}){return r("div",{className:"flex flex-col gap-6 pt-4",children:[e(i,{partNumber:h,questionCount:p,partName:u,isReviewMode:!0}),b.map(i=>{const h=function(e){let r=e?.payload;if("string"==typeof r)try{r=JSON.parse(r)}catch{return{}}if(!c(r))return{};const t=Array.isArray(r.objectNodes)?r.objectNodes.flatMap(e=>c(e)&&"string"==typeof e.id?[{id:e.id,label:"string"==typeof e.label?e.label:"",imageUrl:"string"==typeof e.imageUrl?e.imageUrl:"",x:"number"==typeof e.x?e.x:0,y:"number"==typeof e.y?e.y:0,width:"number"==typeof e.width?e.width:0,height:"number"==typeof e.height?e.height:0}]:[]):[];return{backgroundImageUrl:"string"==typeof r.backgroundImageUrl?r.backgroundImageUrl:void 0,objectNodes:t}}(i.questionGroup),p=h.objectNodes??[],u=h.backgroundImageUrl??"",b=m(i.content,"audioUrl")||m(i.content,"audio"),N=m(g[i.id],"objectId"),y=m(f[i.id],"objectId"),v=x[i.id]??null,j=o(v,!!N),k=w[i.id]??{score:null,maxScore:Number(i.total_points||0)},U=p.find(e=>e.id===y),q=p.find(e=>e.id===N);return r("div",{className:"rounded-2xl border border-slate-100 bg-slate-50/60 p-4 md:p-5",children:[e(s,{questionNumber:i.question_number,typeLabel:"Listen & choose object",status:j,score:k.score,maxScore:k.maxScore,audioKey:b}),e(n,{showCorrect:!0,showStudent:!!N&&!v}),e("div",{className:"mt-3 overflow-hidden rounded-xl border border-slate-200 bg-white",children:r("div",{className:"relative w-full",style:{aspectRatio:"16 / 9"},children:[u?e(a,{src:u,alt:"Scene",className:"absolute inset-0 h-full w-full object-contain",draggable:!1}):e("div",{className:"absolute inset-0 flex items-center justify-center text-sm text-slate-400",children:"No scene image"}),p.map(i=>{const s=i.id===y,n=i.id===N;let o="",c=null,m=i.label;return s&&n?(o="ring-4 ring-emerald-500 shadow-lg shadow-emerald-500/30",m=`${i.label} – Đáp án đúng (bạn đã chọn)`,c=e("span",{className:"absolute right-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-emerald-500 text-white shadow ring-2 ring-white",children:e(t,{className:"h-3.5 w-3.5",strokeWidth:3})})):s?(o="ring-4 ring-emerald-500 shadow-lg shadow-emerald-500/30",m=`${i.label} – Đáp án đúng`,c=e("span",{className:"absolute right-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-emerald-500 text-white shadow ring-2 ring-white",children:e(t,{className:"h-3.5 w-3.5",strokeWidth:3})})):n&&(o="ring-4 ring-red-500 shadow-lg shadow-red-500/30",m=`${i.label} – Lựa chọn của bạn (sai)`,c=e("span",{className:"absolute right-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white shadow ring-2 ring-white",children:e(l,{className:"h-3.5 w-3.5",strokeWidth:3})})),e("div",{className:"absolute",style:{left:`${i.x}%`,top:`${i.y}%`,width:`${i.width}%`,height:`${i.height}%`},"aria-label":m,children:r("div",{className:d("relative h-full w-full rounded-md transition",o,!o&&"rounded-md border border-dashed border-slate-300/80 bg-white/40"),children:[e("div",{className:d("h-full w-full overflow-hidden rounded-md",!o&&"opacity-60"),children:i.imageUrl?e(a,{src:i.imageUrl,alt:i.label,className:"h-full w-full object-contain",draggable:!1}):e("div",{className:"flex h-full w-full items-center justify-center bg-white/60 text-[10px] text-slate-500",children:i.label})}),c]})},i.id)})]})}),r("div","correct"===j?{className:"mt-3 rounded-lg border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm",children:[e("p",{className:"text-[11px] font-semibold uppercase tracking-wide text-emerald-700",children:"Đáp án đúng"}),e("p",{className:"mt-0.5 text-emerald-800",children:U?.label||y||"—"})]}:"unanswered"===j?{className:"mt-3 grid grid-cols-1 gap-2 text-sm sm:grid-cols-2",children:[r("div",{className:"rounded-lg border border-emerald-200 bg-emerald-50 px-3 py-2",children:[e("p",{className:"text-[11px] font-semibold uppercase tracking-wide text-emerald-700",children:"Đáp án đúng"}),e("p",{className:"mt-0.5 text-emerald-800",children:U?.label||y||"—"})]}),r("div",{className:"rounded-lg border border-slate-200 bg-white px-3 py-2",children:[e("p",{className:"text-[11px] font-semibold uppercase tracking-wide text-slate-500",children:"Đáp án của bạn"}),e("p",{className:"mt-0.5 italic text-slate-500",children:"Bạn chưa trả lời"})]})]}:{className:"mt-3 grid grid-cols-1 gap-2 text-sm sm:grid-cols-2",children:[r("div",{className:"rounded-lg border border-emerald-200 bg-emerald-50 px-3 py-2",children:[e("p",{className:"text-[11px] font-semibold uppercase tracking-wide text-emerald-700",children:"Đáp án đúng"}),e("p",{className:"mt-0.5 text-emerald-800",children:U?.label||y||"—"})]}),r("div",{className:"rounded-lg border border-red-200 bg-red-50 px-3 py-2",children:[e("p",{className:"text-[11px] font-semibold uppercase tracking-wide text-red-700",children:"Đáp án của bạn"}),e("p",{className:"mt-0.5 text-red-800",children:q?.label||N||"—"})]})]})]},i.id)})]})}
@@ -0,0 +1,25 @@
1
+ export interface ReviewDragObjectsQuestion {
2
+ id: string;
3
+ question_number: number;
4
+ content?: Record<string, unknown>;
5
+ questionGroup?: {
6
+ payload?: unknown;
7
+ } | null;
8
+ total_points?: string | number;
9
+ }
10
+ interface Props {
11
+ partNumber: number;
12
+ questionCount: number;
13
+ partName?: string;
14
+ instruction?: string;
15
+ questions: ReviewDragObjectsQuestion[];
16
+ studentAnswers: Record<string, unknown>;
17
+ rawCorrectAnswerMap: Record<string, unknown>;
18
+ isCorrectMap: Record<string, boolean | null>;
19
+ questionScoreMap: Record<string, {
20
+ score: number | null;
21
+ maxScore: number;
22
+ }>;
23
+ }
24
+ export declare function ReviewListenDragObjectsRenderer({ partNumber, questionCount, partName, questions, studentAnswers, rawCorrectAnswerMap, isCorrectMap, questionScoreMap, }: Props): import("react").JSX.Element;
25
+ export {};
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Check as t,X as a}from"lucide-react";import{ResolvedImage as i}from"../../../components/common/ResolvedImage";import l from"../../../components/themes/cambridge-yle/CambridgeYlePartBanner";import{cn as d}from"../../../shared/lib/utils";import{ReviewListeningHeader as s,ListeningLegend as n,getListeningStatus as o}from"./ReviewListeningHeader";function c(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function m(e,r){return c(e)&&"string"==typeof e[r]?e[r]:""}function u({zone:l,studentObject:s,correctObject:n,isStudentZone:o,isCorrectZone:c,isStudentCorrect:m}){let u,b="border-2 border-dashed border-slate-300/80 bg-white/30",p=null;return c&&o?(b=m?"border-2 border-emerald-500 bg-emerald-50/70 ring-2 ring-emerald-400/40":"border-2 border-red-500 bg-red-50/70 ring-2 ring-red-400/40",p=e("span",m?{className:"absolute right-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-emerald-500 text-white shadow ring-2 ring-white",children:e(t,{className:"h-3.5 w-3.5",strokeWidth:3})}:{className:"absolute right-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white shadow ring-2 ring-white",children:e(a,{className:"h-3.5 w-3.5",strokeWidth:3})}),u=s):o?(b="border-2 border-red-500 bg-red-50/70 ring-2 ring-red-400/40",p=e("span",{className:"absolute right-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white shadow ring-2 ring-white",children:e(a,{className:"h-3.5 w-3.5",strokeWidth:3})}),u=s):c&&(b="border-2 border-emerald-500 bg-emerald-50/70 ring-2 ring-emerald-400/40",p=e("span",{className:"absolute right-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-emerald-500 text-white shadow ring-2 ring-white",children:e(t,{className:"h-3.5 w-3.5",strokeWidth:3})}),u=n),e("div",{className:"absolute",style:{left:`${l.x}%`,top:`${l.y}%`,width:`${l.width}%`,height:`${l.height}%`},children:r("div",{className:d("relative flex h-full w-full items-center justify-center rounded-md",b),children:[u?.imageUrl?e(i,{src:u.imageUrl,alt:u.label,className:"h-full w-full object-contain p-1",draggable:!1}):e("span",{className:"px-1 text-center text-[10px] font-medium text-slate-500",children:l.label}),p]})})}export function ReviewListenDragObjectsRenderer({partNumber:t,questionCount:a,partName:b,questions:p,studentAnswers:h,rawCorrectAnswerMap:g,isCorrectMap:f,questionScoreMap:x}){return r("div",{className:"flex flex-col gap-6 pt-4",children:[e(l,{partNumber:t,questionCount:a,partName:b,isReviewMode:!0}),p.map(t=>{const a=function(e){let r=e?.payload;if("string"==typeof r)try{r=JSON.parse(r)}catch{return{}}if(!c(r))return{};const t=Array.isArray(r.objectNodes)?r.objectNodes.flatMap(e=>c(e)&&"string"==typeof e.id?[{id:e.id,label:"string"==typeof e.label?e.label:"",imageUrl:"string"==typeof e.imageUrl?e.imageUrl:""}]:[]):[],a=Array.isArray(r.dropZones)?r.dropZones.flatMap(e=>c(e)&&"string"==typeof e.id?[{id:e.id,label:"string"==typeof e.label?e.label:"",x:"number"==typeof e.x?e.x:0,y:"number"==typeof e.y?e.y:0,width:"number"==typeof e.width?e.width:0,height:"number"==typeof e.height?e.height:0}]:[]):[];return{backgroundImageUrl:"string"==typeof r.backgroundImageUrl?r.backgroundImageUrl:void 0,objectNodes:t,dropZones:a}}(t.questionGroup),l=a.objectNodes??[],b=a.dropZones??[],p=a.backgroundImageUrl??"",N=m(t.content,"audioUrl")||m(t.content,"audio"),w=m(h[t.id],"objectId"),y=m(h[t.id],"dropZoneId"),j=m(g[t.id],"objectId"),v=m(g[t.id],"dropZoneId"),k=!!w||!!y,S=f[t.id]??null,C=o(S,k),U=x[t.id]??{score:null,maxScore:Number(t.total_points||0)},Z=l.find(e=>e.id===w),I=l.find(e=>e.id===j),A=b.find(e=>e.id===y),q=b.find(e=>e.id===v);return r("div",{className:"rounded-2xl border border-slate-100 bg-slate-50/60 p-4 md:p-5",children:[e(s,{questionNumber:t.question_number,typeLabel:"Listen & drag object",status:C,score:U.score,maxScore:U.maxScore,audioKey:N}),e(n,{showCorrect:!0,showStudent:k&&!S}),e("div",{className:"mt-3 overflow-hidden rounded-xl border border-slate-200 bg-white",children:r("div",{className:"relative w-full",style:{aspectRatio:"16 / 9"},children:[p?e(i,{src:p,alt:"Scene",className:"absolute inset-0 h-full w-full object-contain",draggable:!1}):e("div",{className:"absolute inset-0 flex items-center justify-center text-sm text-slate-400",children:"No scene image"}),b.map(r=>e(u,{zone:r,studentObject:Z,correctObject:I,isStudentZone:r.id===y,isCorrectZone:r.id===v,isStudentCorrect:!!S},r.id))]})}),r("div","correct"===C?{className:"mt-3 rounded-lg border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm",children:[e("p",{className:"text-[11px] font-semibold uppercase tracking-wide text-emerald-700",children:"Vị trí đúng"}),r("p",{className:"mt-0.5 text-emerald-800",children:[e("span",{className:"font-medium",children:I?.label||j||"—"}),e("span",{className:"mx-1.5 text-emerald-500",children:"→"}),e("span",{children:q?.label||v||"—"})]})]}:{className:"mt-3 grid grid-cols-1 gap-2 text-sm sm:grid-cols-2",children:[r("div",{className:"rounded-lg border border-emerald-200 bg-emerald-50 px-3 py-2",children:[e("p",{className:"text-[11px] font-semibold uppercase tracking-wide text-emerald-700",children:"Vị trí đúng"}),r("p",{className:"mt-0.5 text-emerald-800",children:[e("span",{className:"font-medium",children:I?.label||j||"—"}),e("span",{className:"mx-1.5 text-emerald-500",children:"→"}),e("span",{children:q?.label||v||"—"})]})]}),r("div",{className:d("rounded-lg border px-3 py-2","incorrect"===C?"border-red-200 bg-red-50":"border-slate-200 bg-white"),children:[e("p",{className:d("text-[11px] font-semibold uppercase tracking-wide","incorrect"===C?"text-red-700":"text-slate-500"),children:"Vị trí của bạn"}),k?r("p",{className:"mt-0.5 text-red-800",children:[e("span",{className:"font-medium",children:Z?.label||w||"—"}),e("span",{className:"mx-1.5 text-red-500",children:"→"}),e("span",{children:A?.label||y||"—"})]}):e("p",{className:"mt-0.5 italic text-slate-500",children:"Bạn chưa trả lời"})]})]})]},t.id)})]})}
@@ -0,0 +1,44 @@
1
+ export type ListeningQuestionStatus = 'correct' | 'incorrect' | 'unanswered';
2
+ export declare function getListeningStatus(isCorrect: boolean | null | undefined, hasStudentAnswer: boolean): ListeningQuestionStatus;
3
+ interface ListeningStatusBadgeProps {
4
+ status: ListeningQuestionStatus;
5
+ }
6
+ /**
7
+ * Combined icon + textual correctness badge. Provides redundancy beyond colour
8
+ * for accessibility / quick scanning.
9
+ */
10
+ export declare function ListeningStatusBadge({ status }: ListeningStatusBadgeProps): import("react").JSX.Element;
11
+ interface ListeningScorePillProps {
12
+ score: number | null;
13
+ maxScore: number;
14
+ status: ListeningQuestionStatus;
15
+ }
16
+ export declare function ListeningScorePill({ score, maxScore, status }: ListeningScorePillProps): import("react").JSX.Element;
17
+ interface ListeningAudioBarProps {
18
+ audioKey: string;
19
+ }
20
+ /**
21
+ * Minimal audio bar tailored for listening prompts in review mode.
22
+ * Uses a speaker icon (not a microphone) so it reads as "audio to listen to",
23
+ * not "your recording".
24
+ */
25
+ export declare function ListeningAudioBar({ audioKey }: ListeningAudioBarProps): import("react").JSX.Element;
26
+ interface ReviewListeningHeaderProps {
27
+ questionNumber: number;
28
+ typeLabel: string;
29
+ status: ListeningQuestionStatus;
30
+ score: number | null;
31
+ maxScore: number;
32
+ audioKey?: string;
33
+ /** Optional title rendered below the type badge row. */
34
+ title?: string;
35
+ /** Optional extra slot rendered to the right of the type badge row. */
36
+ rightExtra?: React.ReactNode;
37
+ }
38
+ export declare function ReviewListeningHeader({ questionNumber, typeLabel, status, score, maxScore, audioKey, title, rightExtra, }: ReviewListeningHeaderProps): import("react").JSX.Element;
39
+ /** Small legend chip rendered above the visualization. Shown only when relevant. */
40
+ export declare function ListeningLegend({ showCorrect, showStudent, }: {
41
+ showCorrect?: boolean;
42
+ showStudent?: boolean;
43
+ }): import("react").JSX.Element;
44
+ export {};
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{Check as r,Volume2 as n,X as a,Pause as s,Play as l}from"lucide-react";import{useCallback as i,useEffect as d,useRef as c,useState as o}from"react";import{Badge as u}from"../../../components/ui/badge";import{cn as m}from"../../../shared/lib/utils";import{usePresignedFileUrl as x}from"../../../shared/lib/hooks/usePresignedFileUrl";export function getListeningStatus(e,t){return t?!0===e?"correct":!1===e?"incorrect":"unanswered":"unanswered"}export function ListeningStatusBadge({status:n}){return t("span","correct"===n?{className:"inline-flex items-center gap-1 rounded-full border border-emerald-200 bg-emerald-50 px-2 py-0.5 text-xs font-semibold text-emerald-700",children:[e("span",{className:"flex h-4 w-4 items-center justify-center rounded-full bg-emerald-500",children:e(r,{className:"h-2.5 w-2.5 text-white",strokeWidth:3})}),"Đúng"]}:"incorrect"===n?{className:"inline-flex items-center gap-1 rounded-full border border-red-200 bg-red-50 px-2 py-0.5 text-xs font-semibold text-red-700",children:[e("span",{className:"flex h-4 w-4 items-center justify-center rounded-full bg-red-500",children:e(a,{className:"h-2.5 w-2.5 text-white",strokeWidth:3})}),"Sai"]}:{className:"inline-flex items-center gap-1 rounded-full border border-slate-200 bg-slate-50 px-2 py-0.5 text-xs font-semibold text-slate-600",children:[e("span",{className:"flex h-4 w-4 items-center justify-center rounded-full bg-slate-400",children:e(a,{className:"h-2.5 w-2.5 text-white",strokeWidth:3})}),"Chưa trả lời"]})}export function ListeningScorePill({score:r,maxScore:n,status:a}){const s=null==r?"—":r;return t("div",{className:m("flex flex-shrink-0 items-baseline gap-1 rounded-full border px-3 py-1","correct"===a?"border-emerald-200 bg-emerald-50 text-emerald-700":"incorrect"===a?"border-red-200 bg-red-50 text-red-700":"border-slate-200 bg-slate-50 text-slate-600"),children:[e("span",{className:"text-sm font-bold leading-none",children:s}),t("span",{className:"text-xs font-medium opacity-80",children:["/ ",n," điểm"]})]})}function h(e){if(!isFinite(e))return"0:00";return`${Math.floor(e/60)}:${Math.floor(e%60).toString().padStart(2,"0")}`}export function ListeningAudioBar({audioKey:r}){const{previewUrl:a}=x(r),u=c(null),[m,p]=o(!1),[f,b]=o(0),[g,w]=o(0),N=i(()=>{const e=u.current;e&&(m?e.pause():e.play().catch(e=>{console.warn("Audio play request was interrupted:",e)}))},[m]),y=i(e=>{const t=u.current;if(!t)return;const r=Number(e.target.value);t.currentTime=r,b(r)},[]);return d(()=>{p(!1),b(0)},[a]),t("div",{className:"flex items-center gap-3 rounded-lg border border-slate-200 bg-white px-3 py-2",children:[a&&e("audio",{ref:u,src:a,onPlay:()=>p(!0),onPause:()=>p(!1),onEnded:()=>p(!1),onTimeUpdate:e=>b(e.currentTarget.currentTime),onLoadedMetadata:e=>w(e.currentTarget.duration),preload:"metadata"}),e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-blue-50 text-blue-600",children:e(n,{className:"h-4 w-4"})}),e("button",{type:"button",onClick:N,disabled:!a,className:"flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full bg-blue-600 text-white shadow-sm transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-40","aria-label":m?"Tạm dừng":"Phát âm thanh câu hỏi",children:m?e(s,{className:"h-4 w-4"}):e(l,{className:"ml-0.5 h-4 w-4"})}),e("span",{className:"w-10 text-xs tabular-nums text-slate-500",children:h(f)}),e("input",{type:"range",min:0,max:g||0,step:.01,value:f,onChange:y,className:"h-1 flex-1 cursor-pointer appearance-none rounded-full bg-slate-200 accent-blue-600","aria-label":"Tua âm thanh"}),e("span",{className:"w-10 text-right text-xs tabular-nums text-slate-500",children:h(g)})]})}export function ReviewListeningHeader({questionNumber:r,typeLabel:n,status:a,score:s,maxScore:l,audioKey:i,title:d,rightExtra:c}){return t("div",{className:"mb-3 space-y-3",children:[t("div",{className:"flex items-start gap-3",children:[e("span",{className:"mt-0.5 flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-sm font-bold text-white",children:r}),t("div",{className:"min-w-0 flex-1",children:[t("div",{className:"flex flex-wrap items-center gap-2",children:[e(u,{variant:"secondary",className:"bg-slate-100 text-slate-700 hover:bg-slate-100",children:n}),e(ListeningStatusBadge,{status:a}),c]}),d?e("p",{className:"mt-1.5 text-sm font-medium text-slate-700",children:d}):null]}),e(ListeningScorePill,{score:s,maxScore:l,status:a})]}),i?e(ListeningAudioBar,{audioKey:i}):null]})}export function ListeningLegend({showCorrect:r=!0,showStudent:n=!0}){return r||n?t("div",{className:"flex flex-wrap items-center gap-3 text-xs text-slate-600",children:[r?t("span",{className:"inline-flex items-center gap-1.5",children:[e("span",{className:"inline-block h-3 w-3 rounded-full border-2 border-emerald-500 bg-emerald-50"}),"Đáp án đúng"]}):null,n?t("span",{className:"inline-flex items-center gap-1.5",children:[e("span",{className:"inline-block h-3 w-3 rounded-full border-2 border-red-500 bg-red-50"}),"Đáp án của bạn"]}):null]}):null}
@@ -0,0 +1,17 @@
1
+ import type { SpeakingReviewItem } from './ReviewSpeakingRenderer.types';
2
+ interface ReviewSpeakingAIPanelProps {
3
+ question: SpeakingReviewItem;
4
+ }
5
+ /**
6
+ * AI review panel for the /student/results/:attemptId/review page.
7
+ *
8
+ * Layout mirrors SpeakingAIAssessmentPanel (speaking-review page) but uses the
9
+ * slate / blue-500 / emerald / amber / rose palette of the existing review page
10
+ * (instead of the #2874A6 tone used on speaking-review).
11
+ *
12
+ * Only renders for questions with a student audio recording. If there's no
13
+ * content score / feedback the right-hand card falls back to a neutral
14
+ * placeholder.
15
+ */
16
+ export declare function ReviewSpeakingAIPanel({ question }: ReviewSpeakingAIPanelProps): import("react").JSX.Element;
17
+ export {};
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as l}from"react/jsx-runtime";import{Activity as t,Award as a,Bot as r,Brain as s,MessageSquare as n,TrendingUp as c}from"lucide-react";import{cn as d}from"../../../shared/lib/utils";export function ReviewSpeakingAIPanel({question:t}){if(!t.studentAudioKey)return null;return null!=t.pronunciationScore||null!=t.accuracyScore||null!=t.fluencyScore||null!=t.prosodyScore||null!=t.completenessScore||!!t.recognizedText||null!=t.contentScore||t.feedbackEn?l("div",{className:"space-y-4",children:[t.classificationTitle?e(i,{title:t.classificationTitle,tag:t.classificationTag}):null,l("div",{className:"grid grid-cols-1 gap-4 lg:grid-cols-2",children:[e(o,{question:t}),null!=t.contentScore||t.feedbackEn?e(x,{contentScore:t.contentScore,feedback:t.feedbackEn}):e(m,{})]})]}):null}function i({title:t,tag:r}){const s={Platinum:{bg:"bg-slate-50 border-slate-200",text:"text-slate-800",iconBg:"bg-slate-200/60"},Gold:{bg:"bg-amber-50/60 border-amber-200/60",text:"text-amber-800",iconBg:"bg-amber-100"},Silver:{bg:"bg-gray-50 border-gray-200",text:"text-gray-700",iconBg:"bg-gray-200"},Bronze:{bg:"bg-orange-50/60 border-orange-200/60",text:"text-orange-800",iconBg:"bg-orange-100"}}[t]??{bg:"bg-blue-50/60 border-blue-100",text:"text-blue-800",iconBg:"bg-blue-100/60"};return l("div",{className:d("flex items-center gap-3 rounded-xl border px-4 py-2.5",s.bg),children:[e("div",{className:d("flex h-8 w-8 items-center justify-center rounded-full",s.iconBg),children:e(a,{className:d("h-4 w-4",s.text)})}),e("div",{className:"flex-1",children:l("div",{className:"flex items-center gap-2",children:[e("span",{className:d("text-sm font-black",s.text),children:t}),r?e("span",{className:"rounded-[4px] border border-gray-100/60 bg-white/70 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-gray-600 shadow-sm",children:r}):null]})}),e("span",{className:"text-[10px] font-semibold uppercase tracking-wider text-gray-400",children:"Phân loại năng lực"})]})}function o({question:a}){const r=[{label:"Phát âm",sublabel:"Pronunciation",score:a.pronunciationScore},{label:"Lưu loát",sublabel:"Fluency",score:a.fluencyScore},{label:"Ngữ điệu",sublabel:"Prosody",score:a.prosodyScore},{label:"Trọn vẹn",sublabel:"Completeness",score:a.completenessScore},{label:"Chính xác",sublabel:"Accuracy",score:a.accuracyScore}];return l("div",{className:"flex flex-col rounded-2xl border border-slate-200 bg-slate-50/60 shadow-[0_2px_8px_rgba(15,23,42,0.03)]",children:[l("div",{className:"flex items-center gap-3 px-4 py-3",children:[e(t,{className:"h-5 w-5 text-blue-600"}),e("h4",{className:"text-sm font-black tracking-wide text-slate-800",children:"Phân tích Khẩu hình"})]}),l("div",{className:"mx-4 mb-4 rounded-xl border border-slate-200 bg-white p-3.5 shadow-sm",children:[e("p",{className:"mb-1 text-[9px] font-bold uppercase tracking-wider text-blue-500",children:"AI Transcribed"}),l("p",{className:"text-sm font-medium italic leading-relaxed text-slate-800",children:["“",a.recognizedText||"Không rõ chữ / Không thể phiên âm","”"]})]}),e("div",{className:"grid grid-cols-2 gap-3 px-4 pb-4",children:r.map(l=>e(b,{...l},l.sublabel))})]})}function b({label:t,score:a}){const r=null!=a?Math.round(a):null,s=null==r?{text:"text-slate-400",bar:"bg-slate-200",bg:"bg-white border-slate-100"}:r>=80?{text:"text-emerald-600",bar:"bg-emerald-500",bg:"bg-emerald-50/50 border-emerald-100"}:r>=50?{text:"text-amber-600",bar:"bg-amber-400",bg:"bg-amber-50/50 border-amber-100"}:{text:"text-rose-600",bar:"bg-rose-400",bg:"bg-rose-50/50 border-rose-100"};return l("div",{className:d("flex flex-col gap-2 rounded-[14px] border p-3 shadow-[0_2px_10px_rgba(15,23,42,0.03)]",s.bg),children:[l("div",{className:"flex items-center justify-between",children:[e("span",{className:"text-xs font-black text-slate-700",children:t}),e("span",{className:d("text-lg font-black tracking-tight",s.text),children:null!=r?r:"N/A"})]}),e("div",{className:"h-1.5 w-full overflow-hidden rounded-full bg-slate-100",children:e("div",{className:d("h-full rounded-full transition-all duration-700 ease-out",s.bar),style:{width:`${Math.max(0,Math.min(100,r??0))}%`}})})]})}function x({contentScore:t,feedback:a}){return l("div",{className:"flex h-full flex-col rounded-2xl border border-slate-200 bg-slate-50/60 shadow-[0_2px_8px_rgba(15,23,42,0.03)]",children:[l("div",{className:"flex shrink-0 items-center gap-3 px-4 py-3",children:[e(s,{className:"h-5 w-5 text-blue-600"}),e("h4",{className:"text-sm font-black tracking-wide text-slate-800",children:"Phân tích Nội dung"})]}),null!=t?e("div",{className:"flex flex-col gap-3 px-4 pb-3",children:e(u,{label:"Phân tích Nội dung (LLM)",score:t,icon:e(c,{className:"h-3.5 w-3.5 text-blue-500"})})}):null,a?l("div",{className:"mx-4 mb-4 flex flex-1 flex-col rounded-xl border border-slate-200 border-l-[6px] border-l-blue-500 bg-white p-4 shadow-sm",children:[l("div",{className:"mb-2 flex shrink-0 items-center gap-2",children:[e("div",{className:"flex h-7 w-7 items-center justify-center rounded-full bg-blue-50 text-blue-600",children:e(r,{className:"h-4 w-4"})}),e("span",{className:"text-xs font-black uppercase tracking-wider text-blue-600",children:"Nhận xét AI"})]}),e("div",{className:"flex-1 overflow-y-auto pr-1",children:e("p",{className:"text-[15px] font-medium leading-relaxed text-slate-700",children:a})})]}):null]})}function u({label:t,score:a,icon:r}){const s=Math.round(a),n=s>=80?{bar:"bg-emerald-500",text:"text-emerald-600"}:s>=50?{bar:"bg-amber-400",text:"text-amber-600"}:{bar:"bg-rose-400",text:"text-rose-600"};return l("div",{className:"flex flex-col gap-1.5 rounded-xl border border-slate-200 bg-white p-2.5 shadow-sm",children:[l("div",{className:"flex items-center justify-between",children:[l("div",{className:"flex items-center gap-1.5",children:[e("span",{className:d("opacity-70",n.text),children:r}),e("span",{className:"text-xs font-bold text-slate-700",children:t})]}),e("span",{className:d("text-xs font-black",n.text),children:s})]}),e("div",{className:"h-1 w-full overflow-hidden rounded-full bg-slate-100",children:e("div",{className:d("h-full rounded-full transition-all duration-1000 ease-out",n.bar),style:{width:`${Math.max(0,Math.min(100,a))}%`}})})]})}function m(){return l("div",{className:"flex flex-col items-center justify-center gap-2 rounded-2xl border border-dashed border-slate-200 bg-slate-50/60 p-6 text-center",children:[e(n,{className:"h-5 w-5 text-slate-300"}),l("div",{children:[e("p",{className:"text-sm font-semibold text-slate-500",children:"Không chấm nội dung"}),e("p",{className:"mt-0.5 text-xs text-slate-400",children:"Dạng câu hỏi có kịch bản mẫu"})]})]})}
@@ -0,0 +1,39 @@
1
+ import type { SpeakingReviewItem } from './ReviewSpeakingRenderer.types';
2
+ export declare function PromptImage({ imageKey, alt }: {
3
+ imageKey: string;
4
+ alt: string;
5
+ }): import("react").JSX.Element;
6
+ export declare function ResolvedImageItem({ imageKey, alt, label, highlight, aspectRatio, }: {
7
+ imageKey: string;
8
+ alt: string;
9
+ label?: string;
10
+ highlight?: boolean;
11
+ aspectRatio?: string;
12
+ }): import("react").JSX.Element;
13
+ export declare function CompareImagesSection({ candidateImageUrl, examinerImageUrl, }: {
14
+ candidateImageUrl: string;
15
+ examinerImageUrl: string;
16
+ }): import("react").JSX.Element;
17
+ export declare function StoryImagesSection({ images }: {
18
+ images: string[];
19
+ }): import("react").JSX.Element;
20
+ export declare function InfoExchangeSection({ data, }: {
21
+ data: NonNullable<SpeakingReviewItem['infoExchange']>;
22
+ }): import("react").JSX.Element;
23
+ export declare function OddOneOutImagesSection({ groupImages, contentImages, activeId, }: {
24
+ groupImages: Array<{
25
+ id: string;
26
+ url: string;
27
+ label: string;
28
+ }>;
29
+ contentImages: string[];
30
+ activeId?: string;
31
+ }): import("react").JSX.Element;
32
+ export declare function AudioSection({ title, subtitle, icon, audioKey, emptyMessage, useListeningBar, }: {
33
+ title: string;
34
+ subtitle: string;
35
+ icon: React.ReactNode;
36
+ audioKey: string;
37
+ emptyMessage: string;
38
+ useListeningBar?: boolean;
39
+ }): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as a}from"react/jsx-runtime";import{ImageIcon as t,ImageOff as r}from"lucide-react";import{useState as l}from"react";import{ResolvedImage as s}from"../../../components/common/ResolvedImage";import{usePresignedFileUrl as i}from"../../../shared/lib/hooks/usePresignedFileUrl";import{cn as n}from"../../../shared/lib/utils";import{SpeakingResultAudioPlayer as d}from"../speaking/SpeakingResultAudioPlayer";import{ListeningAudioBar as o}from"./ReviewListeningHeader";export function PromptImage({imageKey:s,alt:n}){const{previewUrl:d,isLoading:o}=i(s||void 0),[c,m]=l(!1);return s?a("div",{className:"rounded-lg border border-gray-200 bg-white p-4 shadow-sm",children:[a("div",{className:"mb-3 flex items-center gap-2",children:[e(t,{className:"h-4 w-4 text-violet-500"}),e("p",{className:"text-sm font-semibold text-gray-700",children:"Question image"})]}),e("div",{className:"flex min-h-[200px] items-center justify-center rounded-lg bg-gray-50 p-4",children:o?e("span",{className:"text-sm text-gray-400",children:"Đang tải hình ảnh..."}):d&&!c?e("img",{src:d,alt:n,className:"max-h-[320px] w-auto rounded-lg object-contain",onError:()=>m(!0)}):a("div",{className:"flex flex-col items-center justify-center text-gray-400",children:[e(r,{className:"mb-2 h-8 w-8"}),e("span",{className:"text-sm",children:"Không thể tải hình ảnh"})]})})]}):null}export function ResolvedImageItem({imageKey:t,alt:s,label:d,highlight:o,aspectRatio:c="3.3"}){const{previewUrl:m,isLoading:g}=i(t||void 0),[h,x]=l(!1);return a("div",{className:n("relative w-full overflow-hidden rounded-xl border-2 bg-slate-50",o?"border-amber-400 ring-2 ring-amber-300":"border-slate-200"),style:{aspectRatio:c},children:[d&&e("span",{className:"absolute left-1/2 top-2 z-10 -translate-x-1/2 rounded-full bg-slate-800/80 px-3 py-0.5 text-xs font-bold uppercase tracking-wide text-white shadow backdrop-blur-sm",children:d}),g?e("div",{className:"flex h-full w-full items-center justify-center",children:e("span",{className:"text-xs text-gray-400",children:"Đang tải..."})}):m&&!h?e("img",{src:m,alt:s,className:"h-full w-full object-cover",onError:()=>x(!0)}):a("div",{className:"flex h-full w-full flex-col items-center justify-center text-gray-400",children:[e(r,{className:"mb-2 h-6 w-6"}),e("span",{className:"text-xs",children:"Không có ảnh"})]})]})}export function CompareImagesSection({candidateImageUrl:r,examinerImageUrl:l}){return r||l?a("div",{className:"space-y-3 rounded-lg border border-gray-200 bg-white p-4 shadow-sm",children:[a("div",{className:"mb-1 flex items-center gap-2",children:[e(t,{className:"h-4 w-4 text-violet-500"}),e("p",{className:"text-sm font-semibold text-gray-700",children:"Hình ảnh câu hỏi"})]}),e(ResolvedImageItem,{imageKey:r,alt:"Candidate's copy",label:"Candidate's copy"}),e(ResolvedImageItem,{imageKey:l,alt:"Examiner's copy",label:"Examiner's copy"})]}):null}export function StoryImagesSection({images:r}){return r&&0!==r.length?a("div",{className:"rounded-lg border border-gray-200 bg-white p-4 shadow-sm",children:[a("div",{className:"mb-3 flex items-center gap-2",children:[e(t,{className:"h-4 w-4 text-violet-500"}),a("p",{className:"text-sm font-semibold text-gray-700",children:["Hình ảnh câu chuyện (",r.length," ảnh)"]})]}),e("div",{className:"grid grid-cols-2 gap-3",children:r.map((a,t)=>e(ResolvedImageItem,{imageKey:a,alt:`Story image ${t+1}`,label:`Ảnh ${t+1}`,aspectRatio:"1/1"},a+t))})]}):null}export function InfoExchangeSection({data:r}){const{title:l,imageUrl:d,candidateFields:o,examinerFields:c}=r,m=o.length>0?o:c,{previewUrl:g,isLoading:h}=i(d||void 0);return a("div",{className:"space-y-3",children:[l?e("h3",{className:"text-sm font-bold text-gray-800",children:l}):null,d?e("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:h?e("div",{className:"flex h-[220px] items-center justify-center text-sm text-gray-400",children:"Loading image..."}):g?e(s,{src:d,alt:l||"Info exchange",className:"mx-auto h-auto max-h-[260px] w-auto max-w-full object-contain p-2"}):a("div",{className:"flex h-[220px] items-center justify-center text-sm text-gray-400",children:[e(t,{className:"mr-2 h-4 w-4"}),"Image unavailable"]})}):null,m.length>0?e("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:e("table",{className:"w-full text-sm",children:e("tbody",{children:m.map((t,r)=>a("tr",{className:n("border-b border-gray-100 transition-colors last:border-0 hover:bg-blue-50/50",r%2==0?"bg-white":"bg-gray-50/50"),children:[e("td",{className:"w-1/3 px-4 py-2.5 font-bold text-gray-700",children:t.key}),e("td",{className:"px-4 py-2.5",children:t.value||t.answer?e("span",{className:"font-medium text-gray-800",children:t.value||t.answer}):e("span",{className:"italic text-gray-300",children:"—"})})]},`${t.key}-${r}`))})})}):null]})}export function OddOneOutImagesSection({groupImages:r,contentImages:l,activeId:s}){const i=r.length>0?r:l.map((e,a)=>({id:String(a),url:e,label:String(a+1)}));return 0===i.length?null:a("div",{className:"rounded-lg border border-gray-200 bg-white p-4 shadow-sm",children:[a("div",{className:"mb-3 flex items-center gap-2",children:[e(t,{className:"h-4 w-4 text-violet-500"}),e("p",{className:"text-sm font-semibold text-gray-700",children:"Hình ảnh Odd One Out"})]}),e("div",{className:"grid grid-cols-2 gap-3",children:i.map(a=>e(ResolvedImageItem,{imageKey:a.url,alt:`Image ${a.label}`,label:a.id===s?`Ảnh ${a.label} ★`:`Ảnh ${a.label}`,highlight:a.id===s},a.id))})]})}export function AudioSection({title:t,subtitle:r,icon:l,audioKey:s,emptyMessage:i,useListeningBar:n=!1}){return a("div",{className:"audio-player-wrapper rounded-lg border border-gray-200 bg-white p-4 shadow-sm",children:[a("div",{className:"mb-3 flex items-center gap-2",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 text-gray-700",children:l}),a("div",{children:[e("p",{className:"text-sm font-semibold text-gray-800",children:t}),e("p",{className:"text-xs text-gray-500",children:r})]})]}),s?n?e(o,{audioKey:s}):e(d,{audioKey:s,variant:"admin"}):e("div",{className:"rounded-lg border border-dashed border-gray-300 bg-gray-50 px-4 py-3 text-sm text-gray-500",children:i})]})}
@@ -0,0 +1,5 @@
1
+ import { type SpeakingReviewItem } from './ReviewSpeakingRenderer.types';
2
+ export declare function ReviewSpeakingQuestionItem({ question, hidePerQuestionGrading, }: {
3
+ question: SpeakingReviewItem;
4
+ hidePerQuestionGrading?: boolean;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Check as t,Mic as a,Volume2 as s,X as l}from"lucide-react";import{Badge as n}from"../../../components/ui/badge";import{cn as i}from"../../../shared/lib/utils";import{ReviewSpeakingAIPanel as d}from"./ReviewSpeakingAIPanel";import{AudioSection as c,CompareImagesSection as o,InfoExchangeSection as m,OddOneOutImagesSection as u,PromptImage as p,StoryImagesSection as b}from"./ReviewSpeakingMedia";import{AI_PANEL_QUESTION_TYPES as h,LISTENING_AUDIO_BAR_QUESTION_TYPES as g,NO_RECORDING_QUESTION_TYPES as x,getAnswerStatus as f,getQuestionTypeLabel as N}from"./ReviewSpeakingRenderer.types";function y({question:t}){const a=f(t);return r("div",{className:i("rounded-lg border px-3 py-2 text-sm shadow-sm","correct"===a?"border-green-200 bg-green-50 text-green-700":"incorrect"===a?"border-red-200 bg-red-50 text-red-700":"border-blue-200 bg-blue-50 text-blue-700"),children:[e("p",{className:"text-[11px] font-semibold uppercase tracking-wide opacity-80",children:"Score"}),r("div",{className:"mt-1 flex items-baseline gap-1",children:[e("span",{className:"text-xl font-bold leading-none",children:t.score??"—"}),r("span",{className:"text-xs font-medium opacity-80",children:["/ ",t.maxScore]})]})]})}function w({status:r}){return e("div","correct"===r?{className:"flex h-6 w-6 items-center justify-center rounded-full bg-green-500",children:e(t,{className:"h-4 w-4 text-white",strokeWidth:3})}:"incorrect"===r?{className:"flex h-6 w-6 items-center justify-center rounded-full bg-red-500",children:e(l,{className:"h-4 w-4 text-white",strokeWidth:3})}:{className:"flex h-6 w-6 items-center justify-center rounded-full bg-blue-500",children:e(t,{className:"h-4 w-4 text-white",strokeWidth:3})})}function v({question:t}){return r("div",{className:"space-y-3",children:[e(d,{question:t}),t.expectedAnswers.length>0?r("div",{className:"rounded-lg border border-blue-100 bg-blue-50 p-3",children:[e("p",{className:"mb-2 text-xs font-semibold uppercase tracking-wide text-blue-700",children:"Expected answer"}),e("div",{className:"flex flex-wrap gap-2",children:t.expectedAnswers.map(r=>e("span",{className:"rounded-full border border-blue-200 bg-white px-3 py-1 text-sm font-medium text-blue-700",children:r},r))})]}):null,t.wordErrors.length>0?r("div",{className:"rounded-lg border border-amber-100 bg-amber-50 p-3",children:[e("p",{className:"mb-2 text-xs font-semibold uppercase tracking-wide text-amber-700",children:"Pronunciation notes"}),e("div",{className:"flex flex-wrap gap-2",children:t.wordErrors.map((e,t)=>r("span",{className:"rounded-full border border-amber-200 bg-white px-3 py-1 text-sm text-amber-800",children:[e.word,e.errorType?` · ${e.errorType}`:"","number"==typeof e.accuracyScore?` · ${e.accuracyScore}%`:""]},`${e.word}-${t}`))})]}):null]})}function I({label:t,value:a}){const s=a??0;return r("div",{className:"rounded-lg border border-gray-100 bg-white px-3 py-2 shadow-sm",children:[r("div",{className:"mb-1 flex items-center justify-between gap-3",children:[e("p",{className:"text-sm font-medium text-gray-700",children:t}),e("p",{className:"text-sm font-semibold text-emerald-600",children:null!=a?`${a}%`:"—"})]}),e("div",{className:"h-2 overflow-hidden rounded-full bg-emerald-100",children:e("div",{className:"h-full rounded-full bg-emerald-500 transition-all",style:{width:`${Math.max(0,Math.min(100,s))}%`}})})]})}function _({question:t}){return r("div",{className:"grid gap-3 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]",children:[r("div",{className:"rounded-xl border border-slate-200 bg-white p-4 shadow-sm",children:[e("div",{className:"mb-3 flex items-center gap-2",children:e("p",{className:"text-sm font-semibold text-slate-800",children:"Phân tích AI"})}),r("div",{className:"space-y-2.5",children:[e(I,{label:"Pronunciation",value:t.pronunciationScore}),e(I,{label:"Accuracy",value:t.accuracyScore}),e(I,{label:"Fluency",value:t.fluencyScore}),e(I,{label:"Prosody",value:t.prosodyScore}),e(I,{label:"Completeness",value:t.completenessScore}),e(I,{label:"Content",value:t.contentScore})]}),t.classificationTag?r("div",{className:"mt-3 rounded-lg border border-emerald-100 bg-emerald-50 px-3 py-2",children:[e("p",{className:"text-xs font-semibold uppercase tracking-wide text-emerald-700",children:"Speaking level"}),e("p",{className:"mt-1 text-sm font-medium text-emerald-700",children:t.classificationTag})]}):null]}),r("div",{className:"rounded-xl border border-sky-200 bg-white p-4 shadow-sm",children:[e("p",{className:"mb-3 text-sm font-semibold text-slate-800",children:"Phản hồi AI"}),r("div",{className:"space-y-3",children:[r("div",{className:"rounded-lg border border-slate-200 bg-slate-50 p-3",children:[e("p",{className:"mb-1 text-xs font-semibold uppercase tracking-wide text-slate-500",children:"Recognized text"}),e("p",{className:"text-sm leading-6 text-slate-700",children:t.recognizedText||"Chưa có transcript từ hệ thống."})]}),r("div",{className:"rounded-lg border border-sky-100 bg-sky-50 p-3",children:[e("p",{className:"mb-1 text-xs font-semibold uppercase tracking-wide text-sky-700",children:"Feedback"}),e("p",{className:"text-sm leading-6 text-slate-700",children:t.feedbackEn||"Chưa có nhận xét chi tiết từ hệ thống."})]}),t.expectedAnswers.length>0?r("div",{className:"rounded-lg border border-blue-100 bg-blue-50 p-3",children:[e("p",{className:"mb-2 text-xs font-semibold uppercase tracking-wide text-blue-700",children:"Expected answer"}),e("div",{className:"flex flex-wrap gap-2",children:t.expectedAnswers.map(r=>e("span",{className:"rounded-full border border-blue-200 bg-white px-3 py-1 text-sm font-medium text-blue-700",children:r},r))})]}):null,t.wordErrors.length>0?r("div",{className:"rounded-lg border border-amber-100 bg-amber-50 p-3",children:[e("p",{className:"mb-2 text-xs font-semibold uppercase tracking-wide text-amber-700",children:"Pronunciation notes"}),e("div",{className:"flex flex-wrap gap-2",children:t.wordErrors.map((e,t)=>r("span",{className:"rounded-full border border-amber-200 bg-white px-3 py-1 text-sm text-amber-800",children:[e.word,e.errorType?` · ${e.errorType}`:"","number"==typeof e.accuracyScore?` · ${e.accuracyScore}%`:""]},`${e.word}-${t}`))})]}):null]})]})]})}export function ReviewSpeakingQuestionItem({question:t,hidePerQuestionGrading:l=!1}){const i=f(t),d=h.has(t.questionType),I=x.has(t.questionType);return r("div",{className:"rounded-2xl border border-slate-200 bg-white p-4 shadow-[0_8px_24px_rgba(15,23,42,0.06)]",children:[r("div",{className:"mb-4 flex items-start gap-3",children:[e("span",{className:"mt-0.5 flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-sm font-bold text-white",children:t.questionNumber}),r("div",{className:"min-w-0 flex-1",children:[r("div",{className:"mb-2 flex flex-wrap items-center gap-2",children:[e(n,{variant:"secondary",className:"bg-slate-100 text-slate-700 hover:bg-slate-100",children:N(t.questionType)}),!l&&t.classificationTitle?e(n,{variant:"outline",className:"border-amber-200 bg-amber-50 text-amber-700 hover:bg-amber-50",children:t.classificationTitle}):null,!l&&e(w,{status:i})]}),t.promptText?"SPEAKING_CUE_CARD"===t.questionType?e("div",{className:"prose prose-sm max-w-none text-slate-800 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5 [&_li]:my-0.5 [&_p]:my-1",dangerouslySetInnerHTML:{__html:t.promptText}}):e("p",{className:"whitespace-pre-line text-base font-semibold leading-relaxed text-slate-800",children:t.promptText}):null]}),!l&&e(y,{question:t})]}),r("div",{className:"space-y-3",children:[t.promptAudioKey?e(c,{title:"Question audio",subtitle:"Nghe lại âm thanh câu hỏi",icon:e(s,{className:"h-4 w-4"}),audioKey:t.promptAudioKey,emptyMessage:"Không có audio câu hỏi cho mục này.",useListeningBar:g.has(t.questionType)}):null,!I&&e(c,{title:"Student recording",subtitle:"Phát lại câu trả lời đã nộp",icon:e(a,{className:"h-4 w-4"}),audioKey:t.studentAudioKey,emptyMessage:"Học sinh không gửi câu trả lời ghi âm cho câu hỏi này.",useListeningBar:g.has(t.questionType)}),"LISTEN_AND_SPEAK_COMPARE_IMAGES"===t.questionType&&t.compareImages?e(o,{candidateImageUrl:t.compareImages.candidateImageUrl,examinerImageUrl:t.compareImages.examinerImageUrl}):"LISTEN_AND_SPEAK_WITH_STORY_IMAGES"===t.questionType&&t.storyImages?e(b,{images:t.storyImages}):"LISTEN_AND_SPEAK_INFO_EXCHANGE"===t.questionType&&t.infoExchange?e(m,{data:t.infoExchange}):"LISTEN_AND_SPEAK_ODD_ONE_OUT"===t.questionType&&t.oddOneOutImages?e(u,{groupImages:t.oddOneOutImages.groupImages,contentImages:t.oddOneOutImages.contentImages,activeId:t.oddOneOutImages.activeId}):t.promptImageKey?e(p,{imageKey:t.promptImageKey,alt:`Question ${t.questionNumber}`}):null,l?null:e(d?v:_,{question:t})]})]})}