@schoolio/player 1.4.3 → 1.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -39,6 +39,10 @@ interface QuizAnswerDetail {
39
39
  isCorrect: boolean;
40
40
  explanation?: string;
41
41
  hint?: string;
42
+ items?: string[];
43
+ correctOrder?: number[];
44
+ leftItems?: string[];
45
+ rightItems?: string[];
42
46
  }
43
47
  type AttemptStatus = "in_progress" | "completed" | "abandoned";
44
48
  interface ExternalQuizAttempt {
package/dist/index.d.ts CHANGED
@@ -39,6 +39,10 @@ interface QuizAnswerDetail {
39
39
  isCorrect: boolean;
40
40
  explanation?: string;
41
41
  hint?: string;
42
+ items?: string[];
43
+ correctOrder?: number[];
44
+ leftItems?: string[];
45
+ rightItems?: string[];
42
46
  }
43
47
  type AttemptStatus = "in_progress" | "completed" | "abandoned";
44
48
  interface ExternalQuizAttempt {
package/dist/index.js CHANGED
@@ -191,15 +191,16 @@ function checkAnswer(question, selectedAnswer) {
191
191
  return { isCorrect, pointsEarned: isCorrect ? points : 0 };
192
192
  }
193
193
  case "essay":
194
- case "assessment":
195
194
  return { isCorrect: false, pointsEarned: 0 };
195
+ case "assessment":
196
+ return { isCorrect: true, pointsEarned: points };
196
197
  default:
197
198
  return { isCorrect: false, pointsEarned: 0 };
198
199
  }
199
200
  }
