@schoolio/player 1.4.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -80,6 +80,12 @@ var QuizApiClient = class {
80
80
  `/api/external/question-chat/${questionId}/${childId}`
81
81
  );
82
82
  }
83
+ async getChatsByAttempt(attemptId) {
84
+ return this.request(
85
+ "GET",
86
+ `/api/external/quiz-attempts/${attemptId}/chats`
87
+ );
88
+ }
83
89
  async getTextToSpeech(text, voice = "nova") {
84
90
  const headers = {
85
91
  "Content-Type": "application/json"
@@ -662,7 +668,8 @@ function QuestionChatPanel({
662
668
  childId,
663
669
  parentId,
664
670
  lessonId,
665
- courseId
671
+ courseId,
672
+ answerResult
666
673
  }) {
667
674
  const [messages, setMessages] = useState2([]);
668
675
  const [inputValue, setInputValue] = useState2("");
@@ -672,6 +679,7 @@ function QuestionChatPanel({
672
679
  const [isListening, setIsListening] = useState2(false);
673
680
  const [speakingIndex, setSpeakingIndex] = useState2(null);
674
681
  const [audioReadyMap, setAudioReadyMap] = useState2(/* @__PURE__ */ new Map());
682
+ const [hasOfferedHelp, setHasOfferedHelp] = useState2(false);
675
683
  const messagesContainerRef = useRef2(null);
676
684
  const messagesEndRef = useRef2(null);
677
685
  const recognitionRef = useRef2(null);
@@ -791,6 +799,7 @@ function QuestionChatPanel({
791
799
  setMessages([]);
792
800
  setChatId(null);
793
801
  setInputValue("");
802
+ setHasOfferedHelp(false);
794
803
  const loadHistory = async () => {
795
804
  try {
796
805
  const history = await apiClient.getChatHistory(question.id, childId);
@@ -805,6 +814,20 @@ function QuestionChatPanel({
805
814
  };
806
815
  loadHistory();
807
816
  }, [question.id, childId, apiClient, preCacheAudio]);
817
+ useEffect2(() => {
818
+ if (answerResult?.wasIncorrect && !hasOfferedHelp) {
819
+ setHasOfferedHelp(true);
820
+ const selectedAnswerText = answerResult.selectedAnswer || "that answer";
821
+ const helpMessage = `Looks like you chose "${selectedAnswerText}" which was incorrect. Would you like me to help explain the correct answer?`;
822
+ const assistantMessage = {
823
+ role: "assistant",
824
+ content: helpMessage,
825
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
826
+ };
827
+ setMessages((prev) => [...prev, assistantMessage]);
828
+ preCacheAudio(helpMessage);
829
+ }
830
+ }, [answerResult, hasOfferedHelp, preCacheAudio]);
808
831
  const initializeChat = async () => {
809
832
  if (chatId) return chatId;
810
833
  try {
@@ -997,15 +1020,28 @@ var defaultStyles = {
997
1020
  container: {
998
1021
  fontFamily: "system-ui, -apple-system, sans-serif",
999
1022
  width: "100%",
1023
+ height: "100%",
1000
1024
  padding: "20px",
1001
1025
  backgroundColor: "#ffffff",
1002
1026
  borderRadius: "12px",
1003
- boxSizing: "border-box"
1027
+ boxSizing: "border-box",
1028
+ display: "flex",
1029
+ flexDirection: "column"
1004
1030
  },
1005
1031
  header: {
1006
1032
  marginBottom: "20px",
1007
1033
  borderBottom: "1px solid #e5e7eb",
1008
- paddingBottom: "16px"
1034
+ paddingBottom: "16px",
1035
+ display: "flex",
1036
+ flexDirection: "column"
1037
+ },
1038
+ headerTop: {
1039
+ display: "flex",
1040
+ justifyContent: "space-between",
1041
+ alignItems: "flex-start"
1042
+ },
1043
+ headerLeft: {
1044
+ flex: 1
1009
1045
  },
1010
1046
  title: {
1011
1047
  fontSize: "24px",
@@ -1017,7 +1053,8 @@ var defaultStyles = {
1017
1053
  color: "#6b7280"
1018
1054
  },
1019
1055
  progressBar: {
1020
- width: "100%",
1056
+ width: "50%",
1057
+ maxWidth: "300px",
1021
1058
  height: "8px",
1022
1059
  backgroundColor: "#e5e7eb",
1023
1060
  borderRadius: "4px",
@@ -1132,16 +1169,21 @@ var defaultStyles = {
1132
1169
  },
1133
1170
  mainLayout: {
1134
1171
  display: "flex",
1135
- gap: "24px"
1172
+ gap: "24px",
1173
+ flex: 1,
1174
+ minHeight: 0,
1175
+ alignItems: "stretch"
1136
1176
  },
1137
1177
  quizContent: {
1138
1178
  flex: 1,
1139
- minWidth: 0
1179
+ minWidth: 0,
1180
+ overflow: "auto"
1140
1181
  },
1141
1182
  chatPanel: {
1142
1183
  width: "320px",
1143
1184
  flexShrink: 0,
1144
- height: "460px"
1185
+ display: "flex",
1186
+ flexDirection: "column"
1145
1187
  },
1146
1188
  timer: {
1147
1189
  fontSize: "14px",
@@ -1349,6 +1391,16 @@ function QuizPlayer({
1349
1391
  const apiClient = useRef3(null);
1350
1392
  const timerRef = useRef3(null);
1351
1393
  const startTimeRef = useRef3(0);
1394
+ const onCompleteRef = useRef3(onComplete);
1395
+ const onErrorRef = useRef3(onError);
1396
+ const onProgressRef = useRef3(onProgress);
1397
+ const onGenerateMoreQuestionsRef = useRef3(onGenerateMoreQuestions);
1398
+ useEffect3(() => {
1399
+ onCompleteRef.current = onComplete;
1400
+ onErrorRef.current = onError;
1401
+ onProgressRef.current = onProgress;
1402
+ onGenerateMoreQuestionsRef.current = onGenerateMoreQuestions;
1403
+ });
1352
1404
  useEffect3(() => {
1353
1405
  apiClient.current = new QuizApiClient({ baseUrl: apiBaseUrl, authToken });
1354
1406
  }, [apiBaseUrl, authToken]);
@@ -1395,11 +1447,11 @@ function QuizPlayer({
1395
1447
  const message = err instanceof Error ? err.message : "Failed to load quiz";
1396
1448
  setError(message);
1397
1449
  setIsLoading(false);
1398
- onError?.(err instanceof Error ? err : new Error(message));
1450
+ onErrorRef.current?.(err instanceof Error ? err : new Error(message));
1399
1451
  }
1400
1452
  }
1401
1453
  initialize();
1402
- }, [quizId, lessonId, assignLessonId, courseId, childId, parentId, forceNewAttempt, onError]);
1454
+ }, [quizId, lessonId, assignLessonId, courseId, childId, parentId, forceNewAttempt]);
1403
1455
  useEffect3(() => {
1404
1456
  if (timerStarted && !isCompleted && !error) {
1405
1457
  startTimeRef.current = Date.now();
@@ -1425,14 +1477,14 @@ function QuizPlayer({
1425
1477
  const totalQuestions = allQuestions.length;
1426
1478
  const maxQuestions = 50;
1427
1479
  useEffect3(() => {
1428
- if (quiz && onProgress) {
1429
- onProgress({
1480
+ if (quiz && onProgressRef.current) {
1481
+ onProgressRef.current({
1430
1482
  currentQuestion: currentQuestionIndex + 1,
1431
1483
  totalQuestions,
1432
1484
  answeredQuestions: answers.size
1433
1485
  });
1434
1486
  }
1435
- }, [currentQuestionIndex, answers.size, quiz, onProgress, totalQuestions]);
1487
+ }, [currentQuestionIndex, answers.size, quiz, totalQuestions]);
1436
1488
  const currentQuestion = allQuestions[currentQuestionIndex];
1437
1489
  const handleAnswerChange = useCallback2((value) => {
1438
1490
  if (!currentQuestion) return;
@@ -1477,7 +1529,7 @@ function QuizPlayer({
1477
1529
  if (totalQuestions >= maxQuestions) return;
1478
1530
  setIsGeneratingExtra(true);
1479
1531
  try {
1480
- const result2 = await onGenerateMoreQuestions(attempt.id, totalQuestions);
1532
+ const result2 = await onGenerateMoreQuestionsRef.current(attempt.id, totalQuestions);
1481
1533
  if (result2.extraQuestions && result2.extraQuestions.length > 0) {
1482
1534
  const slotsAvailable = maxQuestions - totalQuestions;
1483
1535
  const questionsToAppend = result2.extraQuestions.slice(0, slotsAvailable);
@@ -1490,11 +1542,11 @@ function QuizPlayer({
1490
1542
  }
1491
1543
  } catch (err) {
1492
1544
  console.error("Failed to generate extra questions:", err);
1493
- onError?.(err instanceof Error ? err : new Error("Failed to generate extra questions"));
1545
+ onErrorRef.current?.(err instanceof Error ? err : new Error("Failed to generate extra questions"));
1494
1546
  } finally {
1495
1547
  setIsGeneratingExtra(false);
1496
1548
  }
1497
- }, [attempt, onGenerateMoreQuestions, isGeneratingExtra, totalQuestions, maxQuestions, onError]);
1549
+ }, [attempt, isGeneratingExtra, totalQuestions, maxQuestions]);
1498
1550
  const handleSubmit = useCallback2(async () => {
1499
1551
  if (!quiz || !attempt || !apiClient.current) return;
1500
1552
  setIsSubmitting(true);
@@ -1533,15 +1585,15 @@ function QuizPlayer({
1533
1585
  if (timerRef.current) {
1534
1586
  clearInterval(timerRef.current);
1535
1587
  }
1536
- onComplete?.(quizResult);
1588
+ onCompleteRef.current?.(quizResult);
1537
1589
  } catch (err) {
1538
1590
  const message = err instanceof Error ? err.message : "Failed to submit quiz";
1539
1591
  setError(message);
1540
- onError?.(err instanceof Error ? err : new Error(message));
1592
+ onErrorRef.current?.(err instanceof Error ? err : new Error(message));
1541
1593
  } finally {
1542
1594
  setIsSubmitting(false);
1543
1595
  }
1544
- }, [quiz, attempt, currentQuestion, answers, answersDetail, onComplete, onError, totalQuestions, timerStarted, elapsedSeconds]);
1596
+ }, [quiz, attempt, currentQuestion, answers, answersDetail, totalQuestions, timerStarted, elapsedSeconds]);
1545
1597
  const isExtraQuestion = currentQuestion && extraQuestions.some((q) => q.id === currentQuestion.id);
1546
1598
  const handleSkipQuestion = useCallback2(async (reason, comment) => {
1547
1599
  if (!currentQuestion || !apiClient.current || !attempt) return;
@@ -1577,7 +1629,7 @@ function QuizPlayer({
1577
1629
  timeSpentSeconds: elapsedSeconds
1578
1630
  };
1579
1631
  setResult(quizResult);
1580
- onComplete?.(quizResult);
1632
+ onCompleteRef.current?.(quizResult);
1581
1633
  } else if (currentQuestionIndex >= newTotalQuestions) {
1582
1634
  setCurrentQuestionIndex(newTotalQuestions - 1);
1583
1635
  }
@@ -1589,7 +1641,7 @@ function QuizPlayer({
1589
1641
  } finally {
1590
1642
  setIsSkipping(false);
1591
1643
  }
1592
- }, [currentQuestion, apiClient, attempt, quiz, childId, parentId, lessonId, courseId, assignLessonId, skippedQuestionIds, extraQuestions, currentQuestionIndex, elapsedSeconds, onComplete]);
1644
+ }, [currentQuestion, apiClient, attempt, quiz, childId, parentId, lessonId, courseId, assignLessonId, skippedQuestionIds, extraQuestions, currentQuestionIndex, elapsedSeconds]);
1593
1645
  const handleReportQuestion = useCallback2(async (comment) => {
1594
1646
  if (!currentQuestion || !apiClient.current || !attempt || !comment.trim()) return;
1595
1647
  setIsReporting(true);
@@ -1959,533 +2011,537 @@ function QuizPlayer({
1959
2011
  const remainingSlots = maxQuestions - totalQuestions;
1960
2012
  const questionsToAdd = Math.min(5, remainingSlots);
1961
2013
  const canAddMore = onGenerateMoreQuestions && remainingSlots > 0;
1962
- return /* @__PURE__ */ jsxs3("div", { className, style: defaultStyles.container, children: [
1963
- /* @__PURE__ */ jsxs3("div", { style: defaultStyles.header, children: [
1964
- /* @__PURE__ */ jsx3("div", { style: defaultStyles.title, children: quiz.title }),
1965
- /* @__PURE__ */ jsxs3("div", { style: defaultStyles.progress, children: [
1966
- "Question ",
1967
- currentQuestionIndex + 1,
1968
- " of ",
1969
- totalQuestions
2014
+ return /* @__PURE__ */ jsx3("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ jsxs3("div", { style: defaultStyles.mainLayout, children: [
2015
+ /* @__PURE__ */ jsxs3("div", { style: defaultStyles.quizContent, children: [
2016
+ /* @__PURE__ */ jsxs3("div", { style: defaultStyles.header, children: [
2017
+ /* @__PURE__ */ jsx3("div", { style: defaultStyles.title, children: quiz.title }),
2018
+ /* @__PURE__ */ jsxs3("div", { style: defaultStyles.progress, children: [
2019
+ "Question ",
2020
+ currentQuestionIndex + 1,
2021
+ " of ",
2022
+ totalQuestions
2023
+ ] }),
2024
+ /* @__PURE__ */ jsx3("div", { style: defaultStyles.progressBar, children: /* @__PURE__ */ jsx3("div", { style: { ...defaultStyles.progressFill, width: `${progressPercent}%` } }) })
1970
2025
  ] }),
1971
- /* @__PURE__ */ jsx3("div", { style: defaultStyles.progressBar, children: /* @__PURE__ */ jsx3("div", { style: { ...defaultStyles.progressFill, width: `${progressPercent}%` } }) })
1972
- ] }),
1973
- /* @__PURE__ */ jsxs3("div", { style: defaultStyles.mainLayout, children: [
1974
- /* @__PURE__ */ jsxs3("div", { style: defaultStyles.quizContent, children: [
1975
- /* @__PURE__ */ jsxs3("div", { style: { ...defaultStyles.question, position: "relative", paddingBottom: "40px" }, children: [
1976
- /* @__PURE__ */ jsx3("div", { style: defaultStyles.questionText, children: /* @__PURE__ */ jsx3(TextToSpeech, { text: currentQuestion.question, inline: true, size: "md" }) }),
1977
- isExtraQuestion && /* @__PURE__ */ jsxs3(
1978
- "button",
2026
+ /* @__PURE__ */ jsxs3("div", { style: { ...defaultStyles.question, position: "relative", paddingBottom: "40px" }, children: [
2027
+ /* @__PURE__ */ jsx3("div", { style: defaultStyles.questionText, children: /* @__PURE__ */ jsx3(TextToSpeech, { text: currentQuestion.question, inline: true, size: "md" }) }),
2028
+ isExtraQuestion && /* @__PURE__ */ jsxs3(
2029
+ "button",
2030
+ {
2031
+ onClick: () => setShowSkipModal(true),
2032
+ title: "Skip question",
2033
+ style: {
2034
+ position: "absolute",
2035
+ bottom: "8px",
2036
+ left: "0",
2037
+ background: "transparent",
2038
+ border: "none",
2039
+ cursor: "pointer",
2040
+ padding: "6px 10px",
2041
+ borderRadius: "6px",
2042
+ color: "#9ca3af",
2043
+ display: "flex",
2044
+ alignItems: "center",
2045
+ justifyContent: "center",
2046
+ gap: "4px",
2047
+ fontSize: "12px",
2048
+ opacity: 0.6,
2049
+ transition: "opacity 0.2s, color 0.2s"
2050
+ },
2051
+ onMouseEnter: (e) => {
2052
+ e.currentTarget.style.opacity = "1";
2053
+ e.currentTarget.style.color = "#6b7280";
2054
+ },
2055
+ onMouseLeave: (e) => {
2056
+ e.currentTarget.style.opacity = "0.6";
2057
+ e.currentTarget.style.color = "#9ca3af";
2058
+ },
2059
+ "data-testid": "button-skip-question",
2060
+ children: [
2061
+ /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2062
+ /* @__PURE__ */ jsx3("polygon", { points: "5 4 15 12 5 20 5 4" }),
2063
+ /* @__PURE__ */ jsx3("line", { x1: "19", y1: "5", x2: "19", y2: "19" })
2064
+ ] }),
2065
+ /* @__PURE__ */ jsx3("span", { children: "Skip" })
2066
+ ]
2067
+ }
2068
+ ),
2069
+ !isExtraQuestion && /* @__PURE__ */ jsxs3(
2070
+ "button",
2071
+ {
2072
+ onClick: () => setShowReportModal(true),
2073
+ title: "Report an issue with this question",
2074
+ style: {
2075
+ position: "absolute",
2076
+ bottom: "8px",
2077
+ left: "0",
2078
+ background: "transparent",
2079
+ border: "none",
2080
+ cursor: "pointer",
2081
+ padding: "6px 10px",
2082
+ borderRadius: "6px",
2083
+ color: "#9ca3af",
2084
+ display: "flex",
2085
+ alignItems: "center",
2086
+ justifyContent: "center",
2087
+ gap: "4px",
2088
+ fontSize: "12px",
2089
+ opacity: 0.6,
2090
+ transition: "opacity 0.2s, color 0.2s"
2091
+ },
2092
+ onMouseEnter: (e) => {
2093
+ e.currentTarget.style.opacity = "1";
2094
+ e.currentTarget.style.color = "#ef4444";
2095
+ },
2096
+ onMouseLeave: (e) => {
2097
+ e.currentTarget.style.opacity = "0.6";
2098
+ e.currentTarget.style.color = "#9ca3af";
2099
+ },
2100
+ "data-testid": "button-report-question",
2101
+ children: [
2102
+ /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2103
+ /* @__PURE__ */ jsx3("path", { d: "M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z" }),
2104
+ /* @__PURE__ */ jsx3("line", { x1: "4", y1: "22", x2: "4", y2: "15" })
2105
+ ] }),
2106
+ /* @__PURE__ */ jsx3("span", { children: "Report" })
2107
+ ]
2108
+ }
2109
+ ),
2110
+ (currentQuestion.type === "single" || currentQuestion.type === "true-false") && /* @__PURE__ */ jsx3("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
2111
+ const isSelected = selectedAnswer === option;
2112
+ const isCorrectOption = currentQuestion.correctAnswer === option;
2113
+ let optionStyle = { ...defaultStyles.option };
2114
+ if (showFeedback) {
2115
+ if (isCorrectOption) {
2116
+ optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
2117
+ } else if (isSelected && !isCorrectOption) {
2118
+ optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
2119
+ }
2120
+ } else if (isSelected) {
2121
+ optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
2122
+ }
2123
+ return /* @__PURE__ */ jsxs3(
2124
+ "div",
1979
2125
  {
1980
- onClick: () => setShowSkipModal(true),
1981
- title: "Skip question",
1982
2126
  style: {
1983
- position: "absolute",
1984
- bottom: "8px",
1985
- left: "0",
1986
- background: "transparent",
1987
- border: "none",
1988
- cursor: "pointer",
1989
- padding: "6px 10px",
1990
- borderRadius: "6px",
1991
- color: "#9ca3af",
2127
+ ...optionStyle,
2128
+ cursor: showFeedback ? "default" : "pointer",
1992
2129
  display: "flex",
1993
2130
  alignItems: "center",
1994
- justifyContent: "center",
1995
- gap: "4px",
1996
- fontSize: "12px",
1997
- opacity: 0.6,
1998
- transition: "opacity 0.2s, color 0.2s"
1999
- },
2000
- onMouseEnter: (e) => {
2001
- e.currentTarget.style.opacity = "1";
2002
- e.currentTarget.style.color = "#6b7280";
2131
+ gap: "8px"
2003
2132
  },
2004
- onMouseLeave: (e) => {
2005
- e.currentTarget.style.opacity = "0.6";
2006
- e.currentTarget.style.color = "#9ca3af";
2007
- },
2008
- "data-testid": "button-skip-question",
2133
+ onClick: () => !showFeedback && handleAnswerChange(option),
2009
2134
  children: [
2010
- /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2011
- /* @__PURE__ */ jsx3("polygon", { points: "5 4 15 12 5 20 5 4" }),
2012
- /* @__PURE__ */ jsx3("line", { x1: "19", y1: "5", x2: "19", y2: "19" })
2013
- ] }),
2014
- /* @__PURE__ */ jsx3("span", { children: "Skip" })
2135
+ /* @__PURE__ */ jsx3("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx3(TextToSpeech, { text: option, size: "sm" }) }),
2136
+ /* @__PURE__ */ jsx3("span", { style: { flex: 1 }, children: option })
2015
2137
  ]
2138
+ },
2139
+ idx
2140
+ );
2141
+ }) }),
2142
+ currentQuestion.type === "multiple" && /* @__PURE__ */ jsx3("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
2143
+ const selected = Array.isArray(selectedAnswer) && selectedAnswer.includes(option);
2144
+ const correctAnswers = Array.isArray(currentQuestion.correctAnswer) ? currentQuestion.correctAnswer : currentQuestion.correctAnswer ? [currentQuestion.correctAnswer] : [];
2145
+ const isCorrectOption = correctAnswers.includes(option);
2146
+ let optionStyle = { ...defaultStyles.option };
2147
+ if (showFeedback) {
2148
+ if (isCorrectOption) {
2149
+ optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
2150
+ } else if (selected && !isCorrectOption) {
2151
+ optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
2016
2152
  }
2017
- ),
2018
- !isExtraQuestion && /* @__PURE__ */ jsxs3(
2019
- "button",
2153
+ } else if (selected) {
2154
+ optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
2155
+ }
2156
+ return /* @__PURE__ */ jsxs3(
2157
+ "div",
2020
2158
  {
2021
- onClick: () => setShowReportModal(true),
2022
- title: "Report an issue with this question",
2023
2159
  style: {
2024
- position: "absolute",
2025
- bottom: "8px",
2026
- left: "0",
2027
- background: "transparent",
2028
- border: "none",
2029
- cursor: "pointer",
2030
- padding: "6px 10px",
2031
- borderRadius: "6px",
2032
- color: "#9ca3af",
2160
+ ...optionStyle,
2161
+ cursor: showFeedback ? "default" : "pointer",
2033
2162
  display: "flex",
2034
2163
  alignItems: "center",
2035
- justifyContent: "center",
2036
- gap: "4px",
2037
- fontSize: "12px",
2038
- opacity: 0.6,
2039
- transition: "opacity 0.2s, color 0.2s"
2164
+ gap: "8px"
2040
2165
  },
2041
- onMouseEnter: (e) => {
2042
- e.currentTarget.style.opacity = "1";
2043
- e.currentTarget.style.color = "#ef4444";
2044
- },
2045
- onMouseLeave: (e) => {
2046
- e.currentTarget.style.opacity = "0.6";
2047
- e.currentTarget.style.color = "#9ca3af";
2166
+ onClick: () => {
2167
+ if (showFeedback) return;
2168
+ const current = Array.isArray(selectedAnswer) ? selectedAnswer : [];
2169
+ if (selected) {
2170
+ handleAnswerChange(current.filter((o) => o !== option));
2171
+ } else {
2172
+ handleAnswerChange([...current, option]);
2173
+ }
2048
2174
  },
2049
- "data-testid": "button-report-question",
2050
2175
  children: [
2051
- /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2052
- /* @__PURE__ */ jsx3("path", { d: "M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z" }),
2053
- /* @__PURE__ */ jsx3("line", { x1: "4", y1: "22", x2: "4", y2: "15" })
2054
- ] }),
2055
- /* @__PURE__ */ jsx3("span", { children: "Report" })
2176
+ /* @__PURE__ */ jsx3("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx3(TextToSpeech, { text: option, size: "sm" }) }),
2177
+ /* @__PURE__ */ jsx3("span", { style: { flex: 1 }, children: option })
2056
2178
  ]
2179
+ },
2180
+ idx
2181
+ );
2182
+ }) }),
2183
+ (currentQuestion.type === "free" || currentQuestion.type === "essay") && /* @__PURE__ */ jsx3(
2184
+ "textarea",
2185
+ {
2186
+ style: { ...defaultStyles.input, minHeight: currentQuestion.type === "essay" ? "150px" : "60px" },
2187
+ value: selectedAnswer || "",
2188
+ onChange: (e) => handleAnswerChange(e.target.value),
2189
+ placeholder: "Type your answer here...",
2190
+ disabled: showFeedback
2191
+ }
2192
+ ),
2193
+ currentQuestion.type === "fill" && /* @__PURE__ */ jsx3("div", { style: defaultStyles.options, children: currentQuestion.blanks?.map((_, idx) => /* @__PURE__ */ jsx3(
2194
+ "input",
2195
+ {
2196
+ style: defaultStyles.input,
2197
+ value: (Array.isArray(selectedAnswer) ? selectedAnswer[idx] : "") || "",
2198
+ onChange: (e) => {
2199
+ const current = Array.isArray(selectedAnswer) ? [...selectedAnswer] : [];
2200
+ current[idx] = e.target.value;
2201
+ handleAnswerChange(current);
2202
+ },
2203
+ placeholder: `Blank ${idx + 1}`,
2204
+ disabled: showFeedback
2205
+ },
2206
+ idx
2207
+ )) }),
2208
+ showFeedback && currentAnswerDetail && /* @__PURE__ */ jsxs3("div", { style: {
2209
+ ...defaultStyles.feedback,
2210
+ ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackCorrect : defaultStyles.feedbackIncorrect
2211
+ }, children: [
2212
+ /* @__PURE__ */ jsx3("div", { style: {
2213
+ ...defaultStyles.feedbackTitle,
2214
+ ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackTitleCorrect : defaultStyles.feedbackTitleIncorrect
2215
+ }, children: currentAnswerDetail.isCorrect ? "\u2713 Correct!" : "\u2717 Incorrect" }),
2216
+ currentQuestion.explanation && /* @__PURE__ */ jsx3("div", { style: defaultStyles.feedbackExplanation, children: currentQuestion.explanation })
2217
+ ] })
2218
+ ] }),
2219
+ showSkipModal && /* @__PURE__ */ jsx3("div", { style: {
2220
+ position: "fixed",
2221
+ top: 0,
2222
+ left: 0,
2223
+ right: 0,
2224
+ bottom: 0,
2225
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
2226
+ display: "flex",
2227
+ alignItems: "center",
2228
+ justifyContent: "center",
2229
+ zIndex: 1e3
2230
+ }, children: /* @__PURE__ */ jsxs3("div", { style: {
2231
+ backgroundColor: "#ffffff",
2232
+ borderRadius: "12px",
2233
+ padding: "24px",
2234
+ maxWidth: "400px",
2235
+ width: "90%",
2236
+ boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)"
2237
+ }, children: [
2238
+ /* @__PURE__ */ jsx3("h3", { style: { margin: "0 0 8px 0", fontSize: "18px", fontWeight: "600", color: "#1f2937" }, children: "Skip Question" }),
2239
+ /* @__PURE__ */ jsx3("p", { style: { margin: "0 0 16px 0", fontSize: "14px", color: "#6b7280" }, children: "Help us improve by telling us why you're skipping this question." }),
2240
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "8px", marginBottom: "16px" }, children: [
2241
+ /* @__PURE__ */ jsx3(
2242
+ "button",
2243
+ {
2244
+ onClick: () => setSelectedSkipReason("question_issue"),
2245
+ disabled: isSkipping,
2246
+ style: {
2247
+ padding: "12px 16px",
2248
+ borderRadius: "8px",
2249
+ border: selectedSkipReason === "question_issue" ? "2px solid #8b5cf6" : "1px solid #e5e7eb",
2250
+ backgroundColor: selectedSkipReason === "question_issue" ? "#f5f3ff" : "#f9fafb",
2251
+ cursor: isSkipping ? "not-allowed" : "pointer",
2252
+ fontSize: "14px",
2253
+ fontWeight: "500",
2254
+ color: "#374151",
2255
+ textAlign: "left",
2256
+ opacity: isSkipping ? 0.6 : 1
2257
+ },
2258
+ "data-testid": "button-skip-reason-issue",
2259
+ children: "Question has an issue"
2057
2260
  }
2058
2261
  ),
2059
- (currentQuestion.type === "single" || currentQuestion.type === "true-false") && /* @__PURE__ */ jsx3("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
2060
- const isSelected = selectedAnswer === option;
2061
- const isCorrectOption = currentQuestion.correctAnswer === option;
2062
- let optionStyle = { ...defaultStyles.option };
2063
- if (showFeedback) {
2064
- if (isCorrectOption) {
2065
- optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
2066
- } else if (isSelected && !isCorrectOption) {
2067
- optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
2068
- }
2069
- } else if (isSelected) {
2070
- optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
2071
- }
2072
- return /* @__PURE__ */ jsxs3(
2073
- "div",
2074
- {
2075
- style: {
2076
- ...optionStyle,
2077
- cursor: showFeedback ? "default" : "pointer",
2078
- display: "flex",
2079
- alignItems: "center",
2080
- gap: "8px"
2081
- },
2082
- onClick: () => !showFeedback && handleAnswerChange(option),
2083
- children: [
2084
- /* @__PURE__ */ jsx3("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx3(TextToSpeech, { text: option, size: "sm" }) }),
2085
- /* @__PURE__ */ jsx3("span", { style: { flex: 1 }, children: option })
2086
- ]
2262
+ /* @__PURE__ */ jsx3(
2263
+ "button",
2264
+ {
2265
+ onClick: () => setSelectedSkipReason("dont_know"),
2266
+ disabled: isSkipping,
2267
+ style: {
2268
+ padding: "12px 16px",
2269
+ borderRadius: "8px",
2270
+ border: selectedSkipReason === "dont_know" ? "2px solid #8b5cf6" : "1px solid #e5e7eb",
2271
+ backgroundColor: selectedSkipReason === "dont_know" ? "#f5f3ff" : "#f9fafb",
2272
+ cursor: isSkipping ? "not-allowed" : "pointer",
2273
+ fontSize: "14px",
2274
+ fontWeight: "500",
2275
+ color: "#374151",
2276
+ textAlign: "left",
2277
+ opacity: isSkipping ? 0.6 : 1
2087
2278
  },
2088
- idx
2089
- );
2090
- }) }),
2091
- currentQuestion.type === "multiple" && /* @__PURE__ */ jsx3("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
2092
- const selected = Array.isArray(selectedAnswer) && selectedAnswer.includes(option);
2093
- const correctAnswers = Array.isArray(currentQuestion.correctAnswer) ? currentQuestion.correctAnswer : currentQuestion.correctAnswer ? [currentQuestion.correctAnswer] : [];
2094
- const isCorrectOption = correctAnswers.includes(option);
2095
- let optionStyle = { ...defaultStyles.option };
2096
- if (showFeedback) {
2097
- if (isCorrectOption) {
2098
- optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
2099
- } else if (selected && !isCorrectOption) {
2100
- optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
2101
- }
2102
- } else if (selected) {
2103
- optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
2279
+ "data-testid": "button-skip-reason-dont-know",
2280
+ children: "I don't know the answer"
2104
2281
  }
2105
- return /* @__PURE__ */ jsxs3(
2106
- "div",
2107
- {
2108
- style: {
2109
- ...optionStyle,
2110
- cursor: showFeedback ? "default" : "pointer",
2111
- display: "flex",
2112
- alignItems: "center",
2113
- gap: "8px"
2114
- },
2115
- onClick: () => {
2116
- if (showFeedback) return;
2117
- const current = Array.isArray(selectedAnswer) ? selectedAnswer : [];
2118
- if (selected) {
2119
- handleAnswerChange(current.filter((o) => o !== option));
2120
- } else {
2121
- handleAnswerChange([...current, option]);
2122
- }
2123
- },
2124
- children: [
2125
- /* @__PURE__ */ jsx3("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx3(TextToSpeech, { text: option, size: "sm" }) }),
2126
- /* @__PURE__ */ jsx3("span", { style: { flex: 1 }, children: option })
2127
- ]
2128
- },
2129
- idx
2130
- );
2131
- }) }),
2132
- (currentQuestion.type === "free" || currentQuestion.type === "essay") && /* @__PURE__ */ jsx3(
2282
+ )
2283
+ ] }),
2284
+ /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "16px" }, children: [
2285
+ /* @__PURE__ */ jsx3("label", { style: { display: "block", fontSize: "13px", fontWeight: "500", color: "#374151", marginBottom: "6px" }, children: "Additional details (optional)" }),
2286
+ /* @__PURE__ */ jsx3(
2133
2287
  "textarea",
2134
2288
  {
2135
- style: { ...defaultStyles.input, minHeight: currentQuestion.type === "essay" ? "150px" : "60px" },
2136
- value: selectedAnswer || "",
2137
- onChange: (e) => handleAnswerChange(e.target.value),
2138
- placeholder: "Type your answer here...",
2139
- disabled: showFeedback
2289
+ value: skipComment,
2290
+ onChange: (e) => setSkipComment(e.target.value.slice(0, 200)),
2291
+ placeholder: "Tell us more about the issue...",
2292
+ disabled: isSkipping,
2293
+ style: {
2294
+ width: "100%",
2295
+ minHeight: "80px",
2296
+ padding: "10px 12px",
2297
+ borderRadius: "8px",
2298
+ border: "1px solid #e5e7eb",
2299
+ fontSize: "14px",
2300
+ resize: "vertical",
2301
+ fontFamily: "inherit",
2302
+ boxSizing: "border-box"
2303
+ },
2304
+ "data-testid": "input-skip-comment"
2140
2305
  }
2141
2306
  ),
2142
- currentQuestion.type === "fill" && /* @__PURE__ */ jsx3("div", { style: defaultStyles.options, children: currentQuestion.blanks?.map((_, idx) => /* @__PURE__ */ jsx3(
2143
- "input",
2307
+ /* @__PURE__ */ jsxs3("div", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px", textAlign: "right" }, children: [
2308
+ skipComment.length,
2309
+ "/200"
2310
+ ] })
2311
+ ] }),
2312
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "10px" }, children: [
2313
+ /* @__PURE__ */ jsx3(
2314
+ "button",
2144
2315
  {
2145
- style: defaultStyles.input,
2146
- value: (Array.isArray(selectedAnswer) ? selectedAnswer[idx] : "") || "",
2147
- onChange: (e) => {
2148
- const current = Array.isArray(selectedAnswer) ? [...selectedAnswer] : [];
2149
- current[idx] = e.target.value;
2150
- handleAnswerChange(current);
2316
+ onClick: () => {
2317
+ setShowSkipModal(false);
2318
+ setSkipComment("");
2319
+ setSelectedSkipReason(null);
2151
2320
  },
2152
- placeholder: `Blank ${idx + 1}`,
2153
- disabled: showFeedback
2154
- },
2155
- idx
2156
- )) }),
2157
- showFeedback && currentAnswerDetail && /* @__PURE__ */ jsxs3("div", { style: {
2158
- ...defaultStyles.feedback,
2159
- ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackCorrect : defaultStyles.feedbackIncorrect
2160
- }, children: [
2161
- /* @__PURE__ */ jsx3("div", { style: {
2162
- ...defaultStyles.feedbackTitle,
2163
- ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackTitleCorrect : defaultStyles.feedbackTitleIncorrect
2164
- }, children: currentAnswerDetail.isCorrect ? "\u2713 Correct!" : "\u2717 Incorrect" }),
2165
- currentQuestion.explanation && /* @__PURE__ */ jsx3("div", { style: defaultStyles.feedbackExplanation, children: currentQuestion.explanation })
2321
+ style: {
2322
+ flex: 1,
2323
+ padding: "10px 16px",
2324
+ borderRadius: "8px",
2325
+ border: "1px solid #e5e7eb",
2326
+ backgroundColor: "#ffffff",
2327
+ cursor: "pointer",
2328
+ fontSize: "14px",
2329
+ fontWeight: "500",
2330
+ color: "#6b7280"
2331
+ },
2332
+ "data-testid": "button-skip-cancel",
2333
+ children: "Cancel"
2334
+ }
2335
+ ),
2336
+ /* @__PURE__ */ jsx3(
2337
+ "button",
2338
+ {
2339
+ onClick: () => selectedSkipReason && handleSkipQuestion(selectedSkipReason, skipComment),
2340
+ disabled: isSkipping || !selectedSkipReason,
2341
+ style: {
2342
+ flex: 1,
2343
+ padding: "10px 16px",
2344
+ borderRadius: "8px",
2345
+ border: "none",
2346
+ backgroundColor: selectedSkipReason ? "#8b5cf6" : "#d1d5db",
2347
+ cursor: isSkipping || !selectedSkipReason ? "not-allowed" : "pointer",
2348
+ fontSize: "14px",
2349
+ fontWeight: "500",
2350
+ color: "#ffffff",
2351
+ opacity: isSkipping ? 0.6 : 1
2352
+ },
2353
+ "data-testid": "button-skip-submit",
2354
+ children: isSkipping ? "Skipping..." : "Skip Question"
2355
+ }
2356
+ )
2357
+ ] })
2358
+ ] }) }),
2359
+ showReportModal && /* @__PURE__ */ jsx3("div", { style: {
2360
+ position: "fixed",
2361
+ top: 0,
2362
+ left: 0,
2363
+ right: 0,
2364
+ bottom: 0,
2365
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
2366
+ display: "flex",
2367
+ alignItems: "center",
2368
+ justifyContent: "center",
2369
+ zIndex: 1e3
2370
+ }, children: /* @__PURE__ */ jsxs3("div", { style: {
2371
+ backgroundColor: "#ffffff",
2372
+ borderRadius: "12px",
2373
+ padding: "24px",
2374
+ maxWidth: "400px",
2375
+ width: "90%",
2376
+ boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)"
2377
+ }, children: [
2378
+ /* @__PURE__ */ jsx3("h3", { style: { margin: "0 0 8px 0", fontSize: "18px", fontWeight: "600", color: "#1f2937" }, children: "Report Question" }),
2379
+ /* @__PURE__ */ jsx3("p", { style: { margin: "0 0 16px 0", fontSize: "14px", color: "#6b7280" }, children: "What's wrong with this question?" }),
2380
+ /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "16px" }, children: [
2381
+ /* @__PURE__ */ jsx3(
2382
+ "textarea",
2383
+ {
2384
+ value: reportComment,
2385
+ onChange: (e) => setReportComment(e.target.value.slice(0, 300)),
2386
+ placeholder: "Describe the issue with this question...",
2387
+ disabled: isReporting,
2388
+ style: {
2389
+ width: "100%",
2390
+ minHeight: "120px",
2391
+ padding: "10px 12px",
2392
+ borderRadius: "8px",
2393
+ border: "1px solid #e5e7eb",
2394
+ fontSize: "14px",
2395
+ resize: "vertical",
2396
+ fontFamily: "inherit",
2397
+ boxSizing: "border-box"
2398
+ },
2399
+ "data-testid": "input-report-comment"
2400
+ }
2401
+ ),
2402
+ /* @__PURE__ */ jsxs3("div", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px", textAlign: "right" }, children: [
2403
+ reportComment.length,
2404
+ "/300"
2166
2405
  ] })
2167
2406
  ] }),
2168
- showSkipModal && /* @__PURE__ */ jsx3("div", { style: {
2169
- position: "fixed",
2170
- top: 0,
2171
- left: 0,
2172
- right: 0,
2173
- bottom: 0,
2174
- backgroundColor: "rgba(0, 0, 0, 0.5)",
2175
- display: "flex",
2176
- alignItems: "center",
2177
- justifyContent: "center",
2178
- zIndex: 1e3
2179
- }, children: /* @__PURE__ */ jsxs3("div", { style: {
2180
- backgroundColor: "#ffffff",
2181
- borderRadius: "12px",
2182
- padding: "24px",
2183
- maxWidth: "400px",
2184
- width: "90%",
2185
- boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)"
2186
- }, children: [
2187
- /* @__PURE__ */ jsx3("h3", { style: { margin: "0 0 8px 0", fontSize: "18px", fontWeight: "600", color: "#1f2937" }, children: "Skip Question" }),
2188
- /* @__PURE__ */ jsx3("p", { style: { margin: "0 0 16px 0", fontSize: "14px", color: "#6b7280" }, children: "Help us improve by telling us why you're skipping this question." }),
2189
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "8px", marginBottom: "16px" }, children: [
2190
- /* @__PURE__ */ jsx3(
2191
- "button",
2192
- {
2193
- onClick: () => setSelectedSkipReason("question_issue"),
2194
- disabled: isSkipping,
2195
- style: {
2196
- padding: "12px 16px",
2197
- borderRadius: "8px",
2198
- border: selectedSkipReason === "question_issue" ? "2px solid #8b5cf6" : "1px solid #e5e7eb",
2199
- backgroundColor: selectedSkipReason === "question_issue" ? "#f5f3ff" : "#f9fafb",
2200
- cursor: isSkipping ? "not-allowed" : "pointer",
2201
- fontSize: "14px",
2202
- fontWeight: "500",
2203
- color: "#374151",
2204
- textAlign: "left",
2205
- opacity: isSkipping ? 0.6 : 1
2206
- },
2207
- "data-testid": "button-skip-reason-issue",
2208
- children: "Question has an issue"
2209
- }
2210
- ),
2211
- /* @__PURE__ */ jsx3(
2212
- "button",
2213
- {
2214
- onClick: () => setSelectedSkipReason("dont_know"),
2215
- disabled: isSkipping,
2216
- style: {
2217
- padding: "12px 16px",
2218
- borderRadius: "8px",
2219
- border: selectedSkipReason === "dont_know" ? "2px solid #8b5cf6" : "1px solid #e5e7eb",
2220
- backgroundColor: selectedSkipReason === "dont_know" ? "#f5f3ff" : "#f9fafb",
2221
- cursor: isSkipping ? "not-allowed" : "pointer",
2222
- fontSize: "14px",
2223
- fontWeight: "500",
2224
- color: "#374151",
2225
- textAlign: "left",
2226
- opacity: isSkipping ? 0.6 : 1
2227
- },
2228
- "data-testid": "button-skip-reason-dont-know",
2229
- children: "I don't know the answer"
2230
- }
2231
- )
2232
- ] }),
2233
- /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "16px" }, children: [
2234
- /* @__PURE__ */ jsx3("label", { style: { display: "block", fontSize: "13px", fontWeight: "500", color: "#374151", marginBottom: "6px" }, children: "Additional details (optional)" }),
2235
- /* @__PURE__ */ jsx3(
2236
- "textarea",
2237
- {
2238
- value: skipComment,
2239
- onChange: (e) => setSkipComment(e.target.value.slice(0, 200)),
2240
- placeholder: "Tell us more about the issue...",
2241
- disabled: isSkipping,
2242
- style: {
2243
- width: "100%",
2244
- minHeight: "80px",
2245
- padding: "10px 12px",
2246
- borderRadius: "8px",
2247
- border: "1px solid #e5e7eb",
2248
- fontSize: "14px",
2249
- resize: "vertical",
2250
- fontFamily: "inherit",
2251
- boxSizing: "border-box"
2252
- },
2253
- "data-testid": "input-skip-comment"
2254
- }
2255
- ),
2256
- /* @__PURE__ */ jsxs3("div", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px", textAlign: "right" }, children: [
2257
- skipComment.length,
2258
- "/200"
2259
- ] })
2260
- ] }),
2261
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "10px" }, children: [
2262
- /* @__PURE__ */ jsx3(
2263
- "button",
2264
- {
2265
- onClick: () => {
2266
- setShowSkipModal(false);
2267
- setSkipComment("");
2268
- setSelectedSkipReason(null);
2269
- },
2270
- style: {
2271
- flex: 1,
2272
- padding: "10px 16px",
2273
- borderRadius: "8px",
2274
- border: "1px solid #e5e7eb",
2275
- backgroundColor: "#ffffff",
2276
- cursor: "pointer",
2277
- fontSize: "14px",
2278
- fontWeight: "500",
2279
- color: "#6b7280"
2280
- },
2281
- "data-testid": "button-skip-cancel",
2282
- children: "Cancel"
2283
- }
2284
- ),
2285
- /* @__PURE__ */ jsx3(
2286
- "button",
2287
- {
2288
- onClick: () => selectedSkipReason && handleSkipQuestion(selectedSkipReason, skipComment),
2289
- disabled: isSkipping || !selectedSkipReason,
2290
- style: {
2291
- flex: 1,
2292
- padding: "10px 16px",
2293
- borderRadius: "8px",
2294
- border: "none",
2295
- backgroundColor: selectedSkipReason ? "#8b5cf6" : "#d1d5db",
2296
- cursor: isSkipping || !selectedSkipReason ? "not-allowed" : "pointer",
2297
- fontSize: "14px",
2298
- fontWeight: "500",
2299
- color: "#ffffff",
2300
- opacity: isSkipping ? 0.6 : 1
2301
- },
2302
- "data-testid": "button-skip-submit",
2303
- children: isSkipping ? "Skipping..." : "Skip Question"
2304
- }
2305
- )
2306
- ] })
2307
- ] }) }),
2308
- showReportModal && /* @__PURE__ */ jsx3("div", { style: {
2309
- position: "fixed",
2310
- top: 0,
2311
- left: 0,
2312
- right: 0,
2313
- bottom: 0,
2314
- backgroundColor: "rgba(0, 0, 0, 0.5)",
2315
- display: "flex",
2316
- alignItems: "center",
2317
- justifyContent: "center",
2318
- zIndex: 1e3
2319
- }, children: /* @__PURE__ */ jsxs3("div", { style: {
2320
- backgroundColor: "#ffffff",
2321
- borderRadius: "12px",
2322
- padding: "24px",
2323
- maxWidth: "400px",
2324
- width: "90%",
2325
- boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)"
2326
- }, children: [
2327
- /* @__PURE__ */ jsx3("h3", { style: { margin: "0 0 8px 0", fontSize: "18px", fontWeight: "600", color: "#1f2937" }, children: "Report Question" }),
2328
- /* @__PURE__ */ jsx3("p", { style: { margin: "0 0 16px 0", fontSize: "14px", color: "#6b7280" }, children: "What's wrong with this question?" }),
2329
- /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "16px" }, children: [
2330
- /* @__PURE__ */ jsx3(
2331
- "textarea",
2332
- {
2333
- value: reportComment,
2334
- onChange: (e) => setReportComment(e.target.value.slice(0, 300)),
2335
- placeholder: "Describe the issue with this question...",
2336
- disabled: isReporting,
2337
- style: {
2338
- width: "100%",
2339
- minHeight: "120px",
2340
- padding: "10px 12px",
2341
- borderRadius: "8px",
2342
- border: "1px solid #e5e7eb",
2343
- fontSize: "14px",
2344
- resize: "vertical",
2345
- fontFamily: "inherit",
2346
- boxSizing: "border-box"
2347
- },
2348
- "data-testid": "input-report-comment"
2349
- }
2350
- ),
2351
- /* @__PURE__ */ jsxs3("div", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px", textAlign: "right" }, children: [
2352
- reportComment.length,
2353
- "/300"
2354
- ] })
2355
- ] }),
2356
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "10px" }, children: [
2357
- /* @__PURE__ */ jsx3(
2358
- "button",
2359
- {
2360
- onClick: () => {
2361
- setShowReportModal(false);
2362
- setReportComment("");
2363
- },
2364
- style: {
2365
- flex: 1,
2366
- padding: "10px 16px",
2367
- borderRadius: "8px",
2368
- border: "1px solid #e5e7eb",
2369
- backgroundColor: "#ffffff",
2370
- cursor: "pointer",
2371
- fontSize: "14px",
2372
- fontWeight: "500",
2373
- color: "#6b7280"
2374
- },
2375
- "data-testid": "button-report-cancel",
2376
- children: "Cancel"
2377
- }
2378
- ),
2379
- /* @__PURE__ */ jsx3(
2380
- "button",
2381
- {
2382
- onClick: () => handleReportQuestion(reportComment),
2383
- disabled: isReporting || !reportComment.trim(),
2384
- style: {
2385
- flex: 1,
2386
- padding: "10px 16px",
2387
- borderRadius: "8px",
2388
- border: "none",
2389
- backgroundColor: reportComment.trim() ? "#ef4444" : "#d1d5db",
2390
- cursor: isReporting || !reportComment.trim() ? "not-allowed" : "pointer",
2391
- fontSize: "14px",
2392
- fontWeight: "500",
2393
- color: "#ffffff",
2394
- opacity: isReporting ? 0.6 : 1
2395
- },
2396
- "data-testid": "button-report-submit",
2397
- children: isReporting ? "Reporting..." : "Report"
2398
- }
2399
- )
2400
- ] })
2401
- ] }) }),
2402
- /* @__PURE__ */ jsxs3("div", { style: defaultStyles.buttonsColumn, children: [
2403
- showFeedback && isLastQuestion && canAddMore && /* @__PURE__ */ jsx3(
2407
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "10px" }, children: [
2408
+ /* @__PURE__ */ jsx3(
2404
2409
  "button",
2405
2410
  {
2411
+ onClick: () => {
2412
+ setShowReportModal(false);
2413
+ setReportComment("");
2414
+ },
2406
2415
  style: {
2407
- ...defaultStyles.buttonAddMore,
2408
- ...isGeneratingExtra ? defaultStyles.buttonAddMoreDisabled : {}
2416
+ flex: 1,
2417
+ padding: "10px 16px",
2418
+ borderRadius: "8px",
2419
+ border: "1px solid #e5e7eb",
2420
+ backgroundColor: "#ffffff",
2421
+ cursor: "pointer",
2422
+ fontSize: "14px",
2423
+ fontWeight: "500",
2424
+ color: "#6b7280"
2409
2425
  },
2410
- onClick: handleAddMoreQuestions,
2411
- disabled: isGeneratingExtra,
2412
- "data-testid": "button-add-more-questions",
2413
- children: isGeneratingExtra ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
2414
- /* @__PURE__ */ jsx3(Spinner, { size: 16, color: "#9ca3af" }),
2415
- "Generating Questions..."
2416
- ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
2417
- "+ Add ",
2418
- questionsToAdd,
2419
- " More Question",
2420
- questionsToAdd !== 1 ? "s" : ""
2421
- ] })
2426
+ "data-testid": "button-report-cancel",
2427
+ children: "Cancel"
2422
2428
  }
2423
2429
  ),
2424
- /* @__PURE__ */ jsx3("div", { style: { ...defaultStyles.buttons, justifyContent: "flex-end" }, children: showFeedback ? (
2425
- // After viewing feedback
2426
- isLastQuestion ? /* @__PURE__ */ jsx3(
2427
- "button",
2428
- {
2429
- style: {
2430
- ...defaultStyles.button,
2431
- ...isSubmitting || isGeneratingExtra ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
2432
- },
2433
- onClick: handleSubmit,
2434
- disabled: isSubmitting || isGeneratingExtra,
2435
- "data-testid": "button-submit-quiz",
2436
- children: isSubmitting ? /* @__PURE__ */ jsx3(Spinner, { size: 16, color: "#9ca3af" }) : "Submit Quiz"
2437
- }
2438
- ) : /* @__PURE__ */ jsx3(
2439
- "button",
2440
- {
2441
- style: {
2442
- ...defaultStyles.button,
2443
- ...defaultStyles.buttonPrimary
2444
- },
2445
- onClick: handleContinue,
2446
- "data-testid": "button-continue",
2447
- children: "Continue"
2448
- }
2449
- )
2450
- ) : (
2451
- // Before checking answer
2452
- /* @__PURE__ */ jsx3(
2453
- "button",
2454
- {
2455
- style: {
2456
- ...defaultStyles.button,
2457
- ...isNavigating || selectedAnswer === void 0 ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
2458
- },
2459
- onClick: handleCheckAnswer,
2460
- disabled: isNavigating || selectedAnswer === void 0,
2461
- "data-testid": "button-check-answer",
2462
- children: isNavigating ? /* @__PURE__ */ jsx3(Spinner, { size: 16, color: "#9ca3af" }) : "Check Answer"
2463
- }
2464
- )
2465
- ) })
2430
+ /* @__PURE__ */ jsx3(
2431
+ "button",
2432
+ {
2433
+ onClick: () => handleReportQuestion(reportComment),
2434
+ disabled: isReporting || !reportComment.trim(),
2435
+ style: {
2436
+ flex: 1,
2437
+ padding: "10px 16px",
2438
+ borderRadius: "8px",
2439
+ border: "none",
2440
+ backgroundColor: reportComment.trim() ? "#ef4444" : "#d1d5db",
2441
+ cursor: isReporting || !reportComment.trim() ? "not-allowed" : "pointer",
2442
+ fontSize: "14px",
2443
+ fontWeight: "500",
2444
+ color: "#ffffff",
2445
+ opacity: isReporting ? 0.6 : 1
2446
+ },
2447
+ "data-testid": "button-report-submit",
2448
+ children: isReporting ? "Reporting..." : "Report"
2449
+ }
2450
+ )
2466
2451
  ] })