200
201
  function createAnswerDetail(question, selectedAnswer) {
201
202
  const { isCorrect, pointsEarned } = checkAnswer(question, selectedAnswer);
202
- return {
203
+ const detail = {
203
204
  questionId: question.id,
204
205
  questionText: question.question,
205
206
  questionType: question.type,
@@ -211,6 +212,14 @@ function createAnswerDetail(question, selectedAnswer) {
211
212
  explanation: question.explanation,
212
213
  hint: question.hint
213
214
  };
215
+ if (question.type === "sorting") {
216
+ detail.items = question.items;
217
+ detail.correctOrder = question.correctOrder;
218
+ } else if (question.type === "matrix") {
219
+ detail.leftItems = question.leftItems;
220
+ detail.rightItems = question.rightItems;
221
+ }
222
+ return detail;
214
223
  }
215
224
  function calculateScore(answers) {
216
225
  const totalPoints = answers.reduce((sum, a) => sum + a.points, 0);
@@ -1349,6 +1358,13 @@ var defaultStyles = {
1349
1358
  backgroundColor: "#fef2f2",
1350
1359
  borderColor: "#ef4444"
1351
1360
  },
1361
+ feedbackNeutral: {
1362
+ backgroundColor: "#f0f9ff",
1363
+ borderColor: "#0ea5e9"
1364
+ },
1365
+ feedbackTitleNeutral: {
1366
+ color: "#0369a1"
1367
+ },
1352
1368
  feedbackTitle: {
1353
1369
  fontSize: "16px",
1354
1370
  fontWeight: "600",
@@ -1369,6 +1385,290 @@ var defaultStyles = {
1369
1385
  lineHeight: "1.5"
1370
1386
  }
1371
1387
  };
1388
+ function SortingDragDrop({ items, currentOrder, correctOrder, showFeedback, onOrderChange }) {
1389
+ const [draggedIndex, setDraggedIndex] = (0, import_react3.useState)(null);
1390
+ const [dragOverIndex, setDragOverIndex] = (0, import_react3.useState)(null);
1391
+ const handleDragStart = (e, position) => {
1392
+ if (showFeedback) return;
1393
+ setDraggedIndex(position);
1394
+ e.dataTransfer.effectAllowed = "move";
1395
+ e.dataTransfer.setData("text/plain", position.toString());
1396
+ };
1397
+ const handleDragOver = (e, position) => {
1398
+ e.preventDefault();
1399
+ if (showFeedback) return;
1400
+ e.dataTransfer.dropEffect = "move";
1401
+ setDragOverIndex(position);
1402
+ };
1403
+ const handleDragLeave = () => {
1404
+ setDragOverIndex(null);
1405
+ };
1406
+ const handleDrop = (e, toPosition) => {
1407
+ e.preventDefault();
1408
+ if (showFeedback) return;
1409
+ const fromPosition = parseInt(e.dataTransfer.getData("text/plain"), 10);
1410
+ if (fromPosition !== toPosition) {
1411
+ const newOrder = [...currentOrder];
1412
+ const [movedItem] = newOrder.splice(fromPosition, 1);
1413
+ newOrder.splice(toPosition, 0, movedItem);
1414
+ onOrderChange(newOrder);
1415
+ }
1416
+ setDraggedIndex(null);
1417
+ setDragOverIndex(null);
1418
+ };
1419
+ const handleDragEnd = () => {
1420
+ setDraggedIndex(null);
1421
+ setDragOverIndex(null);
1422
+ };
1423
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.options, children: currentOrder.map((itemIndex, position) => {
1424
+ const isCorrectPosition = correctOrder?.[position] === itemIndex;
1425
+ const isDragging = draggedIndex === position;
1426
+ const isDragOver = dragOverIndex === position;
1427
+ let itemStyle = {
1428
+ ...defaultStyles.option,
1429
+ display: "flex",
1430
+ alignItems: "center",
1431
+ gap: "12px",
1432
+ cursor: showFeedback ? "default" : "grab",
1433
+ opacity: isDragging ? 0.5 : 1,
1434
+ transition: "all 0.2s ease",
1435
+ transform: isDragOver && !showFeedback ? "scale(1.02)" : "scale(1)"
1436
+ };
1437
+ if (showFeedback) {
1438
+ if (isCorrectPosition) {
1439
+ itemStyle = { ...itemStyle, ...defaultStyles.optionCorrect };
1440
+ } else {
1441
+ itemStyle = { ...itemStyle, ...defaultStyles.optionIncorrect };
1442
+ }
1443
+ } else if (isDragOver) {
1444
+ itemStyle = { ...itemStyle, borderColor: "#6366f1", backgroundColor: "#eef2ff" };
1445
+ }
1446
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1447
+ "div",
1448
+ {
1449
+ style: itemStyle,
1450
+ "data-testid": `sorting-item-${position}`,
1451
+ draggable: !showFeedback,
1452
+ onDragStart: (e) => handleDragStart(e, position),
1453
+ onDragOver: (e) => handleDragOver(e, position),
1454
+ onDragLeave: handleDragLeave,
1455
+ onDrop: (e) => handleDrop(e, position),
1456
+ onDragEnd: handleDragEnd,
1457
+ children: [
1458
+ !showFeedback && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
1459
+ cursor: "grab",
1460
+ padding: "4px",
1461
+ display: "flex",
1462
+ alignItems: "center",
1463
+ color: "#9ca3af"
1464
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1465
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "9", cy: "5", r: "1" }),
1466
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "9", cy: "12", r: "1" }),
1467
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "9", cy: "19", r: "1" }),
1468
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "15", cy: "5", r: "1" }),
1469
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "15", cy: "12", r: "1" }),
1470
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "15", cy: "19", r: "1" })
1471
+ ] }) }),
1472
+ showFeedback && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
1473
+ display: "flex",
1474
+ alignItems: "center",
1475
+ color: isCorrectPosition ? "#22c55e" : "#ef4444"
1476
+ }, children: isCorrectPosition ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1477
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14" }),
1478
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "22 4 12 14.01 9 11.01" })
1479
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1480
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
1481
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "15", y1: "9", x2: "9", y2: "15" }),
1482
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "9", y1: "9", x2: "15", y2: "15" })
1483
+ ] }) }),
1484
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { flex: 1 }, children: items[itemIndex] }),
1485
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TextToSpeech, { text: items[itemIndex], size: "sm" }) })
1486
+ ]
1487
+ },
1488
+ itemIndex
1489
+ );
1490
+ }) });
1491
+ }
1492
+ function MatchingDragDrop({ leftItems, rightItems, currentMatches, correctMatches, showFeedback, onMatchChange }) {
1493
+ const [draggedItem, setDraggedItem] = (0, import_react3.useState)(null);
1494
+ const [dragOverLeft, setDragOverLeft] = (0, import_react3.useState)(null);
1495
+ const matchedRightItems = Object.values(currentMatches);
1496
+ const unmatchedRightItems = rightItems.filter((item) => !matchedRightItems.includes(item));
1497
+ const handleDragStart = (e, rightItem) => {
1498
+ if (showFeedback) return;
1499
+ setDraggedItem(rightItem);
1500
+ e.dataTransfer.effectAllowed = "move";
1501
+ e.dataTransfer.setData("text/plain", rightItem);
1502
+ };
1503
+ const handleDragOver = (e, leftItem) => {
1504
+ e.preventDefault();
1505
+ if (showFeedback) return;
1506
+ e.dataTransfer.dropEffect = "move";
1507
+ setDragOverLeft(leftItem);
1508
+ };
1509
+ const handleDragLeave = () => {
1510
+ setDragOverLeft(null);
1511
+ };
1512
+ const handleDrop = (e, leftItem) => {
1513
+ e.preventDefault();
1514
+ if (showFeedback) return;
1515
+ const rightItem = e.dataTransfer.getData("text/plain");
1516
+ if (rightItem) {
1517
+ const newMatches = { ...currentMatches };
1518
+ Object.keys(newMatches).forEach((key) => {
1519
+ if (newMatches[key] === rightItem) {
1520
+ delete newMatches[key];
1521
+ }
1522
+ });
1523
+ newMatches[leftItem] = rightItem;
1524
+ onMatchChange(newMatches);
1525
+ }
1526
+ setDraggedItem(null);
1527
+ setDragOverLeft(null);
1528
+ };
1529
+ const handleDragEnd = () => {
1530
+ setDraggedItem(null);
1531
+ setDragOverLeft(null);
1532
+ };
1533
+ const handleClearMatch = (leftItem) => {
1534
+ if (showFeedback) return;
1535
+ const newMatches = { ...currentMatches };
1536
+ delete newMatches[leftItem];
1537
+ onMatchChange(newMatches);
1538
+ };
1539
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", gap: "24px", flexWrap: "wrap" }, children: [
1540
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { flex: 1, minWidth: "200px", display: "flex", flexDirection: "column", gap: "8px" }, children: [
1541
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { fontSize: "14px", fontWeight: "600", color: "#6b7280", marginBottom: "4px" }, children: "Match these items:" }),
1542
+ leftItems.map((leftItem, idx) => {
1543
+ const matchedRight = currentMatches[leftItem];
1544
+ const correctMatch = correctMatches?.[leftItem];
1545
+ const isCorrect = matchedRight === correctMatch;
1546
+ const isDragOver = dragOverLeft === leftItem;
1547
+ let rowStyle = {
1548
+ display: "flex",
1549
+ alignItems: "center",
1550
+ gap: "12px",
1551
+ padding: "12px 16px",
1552
+ border: "2px dashed #e5e7eb",
1553
+ borderRadius: "8px",
1554
+ backgroundColor: "#ffffff",
1555
+ minHeight: "56px",
1556
+ transition: "all 0.2s ease"
1557
+ };
1558
+ if (showFeedback) {
1559
+ rowStyle.borderStyle = "solid";
1560
+ if (isCorrect) {
1561
+ rowStyle = { ...rowStyle, borderColor: "#22c55e", backgroundColor: "#f0fdf4" };
1562
+ } else {
1563
+ rowStyle = { ...rowStyle, borderColor: "#ef4444", backgroundColor: "#fef2f2" };
1564
+ }
1565
+ } else if (isDragOver) {
1566
+ rowStyle = { ...rowStyle, borderColor: "#6366f1", backgroundColor: "#eef2ff", borderStyle: "solid" };
1567
+ } else if (matchedRight) {
1568
+ rowStyle = { ...rowStyle, borderStyle: "solid", borderColor: "#22c55e" };
1569
+ }
1570
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1571
+ "div",
1572
+ {
1573
+ style: rowStyle,
1574
+ "data-testid": `matrix-row-${idx}`,
1575
+ onDragOver: (e) => handleDragOver(e, leftItem),
1576
+ onDragLeave: handleDragLeave,
1577
+ onDrop: (e) => handleDrop(e, leftItem),
1578
+ children: [
1579
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { flex: 1, fontWeight: "500" }, children: leftItem }),
1580
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { color: "#6b7280" }, children: "\u2192" }),
1581
+ matchedRight ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
1582
+ display: "flex",
1583
+ alignItems: "center",
1584
+ gap: "8px",
1585
+ padding: "6px 12px",
1586
+ backgroundColor: showFeedback ? isCorrect ? "#dcfce7" : "#fee2e2" : "#e0e7ff",
1587
+ borderRadius: "6px",
1588
+ fontSize: "14px"
1589
+ }, children: [
1590
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: matchedRight }),
1591
+ !showFeedback && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1592
+ "button",
1593
+ {
1594
+ onClick: () => handleClearMatch(leftItem),
1595
+ style: {
1596
+ background: "none",
1597
+ border: "none",
1598
+ cursor: "pointer",
1599
+ padding: "2px",
1600
+ display: "flex",
1601
+ color: "#6b7280"
1602
+ },
1603
+ "aria-label": "Remove match",
1604
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1605
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1606
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1607
+ ] })
1608
+ }
1609
+ ),
1610
+ showFeedback && (isCorrect ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#22c55e", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "20 6 9 17 4 12" }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#ef4444", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1611
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1612
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1613
+ ] }))
1614
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { color: "#9ca3af", fontSize: "14px", fontStyle: "italic" }, children: "Drop here" }),
1615
+ showFeedback && !isCorrect && correctMatch && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { color: "#166534", fontSize: "13px", marginLeft: "8px" }, children: [
1616
+ "(Correct: ",
1617
+ correctMatch,
1618
+ ")"
1619
+ ] })
1620
+ ]
1621
+ },
1622
+ idx
1623
+ );
1624
+ })
1625
+ ] }),
1626
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { flex: 1, minWidth: "200px", display: "flex", flexDirection: "column", gap: "8px" }, children: [
1627
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { fontSize: "14px", fontWeight: "600", color: "#6b7280", marginBottom: "4px" }, children: "Drag to match:" }),
1628
+ unmatchedRightItems.length > 0 ? unmatchedRightItems.map((rightItem, idx) => {
1629
+ const isDragging = draggedItem === rightItem;
1630
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1631
+ "div",
1632
+ {
1633
+ style: {
1634
+ padding: "12px 16px",
1635
+ border: "2px solid #e5e7eb",
1636
+ borderRadius: "8px",
1637
+ backgroundColor: "#ffffff",
1638
+ cursor: showFeedback ? "default" : "grab",
1639
+ opacity: isDragging ? 0.5 : 1,
1640
+ display: "flex",
1641
+ alignItems: "center",
1642
+ gap: "8px",
1643
+ transition: "all 0.2s ease"
1644
+ },
1645
+ draggable: !showFeedback,
1646
+ onDragStart: (e) => handleDragStart(e, rightItem),
1647
+ onDragEnd: handleDragEnd,
1648
+ "data-testid": `draggable-right-${idx}`,
1649
+ children: [
1650
+ !showFeedback && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { color: "#9ca3af", display: "flex" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1651
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "9", cy: "5", r: "1" }),
1652
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "9", cy: "12", r: "1" }),
1653
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "9", cy: "19", r: "1" }),
1654
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "15", cy: "5", r: "1" }),
1655
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "15", cy: "12", r: "1" }),
1656
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "15", cy: "19", r: "1" })
1657
+ ] }) }),
1658
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { flex: 1 }, children: rightItem })
1659
+ ]
1660
+ },
1661
+ idx
1662
+ );
1663
+ }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
1664
+ padding: "16px",
1665
+ textAlign: "center",
1666
+ color: "#22c55e",
1667
+ fontSize: "14px"
1668
+ }, children: "All items matched!" })
1669
+ ] })
1670
+ ] });
1671
+ }
1372
1672
  function Spinner({ size = 16, color = "#ffffff" }) {
1373
1673
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1374
1674
  "span",
@@ -2245,14 +2545,123 @@ function QuizPlayer({
2245
2545
  },
2246
2546
  idx
2247
2547
  )) }),
2548
+ currentQuestion.type === "sorting" && currentQuestion.items && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2549
+ SortingDragDrop,
2550
+ {
2551
+ items: currentQuestion.items,
2552
+ currentOrder: Array.isArray(selectedAnswer) ? selectedAnswer : currentQuestion.items.map((_, i) => i),
2553
+ correctOrder: currentQuestion.correctOrder,
2554
+ showFeedback,
2555
+ onOrderChange: handleAnswerChange
2556
+ }
2557
+ ),
2558
+ currentQuestion.type === "matrix" && currentQuestion.leftItems && currentQuestion.rightItems && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2559
+ MatchingDragDrop,
2560
+ {
2561
+ leftItems: currentQuestion.leftItems,
2562
+ rightItems: currentQuestion.rightItems,
2563
+ currentMatches: typeof selectedAnswer === "object" && selectedAnswer !== null && !Array.isArray(selectedAnswer) ? selectedAnswer : {},
2564
+ correctMatches: currentQuestion.correctMatches,
2565
+ showFeedback,
2566
+ onMatchChange: handleAnswerChange
2567
+ }
2568
+ ),
2569
+ currentQuestion.type === "assessment" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.options, children: (() => {
2570
+ const scaleType = currentQuestion.scaleType || "likert";
2571
+ if (scaleType === "yes-no") {
2572
+ const options = ["Yes", "No"];
2573
+ return options.map((option, idx) => {
2574
+ const isSelected = selectedAnswer === option;
2575
+ let optionStyle = { ...defaultStyles.option };
2576
+ if (isSelected) {
2577
+ optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
2578
+ }
2579
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2580
+ "div",
2581
+ {
2582
+ style: {
2583
+ ...optionStyle,
2584
+ cursor: showFeedback ? "default" : "pointer",
2585
+ display: "flex",
2586
+ alignItems: "center",
2587
+ gap: "8px"
2588
+ },
2589
+ onClick: () => !showFeedback && handleAnswerChange(option),
2590
+ "data-testid": `assessment-option-${option.toLowerCase()}`,
2591
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { flex: 1 }, children: option })
2592
+ },
2593
+ idx
2594
+ );
2595
+ });
2596
+ }
2597
+ if (scaleType === "rating") {
2598
+ const min = currentQuestion.scaleMin || 1;
2599
+ const max = currentQuestion.scaleMax || 5;
2600
+ const ratings = Array.from({ length: max - min + 1 }, (_, i) => min + i);
2601
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { display: "flex", gap: "8px", flexWrap: "wrap", justifyContent: "center" }, children: ratings.map((rating) => {
2602
+ const isSelected = selectedAnswer === rating;
2603
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2604
+ "button",
2605
+ {
2606
+ onClick: () => !showFeedback && handleAnswerChange(rating),
2607
+ disabled: showFeedback,
2608
+ style: {
2609
+ width: "48px",
2610
+ height: "48px",
2611
+ borderRadius: "50%",
2612
+ border: isSelected ? "2px solid #6721b0" : "2px solid #e5e7eb",
2613
+ backgroundColor: isSelected ? "#f3e8ff" : "#ffffff",
2614
+ cursor: showFeedback ? "not-allowed" : "pointer",
2615
+ fontSize: "18px",
2616
+ fontWeight: "600",
2617
+ color: isSelected ? "#6721b0" : "#374151"
2618
+ },
2619
+ "data-testid": `assessment-rating-${rating}`,
2620
+ children: rating
2621
+ },
2622
+ rating
2623
+ );
2624
+ }) });
2625
+ }
2626
+ const likertOptions = [
2627
+ "Strongly Disagree",
2628
+ "Disagree",
2629
+ "Neutral",
2630
+ "Agree",
2631
+ "Strongly Agree"
2632
+ ];
2633
+ return likertOptions.map((option, idx) => {
2634
+ const isSelected = selectedAnswer === option;
2635
+ let optionStyle = { ...defaultStyles.option };
2636
+ if (isSelected) {
2637
+ optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
2638
+ }
2639
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2640
+ "div",
2641
+ {
2642
+ style: {
2643
+ ...optionStyle,
2644
+ cursor: showFeedback ? "default" : "pointer",
2645
+ display: "flex",
2646
+ alignItems: "center",
2647
+ gap: "8px"
2648
+ },
2649
+ onClick: () => !showFeedback && handleAnswerChange(option),
2650
+ "data-testid": `assessment-likert-${idx}`,
2651
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { flex: 1 }, children: option })
2652
+ },
2653
+ idx
2654
+ );
2655
+ });
2656
+ })() }),
2248
2657
  showFeedback && currentAnswerDetail && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