2467
- ] }),
2468
- /* @__PURE__ */ jsx3("div", { style: defaultStyles.chatPanel, children: apiClient.current && /* @__PURE__ */ jsx3(
2469
- QuestionChatPanel,
2470
- {
2471
- apiClient: apiClient.current,
2472
- question: {
2473
- id: currentQuestion.id,
2474
- question: currentQuestion.question,
2475
- type: currentQuestion.type,
2476
- options: currentQuestion.options,
2477
- correctAnswer: currentQuestion.correctAnswer,
2478
- explanation: currentQuestion.explanation
2479
- },
2480
- quizId: quiz.id,
2481
- childId,
2482
- parentId,
2483
- lessonId,
2484
- courseId
2485
- }
2486
- ) })
2487
- ] })
2488
- ] });
2452
+ ] }) }),
2453
+ /* @__PURE__ */ jsxs3("div", { style: defaultStyles.buttonsColumn, children: [
2454
+ showFeedback && isLastQuestion && canAddMore && /* @__PURE__ */ jsx3(
2455
+ "button",
2456
+ {
2457
+ style: {
2458
+ ...defaultStyles.buttonAddMore,
2459
+ ...isGeneratingExtra ? defaultStyles.buttonAddMoreDisabled : {}
2460
+ },
2461
+ onClick: handleAddMoreQuestions,
2462
+ disabled: isGeneratingExtra,
2463
+ "data-testid": "button-add-more-questions",
2464
+ children: isGeneratingExtra ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
2465
+ /* @__PURE__ */ jsx3(Spinner, { size: 16, color: "#9ca3af" }),
2466
+ "Generating Questions..."
2467
+ ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
2468
+ "+ Add ",
2469
+ questionsToAdd,
2470
+ " More Question",
2471
+ questionsToAdd !== 1 ? "s" : ""
2472
+ ] })
2473
+ }
2474
+ ),
2475
+ /* @__PURE__ */ jsx3("div", { style: { ...defaultStyles.buttons, justifyContent: "flex-end" }, children: showFeedback ? (
2476
+ // After viewing feedback
2477
+ isLastQuestion ? /* @__PURE__ */ jsx3(
2478
+ "button",
2479
+ {
2480
+ style: {
2481
+ ...defaultStyles.button,
2482
+ ...isSubmitting || isGeneratingExtra ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
2483
+ },
2484
+ onClick: handleSubmit,
2485
+ disabled: isSubmitting || isGeneratingExtra,
2486
+ "data-testid": "button-submit-quiz",
2487
+ children: isSubmitting ? /* @__PURE__ */ jsx3(Spinner, { size: 16, color: "#9ca3af" }) : "Submit Quiz"
2488
+ }
2489
+ ) : /* @__PURE__ */ jsx3(
2490
+ "button",
2491
+ {
2492
+ style: {
2493
+ ...defaultStyles.button,
2494
+ ...defaultStyles.buttonPrimary
2495
+ },
2496
+ onClick: handleContinue,
2497
+ "data-testid": "button-continue",
2498
+ children: "Continue"
2499
+ }
2500
+ )
2501
+ ) : (
2502
+ // Before checking answer
2503
+ /* @__PURE__ */ jsx3(
2504
+ "button",
2505
+ {
2506
+ style: {
2507
+ ...defaultStyles.button,
2508
+ ...isNavigating || selectedAnswer === void 0 ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
2509
+ },
2510
+ onClick: handleCheckAnswer,
2511
+ disabled: isNavigating || selectedAnswer === void 0,
2512
+ "data-testid": "button-check-answer",
2513
+ children: isNavigating ? /* @__PURE__ */ jsx3(Spinner, { size: 16, color: "#9ca3af" }) : "Check Answer"
2514
+ }
2515
+ )
2516
+ ) })
2517
+ ] })
2518
+ ] }),
2519
+ /* @__PURE__ */ jsx3("div", { style: defaultStyles.chatPanel, children: apiClient.current && /* @__PURE__ */ jsx3(
2520
+ QuestionChatPanel,
2521
+ {
2522
+ apiClient: apiClient.current,
2523
+ question: {
2524
+ id: currentQuestion.id,
2525
+ question: currentQuestion.question,
2526
+ type: currentQuestion.type,
2527
+ options: currentQuestion.options,
2528
+ correctAnswer: currentQuestion.correctAnswer,
2529
+ explanation: currentQuestion.explanation
2530
+ },
2531
+ quizId: quiz.id,
2532
+ childId,
2533
+ parentId,
2534
+ lessonId,
2535
+ courseId,
2536
+ answerResult: showFeedback && currentAnswerDetail ? {
2537
+ wasIncorrect: !currentAnswerDetail.isCorrect,
2538
+ selectedAnswer: typeof selectedAnswer === "string" ? selectedAnswer : Array.isArray(selectedAnswer) ? selectedAnswer.join(", ") : void 0,
2539
+ correctAnswer: typeof currentQuestion.correctAnswer === "string" ? currentQuestion.correctAnswer : Array.isArray(currentQuestion.correctAnswer) ? currentQuestion.correctAnswer.join(", ") : void 0,
2540
+ explanation: currentQuestion.explanation
2541
+ } : void 0
2542
+ }
2543
+ ) })
2544
+ ] }) });
2489
2545
  }