2249
2658
  ...defaultStyles.feedback,
2250
- ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackCorrect : defaultStyles.feedbackIncorrect
2659
+ ...currentQuestion.type === "assessment" ? defaultStyles.feedbackNeutral : currentAnswerDetail.isCorrect ? defaultStyles.feedbackCorrect : defaultStyles.feedbackIncorrect
2251
2660
  }, children: [
2252
2661
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
2253
2662
  ...defaultStyles.feedbackTitle,
2254
- ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackTitleCorrect : defaultStyles.feedbackTitleIncorrect
2255
- }, children: currentAnswerDetail.isCorrect ? "\u2713 Correct!" : "\u2717 Incorrect" }),
2663
+ ...currentQuestion.type === "assessment" ? defaultStyles.feedbackTitleNeutral : currentAnswerDetail.isCorrect ? defaultStyles.feedbackTitleCorrect : defaultStyles.feedbackTitleIncorrect
2664
+ }, children: currentQuestion.type === "assessment" ? "\u2713 Response recorded" : currentAnswerDetail.isCorrect ? "\u2713 Correct!" : "\u2717 Incorrect" }),
2256
2665
  currentQuestion.explanation && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.feedbackExplanation, children: currentQuestion.explanation })
2257
2666
  ] })
2258
2667
  ] }),