2490
2546
 
2491
2547
  // src/AttemptViewer.tsx
@@ -2611,6 +2667,46 @@ var defaultStyles2 = {
2611
2667
  fontSize: "14px",
2612
2668
  color: "#581c87"
2613
2669
  },
2670
+ chatHistorySection: {
2671
+ marginTop: "12px",
2672
+ borderTop: "1px solid #e5e7eb",
2673
+ paddingTop: "12px"
2674
+ },
2675
+ chatToggleButton: {
2676
+ display: "flex",
2677
+ alignItems: "center",
2678
+ gap: "6px",
2679
+ padding: "6px 12px",
2680
+ backgroundColor: "#f3f4f6",
2681
+ border: "none",
2682
+ borderRadius: "6px",
2683
+ fontSize: "13px",
2684
+ color: "#6b7280",
2685
+ cursor: "pointer",
2686
+ fontWeight: "500"
2687
+ },
2688
+ chatMessages: {
2689
+ marginTop: "12px",
2690
+ display: "flex",
2691
+ flexDirection: "column",
2692
+ gap: "8px"
2693
+ },
2694
+ chatMessage: {
2695
+ padding: "8px 12px",
2696
+ borderRadius: "8px",
2697
+ fontSize: "13px",
2698
+ maxWidth: "85%"
2699
+ },
2700
+ chatMessageUser: {
2701
+ backgroundColor: "#6721b0",
2702
+ color: "#ffffff",
2703
+ alignSelf: "flex-end"
2704
+ },
2705
+ chatMessageAssistant: {
2706
+ backgroundColor: "#f3f4f6",
2707
+ color: "#111827",
2708
+ alignSelf: "flex-start"
2709
+ },
2614
2710
  loading: {
2615
2711
  textAlign: "center",
2616
2712
  padding: "40px 20px"
@@ -2668,11 +2764,14 @@ function AttemptViewer({
2668
2764
  onError,
2669
2765
  className,
2670
2766
  showExplanations = true,
2767
+ showConversation = false,
2671
2768
  title
2672
2769
  }) {
2673
2770
  const [attempt, setAttempt] = useState4(null);
2674
2771
  const [loading, setLoading] = useState4(true);
2675
2772
  const [error, setError] = useState4(null);
2773
+ const [chatHistories, setChatHistories] = useState4({});
2774
+ const [expandedChats, setExpandedChats] = useState4(/* @__PURE__ */ new Set());
2676
2775
  useEffect4(() => {
2677
2776
  const apiClient = new QuizApiClient({
2678
2777
  baseUrl: apiBaseUrl,
@@ -2690,6 +2789,14 @@ function AttemptViewer({
2690
2789
  }
2691
2790
  const data = await response.json();
2692
2791
  setAttempt(data);
2792
+ if (showConversation) {
2793
+ try {
2794
+ const chats = await apiClient.getChatsByAttempt(attemptId);
2795
+ setChatHistories(chats);
2796
+ } catch (chatErr) {
2797
+ console.error("Failed to load chat histories:", chatErr);
2798
+ }
2799
+ }
2693
2800
  } catch (err) {
2694
2801
  const errorMessage = err instanceof Error ? err.message : "Failed to load attempt";
2695
2802
  setError(errorMessage);
@@ -2699,7 +2806,18 @@ function AttemptViewer({
2699
2806
  }
2700
2807
  }
2701
2808
  fetchAttempt();
2702
- }, [attemptId, apiBaseUrl, authToken, onError]);
2809
+ }, [attemptId, apiBaseUrl, authToken, onError, showConversation]);
2810
+ const toggleChatExpanded = (questionId) => {
2811
+ setExpandedChats((prev) => {
2812
+ const newSet = new Set(prev);
2813
+ if (newSet.has(questionId)) {
2814
+ newSet.delete(questionId);
2815
+ } else {
2816
+ newSet.add(questionId);
2817
+ }
2818
+ return newSet;
2819
+ });
2820
+ };
2703
2821
  const handleRetry = () => {
2704
2822
  setLoading(true);
2705
2823
  setError(null);
@@ -2791,6 +2909,34 @@ function AttemptViewer({
2791
2909
  /* @__PURE__ */ jsx4("strong", { children: "Explanation:" }),
2792
2910
  " ",
2793
2911
  answer.explanation
2912
+ ] }),
2913
+ showConversation && chatHistories[answer.questionId] && chatHistories[answer.questionId].messages.length > 0 && /* @__PURE__ */ jsxs4("div", { style: defaultStyles2.chatHistorySection, children: [
2914
+ /* @__PURE__ */ jsxs4(
2915
+ "button",
2916
+ {
2917
+ style: defaultStyles2.chatToggleButton,
2918
+ onClick: () => toggleChatExpanded(answer.questionId),
2919
+ "data-testid": `button-toggle-chat-${answer.questionId}`,
2920
+ children: [
2921
+ /* @__PURE__ */ jsx4("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx4("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) }),
2922
+ expandedChats.has(answer.questionId) ? "Hide" : "View",
2923
+ " Chat History (",
2924
+ chatHistories[answer.questionId].messages.length,
2925
+ " messages)"
2926
+ ]
2927
+ }
2928
+ ),
2929
+ expandedChats.has(answer.questionId) && /* @__PURE__ */ jsx4("div", { style: defaultStyles2.chatMessages, children: chatHistories[answer.questionId].messages.map((msg, msgIndex) => /* @__PURE__ */ jsx4(
2930
+ "div",
2931
+ {
2932
+ style: {
2933
+ ...defaultStyles2.chatMessage,
2934
+ ...msg.role === "user" ? defaultStyles2.chatMessageUser : defaultStyles2.chatMessageAssistant
2935
+ },
2936
+ children: msg.content
2937
+ },
2938
+ msgIndex
2939
+ )) })
2794
2940
  ] })
2795
2941
  ]
2796
2942
  },