@@ -2574,7 +2983,7 @@ function QuizPlayer({
2574
2983
  lessonId,
2575
2984
  courseId,
2576
2985
  answerResult: showFeedback && currentAnswerDetail ? {
2577
- wasIncorrect: !currentAnswerDetail.isCorrect,
2986
+ wasIncorrect: currentQuestion.type !== "assessment" && !currentAnswerDetail.isCorrect,
2578
2987
  selectedAnswer: typeof selectedAnswer === "string" ? selectedAnswer : Array.isArray(selectedAnswer) ? selectedAnswer.join(", ") : void 0,
2579
2988
  correctAnswer: typeof currentQuestion.correctAnswer === "string" ? currentQuestion.correctAnswer : Array.isArray(currentQuestion.correctAnswer) ? currentQuestion.correctAnswer.join(", ") : void 0,
2580
2989
  explanation: currentQuestion.explanation
@@ -2782,10 +3191,31 @@ var spinnerKeyframes = `
2782
3191
  to { transform: rotate(360deg); }
2783
3192
  }
2784
3193
  `;
2785
- function formatAnswer(answer) {
3194
+ function formatAnswer(answer, questionType, items, leftItems) {
2786
3195
  if (answer === null || answer === void 0) {
2787
3196
  return "No answer";
2788
3197
  }
3198
+ if (questionType === "sorting" && Array.isArray(answer)) {
3199
+ const indices = answer;
3200
+ if (items && items.length > 0) {
3201
+ return indices.map((idx, pos) => `${pos + 1}. ${items[idx] ?? `Item ${idx + 1}`}`).join(", ");
3202
+ }
3203
+ return indices.map((idx, pos) => `Position ${pos + 1}: Item ${idx + 1}`).join(", ");
3204
+ }
3205
+ if (questionType === "matrix" && typeof answer === "object" && !Array.isArray(answer)) {
3206
+ const matches = answer;
3207
+ if (leftItems && leftItems.length > 0) {
3208
+ return leftItems.map((left) => {
3209
+ const right = matches[left];
3210
+ return `${left} \u2192 ${right || "No answer"}`;
3211
+ }).join(", ");
3212
+ }
3213
+ const entries = Object.entries(matches);
3214
+ if (entries.length === 0) {
3215
+ return "No answer";
3216
+ }
3217
+ return entries.map(([left, right]) => `${left} \u2192 ${right || "No answer"}`).join(", ");
3218
+ }
2789
3219
  if (typeof answer === "string") {
2790
3220
  return answer;
2791
3221
  }
@@ -2797,6 +3227,15 @@ function formatAnswer(answer) {
2797
3227
  }
2798
3228
  return String(answer);
2799
3229
  }
3230
+ function formatCorrectSortingAnswer(items, correctOrder) {
3231
+ return correctOrder.map((idx, pos) => `${pos + 1}. ${items[idx] ?? `Item ${idx + 1}`}`).join(", ");
3232
+ }
3233
+ function formatCorrectMatrixAnswer(correctMatches, leftItems) {
3234
+ if (leftItems && leftItems.length > 0) {
3235
+ return leftItems.map((left) => `${left} \u2192 ${correctMatches[left] || "N/A"}`).join(", ");
3236
+ }
3237
+ return Object.entries(correctMatches).map(([left, right]) => `${left} \u2192 ${right}`).join(", ");
3238
+ }
2800
3239
  function AttemptViewer({
2801
3240
  attemptId,
2802
3241
  apiBaseUrl,
@@ -2933,11 +3372,11 @@ function AttemptViewer({
2933
3372
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.questionText, children: answer.questionText }),
2934
3373
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.answerSection, children: [
2935
3374
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.answerLabel, children: "Your answer:" }),
2936
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.studentAnswer, children: formatAnswer(answer.selectedAnswer) })
3375
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.studentAnswer, children: formatAnswer(answer.selectedAnswer, answer.questionType, answer.items, answer.leftItems) })
2937
3376
  ] }),
2938
3377
  !answer.isCorrect && answer.correctAnswer && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.answerSection, children: [
2939
3378
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.answerLabel, children: "Correct answer:" }),
2940
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.correctAnswer, children: formatAnswer(answer.correctAnswer) })
3379
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.correctAnswer, children: answer.questionType === "sorting" && answer.items && answer.correctOrder ? formatCorrectSortingAnswer(answer.items, answer.correctOrder) : answer.questionType === "matrix" && answer.correctAnswer && typeof answer.correctAnswer === "object" && !Array.isArray(answer.correctAnswer) ? formatCorrectMatrixAnswer(answer.correctAnswer, answer.leftItems) : formatAnswer(answer.correctAnswer, answer.questionType, answer.items, answer.leftItems) })
2941
3380
  ] }),
2942
3381
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.points, children: [
2943
3382
  answer.pointsEarned,