@nualang/nualang-ui-components 0.1.1434 → 0.1.1436

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 (31) hide show
  1. package/dist/Cards/Bot/Bot.js +1 -1
  2. package/dist/Cards/Course/Course.js +340 -31
  3. package/dist/Cards/Course/Course.test.js +124 -6
  4. package/dist/Cards/FeedbackCard/FeedbackCard.js +6 -1
  5. package/dist/Cards/Roleplay/Roleplay.js +1 -1
  6. package/dist/Dialogs/DynamicAIWarning/DynamicAIWarning.js +2 -2
  7. package/dist/Dialogs/FindTopicsDialog/FindTopicsDialog.js +70 -23
  8. package/dist/Dialogs/GroupedFeedbackDialog/GroupedFeedbackDialog.js +7 -3
  9. package/dist/Dialogs/GroupedFeedbackDialog/GroupedFeedbackDialog.test.js +39 -0
  10. package/dist/Dialogs/QuickCreateClassroomDialog/QuickCreateClassroomDialog.js +0 -1
  11. package/dist/Exercises/Bot/Bot.js +12 -6
  12. package/dist/Forms/ContentTypeFilter/ContentTypeFilter.js +214 -0
  13. package/dist/Forms/ContentTypeFilter/ContentTypeFilter.test.js +141 -0
  14. package/dist/Forms/CreateClassroom/Steps/AISettings/AISettings.js +235 -0
  15. package/dist/Forms/CreateClassroom/Steps/AISettings/AISettings.test.js +237 -0
  16. package/dist/Forms/CreateClassroom/Steps/ClassroomSettings/ClassroomSettings.js +2 -64
  17. package/dist/Forms/CreateClassroom/Steps/CourseSettings/CourseSettings.js +16 -13
  18. package/dist/Forms/CreateClassroom/Steps/index.js +2 -1
  19. package/dist/Forms/UpdateClassroom/UpdateClassroom.js +75 -10
  20. package/dist/Lists/Bots/Bots.js +55 -47
  21. package/dist/Lists/CourseOutline/CourseOutline.js +21 -20
  22. package/dist/Lists/Courses/Courses.js +71 -4
  23. package/dist/Lists/Exercises/Exercises.js +34 -1
  24. package/dist/Lists/ListElements/index.js +15 -1
  25. package/dist/Lists/Roleplays/Roleplays.js +54 -46
  26. package/dist/Lists/Topics/Topics.js +5 -16
  27. package/dist/Tables/MeetingPrompstList/MeetingPromptsList.js +4 -0
  28. package/dist/hooks/useExerciseState.js +2 -2
  29. package/dist/index.js +2 -1
  30. package/dist/utils/aiControls.js +63 -0
  31. package/package.json +1 -1
@@ -138,7 +138,7 @@ function Bot({
138
138
  sx: classes.media
139
139
  })
140
140
  }), /*#__PURE__*/_jsx(CardHeader, {
141
- action: /*#__PURE__*/_jsx(IconButton, {
141
+ action: compact ? undefined : /*#__PURE__*/_jsx(IconButton, {
142
142
  disabled: true,
143
143
  size: "large",
144
144
  value: "More",
@@ -4,7 +4,7 @@ import { useInView } from "react-intersection-observer";
4
4
  // Mui
5
5
  import { Button, Box, Typography, IconButton, Menu, MenuItem, Checkbox, Tooltip, CardActions, CardActionArea, Divider, Chip } from "@mui/material";
6
6
  import Skeleton from "@mui/material/Skeleton";
7
- import { styled } from "@mui/material/styles";
7
+ import { styled, alpha } from "@mui/material/styles";
8
8
  import { tooltipClasses } from "@mui/material/Tooltip";
9
9
  import Group from "@mui/icons-material/Group";
10
10
  import ExitToApp from "@mui/icons-material/ExitToApp";
@@ -16,9 +16,16 @@ import CopyIcon from "@mui/icons-material/FileCopy";
16
16
  import OndemandVideoIcon from "@mui/icons-material/OndemandVideo";
17
17
  import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
18
18
  import DifficultyIcon from "@mui/icons-material/School";
19
+ import BubbleChart from "@mui/icons-material/BubbleChart";
19
20
  import VisibilityIcon from "@mui/icons-material/Visibility";
20
21
  import VisibilityOff from "@mui/icons-material/VisibilityOff";
21
22
  import DeleteIcon from "@mui/icons-material/Delete";
23
+ import FolderOpen from "@mui/icons-material/FolderOpen";
24
+ import Face from "@mui/icons-material/Face";
25
+ import ChevronRight from "@mui/icons-material/ChevronRight";
26
+ import ListAlt from "@mui/icons-material/ListAlt";
27
+ import Forum from "@mui/icons-material/Forum";
28
+ import RecordVoiceOver from "@mui/icons-material/RecordVoiceOver";
22
29
 
23
30
  // components
24
31
  import MembersDialog from "../../Dialogs/Members/Members";
@@ -32,7 +39,201 @@ import ProgressiveCardMedia from "../../Misc/ProgressiveCardMedia/ProgressiveCar
32
39
  import { classes, CardContainer, CardAvatar, CardHeader, CardTitle, CardSubheader, CardInfoSection, CardDescription, CardTop, CardContent, CardMenuIcon, CardInfoSectionItem, CardVisibility } from "../CardElements";
33
40
  import { courses as courseQuerys } from "@nualang/nualang-api-and-queries/Queries";
34
41
  import useConfirm from "../../hooks/useConfirm";
42
+ import primaryPalette from "../../theme/colors/primary";
43
+
44
+ // Per-type presentation for the type badge, meta row and action buttons.
45
+ // `course` is the default and keeps the existing card behaviour untouched.
46
+ // Type icons mirror the in-course exercise list: chatbots use Face, roleplays
47
+ // (scenarios) use Forum. Course keeps School (consistent with the nav); topic
48
+ // has no exercise-list equivalent and keeps FolderOpen.
35
49
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
50
+ const TYPE_META = {
51
+ course: {
52
+ Icon: BubbleChart,
53
+ labelKey: "course",
54
+ actionKey: "start_course"
55
+ },
56
+ // A topic is a container of activities, so its primary action opens it
57
+ // ("View topic") rather than starting a single activity.
58
+ topic: {
59
+ Icon: FolderOpen,
60
+ labelKey: "topic",
61
+ actionKey: "view_topic"
62
+ },
63
+ chatbot: {
64
+ Icon: Face,
65
+ labelKey: "chatbot",
66
+ actionKey: "view_chatbot"
67
+ },
68
+ scenario: {
69
+ Icon: Forum,
70
+ labelKey: "scenario",
71
+ actionKey: "view_scenario"
72
+ }
73
+ };
74
+
75
+ // White pill over the media, top-left. Required for non-course result types.
76
+ function TypeBadge({
77
+ t,
78
+ type
79
+ }) {
80
+ const meta = TYPE_META[type];
81
+ if (!meta) {
82
+ return null;
83
+ }
84
+ const {
85
+ Icon,
86
+ labelKey
87
+ } = meta;
88
+ return /*#__PURE__*/_jsx(Chip, {
89
+ icon: /*#__PURE__*/_jsx(Icon, {
90
+ sx: {
91
+ fontSize: 16
92
+ }
93
+ }),
94
+ label: t(labelKey),
95
+ sx: theme => ({
96
+ position: "absolute",
97
+ bottom: theme.spacing(1),
98
+ right: theme.spacing(1),
99
+ height: 28,
100
+ fontSize: 13,
101
+ fontWeight: 600,
102
+ color: theme.palette.primary.main,
103
+ backgroundColor: theme.palette.background.paper,
104
+ boxShadow: theme.shadows[1],
105
+ pointerEvents: "none",
106
+ "& .MuiChip-icon": {
107
+ color: theme.palette.primary.main
108
+ }
109
+ })
110
+ });
111
+ }
112
+
113
+ // Type-specific meta shown in the info row (activities / conversation / turns).
114
+ function TypeMetaItem({
115
+ t,
116
+ type,
117
+ activitiesCount = 0,
118
+ turns = 0
119
+ }) {
120
+ if (type === "topic") {
121
+ return /*#__PURE__*/_jsxs(CardInfoSectionItem, {
122
+ variant: "caption",
123
+ component: "span",
124
+ children: [/*#__PURE__*/_jsx(ListAlt, {
125
+ sx: {
126
+ ...classes.infoIcon
127
+ },
128
+ fontSize: "small"
129
+ }), t("n_activities", {
130
+ number: activitiesCount
131
+ })]
132
+ });
133
+ }
134
+ if (type === "chatbot") {
135
+ return /*#__PURE__*/_jsxs(CardInfoSectionItem, {
136
+ variant: "caption",
137
+ component: "span",
138
+ children: [/*#__PURE__*/_jsx(Face, {
139
+ sx: {
140
+ ...classes.infoIcon
141
+ },
142
+ fontSize: "small"
143
+ }), t("conversation")]
144
+ });
145
+ }
146
+ if (type === "scenario") {
147
+ return /*#__PURE__*/_jsxs(CardInfoSectionItem, {
148
+ variant: "caption",
149
+ component: "span",
150
+ children: [/*#__PURE__*/_jsx(RecordVoiceOver, {
151
+ sx: {
152
+ ...classes.infoIcon
153
+ },
154
+ fontSize: "small"
155
+ }), t("roleplay_turns", {
156
+ number: turns
157
+ })]
158
+ });
159
+ }
160
+ return null;
161
+ }
162
+
163
+ // Full-width "From course" link shown directly above the action buttons.
164
+ function FromCourseLink({
165
+ t,
166
+ parentCourse,
167
+ onClick,
168
+ compact = false
169
+ }) {
170
+ if (!parentCourse?.id) {
171
+ return null;
172
+ }
173
+ const navigate = event => {
174
+ event.stopPropagation();
175
+ onClick(parentCourse.id);
176
+ };
177
+ return /*#__PURE__*/_jsxs(Box, {
178
+ component: "a",
179
+ role: "link",
180
+ tabIndex: 0,
181
+ "aria-label": t("from_course_named", {
182
+ title: parentCourse.title
183
+ }),
184
+ onClick: navigate,
185
+ onKeyDown: event => {
186
+ if (event.key === "Enter" || event.key === " ") {
187
+ event.preventDefault();
188
+ navigate(event);
189
+ }
190
+ },
191
+ sx: theme => ({
192
+ display: "flex",
193
+ alignItems: "center",
194
+ gap: 0.75,
195
+ cursor: "pointer",
196
+ textDecoration: "none",
197
+ margin: compact ? "0 12px 8px" : "0 16px 8px",
198
+ padding: compact ? "4px 6px 4px 8px" : "6px 8px 6px 10px",
199
+ borderRadius: "8px",
200
+ // Light grey pill on light surfaces; a subtle translucent-white raise
201
+ // on dark ones (grey.100 would be a bright block on a dark card).
202
+ backgroundColor: theme.palette.mode === "dark" ? alpha(theme.palette.common.white, 0.08) : theme.palette.grey[100],
203
+ transition: "background-color 0.2s",
204
+ // Nualang primary 50 tint (#F4E4F5) in light mode; a translucent primary
205
+ // fill in dark mode where the flat tint would be unreadable.
206
+ "&:hover, &:focus-visible": {
207
+ backgroundColor: theme.palette.mode === "dark" ? alpha(theme.palette.primary.main, 0.16) : primaryPalette[50]
208
+ }
209
+ }),
210
+ children: [/*#__PURE__*/_jsx(Typography, {
211
+ variant: "caption",
212
+ sx: {
213
+ color: "text.secondary",
214
+ flexShrink: 0
215
+ },
216
+ children: t("from_course")
217
+ }), /*#__PURE__*/_jsx(Typography, {
218
+ variant: "caption",
219
+ sx: {
220
+ fontWeight: 600,
221
+ color: "primary.main",
222
+ overflow: "hidden",
223
+ textOverflow: "ellipsis",
224
+ whiteSpace: "nowrap",
225
+ flexGrow: 1
226
+ },
227
+ children: parentCourse.title
228
+ }), /*#__PURE__*/_jsx(ChevronRight, {
229
+ sx: {
230
+ fontSize: 18,
231
+ color: "text.secondary",
232
+ flexShrink: 0
233
+ }
234
+ })]
235
+ });
236
+ }
36
237
  function OverflowMenu({
37
238
  t,
38
239
  courseId,
@@ -317,7 +518,8 @@ function CourseInfoSection({
317
518
  course,
318
519
  topicDifficulties,
319
520
  isPlaysVisible,
320
- exerciseStartTotal
521
+ exerciseStartTotal,
522
+ type = "course"
321
523
  }) {
322
524
  const iconSx = compact ? {
323
525
  ...classes.infoIcon,
@@ -332,6 +534,11 @@ function CourseInfoSection({
332
534
  t: t,
333
535
  difficulty: topicDifficulties.length > 0 ? topicDifficulties : course?.difficulty,
334
536
  compact: compact
537
+ }), type !== "course" && /*#__PURE__*/_jsx(TypeMetaItem, {
538
+ t: t,
539
+ type: type,
540
+ activitiesCount: course?.activitiesCount,
541
+ turns: course?.turns
335
542
  }), (course?.pdf || course.topicsWithPdf && Object.keys(course?.topicsWithPdf)?.length > 0) && /*#__PURE__*/_jsxs(CardInfoSectionItem, {
336
543
  variant: "caption",
337
544
  component: "span",
@@ -517,6 +724,11 @@ function CourseCard({
517
724
  isClassroomCreator = null,
518
725
  handleDeleteCourse,
519
726
  compact = false,
727
+ type = "course",
728
+ parentCourse,
729
+ handleViewParentCourse,
730
+ handleStartItem,
731
+ showTypeBadge = false,
520
732
  // Compact-only: this course is already in the classroom this dialog is
521
733
  // adding to, so its selection is locked on and can't be toggled off here.
522
734
  alreadyAdded = false
@@ -538,18 +750,18 @@ function CourseCard({
538
750
  limit: 1000
539
751
  }
540
752
  }, {
541
- // Compact cards (the Find Courses picker) never open the outline and
542
- // don't need per-section topic difficulty aggregation - course.difficulty
543
- // already covers the meta row's single level chip, and this dialog never
544
- // wires up getCourseSections either.
545
- enabled: !compact && !!course?.courseId && (isOutlineOpen || visible)
753
+ // Fetch sections when the outline is actually opened (any layout). For the
754
+ // eager preload (difficulty aggregation on scroll into view) stay
755
+ // non-compact only — compact cards fall back to course.difficulty and only
756
+ // pay for sections on demand when the user opens the outline.
757
+ enabled: type === "course" && !!course?.courseId && (isOutlineOpen || !compact && visible)
546
758
  });
547
759
  const {
548
760
  membersQuery,
549
761
  memberQuery,
550
762
  isMember
551
763
  } = useCourseState({
552
- courseId: course && course.courseId ? course.courseId : null,
764
+ courseId: type === "course" && course && course.courseId ? course.courseId : null,
553
765
  username,
554
766
  getCourseSections,
555
767
  getCourseMember,
@@ -590,15 +802,6 @@ function CourseCard({
590
802
  setChecked(!checked);
591
803
  handleClickCourse(course.courseId, value);
592
804
  };
593
-
594
- // Compact cards (the Find Courses picker) mirror Topic.jsx's compact card:
595
- // both the media and the header/content are wrapped in their own
596
- // CardActionArea, each disabled (not just non-interactive) when the course
597
- // is already in the classroom.
598
- const handleCompactCardClick = () => {
599
- setChecked(!checked);
600
- handleClickCourse(course.courseId, !checked);
601
- };
602
805
  const {
603
806
  courseId,
604
807
  courseName,
@@ -638,6 +841,27 @@ function CourseCard({
638
841
  const leaveCourse = () => {
639
842
  handleLeaveCourse(courseId);
640
843
  };
844
+
845
+ // Non-course result types (topic/chatbot/scenario) reuse the course handlers
846
+ // when a type-specific one is not supplied.
847
+ const startItem = () => {
848
+ if (handleStartItem) {
849
+ handleStartItem(course);
850
+ } else if (handleStartCourse) {
851
+ handleStartCourse(course);
852
+ }
853
+ };
854
+ const viewParentCourse = () => {
855
+ const parentId = parentCourse?.id;
856
+ if (!parentId) {
857
+ return;
858
+ }
859
+ if (handleViewParentCourse) {
860
+ handleViewParentCourse(parentId);
861
+ } else if (handleViewCourse) {
862
+ handleViewCourse(parentId);
863
+ }
864
+ };
641
865
  const courseSections = sectionsQuery.isSuccess ? sectionsQuery.data.Items : [];
642
866
  const topicDifficulties = sectionsQuery.isSuccess ? [...new Set(courseSections.flatMap(section => section.topics || []).map(topic => topic.difficulty).filter(Boolean))] : [];
643
867
  const deleteCourse = async () => {
@@ -649,6 +873,12 @@ function CourseCard({
649
873
  }
650
874
  };
651
875
  const handleClickImage = () => {
876
+ // Non-course results open the item itself (matching the primary button),
877
+ // not the course-join flow.
878
+ if (type !== "course" && !selectable) {
879
+ startItem();
880
+ return;
881
+ }
652
882
  if (isCreator || isMember || isAdmin) {
653
883
  viewCourse();
654
884
  } else {
@@ -656,21 +886,44 @@ function CourseCard({
656
886
  }
657
887
  };
658
888
 
889
+ // Compact cards: the Find Courses picker selects on click; everywhere else
890
+ // (e.g. search results) the whole card navigates, exactly like the media
891
+ // click — courses open/join, content types open the item.
892
+ const handleCompactCardClick = () => {
893
+ if (selectable) {
894
+ setChecked(!checked);
895
+ handleClickCourse(course.courseId, !checked);
896
+ return;
897
+ }
898
+ handleClickImage();
899
+ };
900
+
659
901
  // const memberCount = membersQuery.isSuccess ? membersQuery.data.Items.length : 0;
660
902
 
661
903
  const isPlaysVisible = !disablePlays && exerciseStartTotal > -1;
662
904
  const isCollaborator = Array.isArray(course.collaborators) && course.collaborators.some(collab => (typeof collab === "string" ? collab : collab?.S) === email);
663
905
  const canDuplicateCourse = isCreator || course.isShareable === "everyone" || course.isShareable === "collaborators" && isCollaborator;
906
+
907
+ // The type badge (Course/Topic/Chatbot/Scenario) sits bottom-right over the
908
+ // media. In search results it replaces the visibility chip (results are
909
+ // already public), so the two never render together.
910
+ const showTypeChip = type !== "course" || showTypeBadge;
911
+ // Compact whole-card link label: the item action for content types, else the
912
+ // picker select label.
913
+ const compactActionLabel = type !== "course" ? `${t(TYPE_META[type]?.actionKey || "start_course")}: ${courseName}` : selectable ? `${checked ? t("selected") : t("select")} ${courseName}` : `${ariaText} ${courseName}`.trim();
664
914
  const titleNode = /*#__PURE__*/_jsx(CardTitle, {
665
915
  title: courseName,
666
916
  component: cardTitleComponent,
667
917
  compact: compact
668
918
  });
919
+ // Courses describe the language pair; content types (topic/chatbot/scenario)
920
+ // show their creator instead — "learn X for speakers of Y" reads as course
921
+ // chrome on an individual item (matches the dedicated Bot card convention).
669
922
  const subheaderNode = /*#__PURE__*/_jsx(CardSubheader, {
670
- subheader: t("learn_for_speakers_of", {
923
+ subheader: type === "course" ? t("learn_for_speakers_of", {
671
924
  learnLang: capitalize(t(learnLang)),
672
925
  forLang: capitalize(t(forLang))
673
- }),
926
+ }) : createdByName || capitalize(t(learnLang)),
674
927
  compact: compact
675
928
  });
676
929
  return /*#__PURE__*/_jsxs(CardContainer, {
@@ -684,7 +937,7 @@ function CourseCard({
684
937
  onClick: handleCompactCardClick,
685
938
  disabled: alreadyAdded,
686
939
  role: "link",
687
- "aria-label": `${checked ? t("selected") : t("select")} ${courseName}`,
940
+ "aria-label": compactActionLabel,
688
941
  children: /*#__PURE__*/_jsx(CourseCardMedia, {
689
942
  visible: visible,
690
943
  cardPicture: cardPicture,
@@ -692,7 +945,7 @@ function CourseCard({
692
945
  pictureXs: pictureXs,
693
946
  placeholderRef: placeholderRef
694
947
  })
695
- }), (isClassroomCreator ?? isCreator) && /*#__PURE__*/_jsx(CardVisibility, {
948
+ }), !showTypeChip && (isClassroomCreator ?? isCreator) && /*#__PURE__*/_jsx(CardVisibility, {
696
949
  t: t,
697
950
  visibility: visibility,
698
951
  isCourseHidden: isCourseHidden,
@@ -714,11 +967,14 @@ function CourseCard({
714
967
  picture: picture,
715
968
  pictureXs: pictureXs,
716
969
  placeholderRef: placeholderRef
717
- }), (isClassroomCreator ?? isCreator) && /*#__PURE__*/_jsx(CardVisibility, {
970
+ }), !showTypeChip && (isClassroomCreator ?? isCreator) && /*#__PURE__*/_jsx(CardVisibility, {
718
971
  t: t,
719
972
  visibility: visibility,
720
973
  isCourseHidden: isCourseHidden
721
974
  })]
975
+ }), showTypeChip && /*#__PURE__*/_jsx(TypeBadge, {
976
+ t: t,
977
+ type: type
722
978
  }), userImage ? /*#__PURE__*/_jsx(CardAvatar, {
723
979
  compact: compact,
724
980
  sx: theme => ({
@@ -797,8 +1053,8 @@ function CourseCard({
797
1053
  subheader: subheaderNode,
798
1054
  sx: {
799
1055
  alignItems: "flex-start",
800
- pt: "14px",
801
- pb: 1.5,
1056
+ pt: "10px",
1057
+ pb: 1,
802
1058
  pl: "12px",
803
1059
  pr: 1
804
1060
  }
@@ -807,13 +1063,21 @@ function CourseCard({
807
1063
  borderColor: "rgba(0, 0, 0, 0.12)"
808
1064
  }
809
1065
  }), /*#__PURE__*/_jsxs(CardContent, {
1066
+ sx: {
1067
+ pt: 1,
1068
+ pb: 1,
1069
+ "&:last-child": {
1070
+ pb: 1
1071
+ }
1072
+ },
810
1073
  children: [/*#__PURE__*/_jsx(CourseInfoSection, {
811
1074
  t: t,
812
1075
  compact: true,
813
1076
  course: course,
814
1077
  topicDifficulties: topicDifficulties,
815
1078
  isPlaysVisible: isPlaysVisible,
816
- exerciseStartTotal: exerciseStartTotal
1079
+ exerciseStartTotal: exerciseStartTotal,
1080
+ type: type
817
1081
  }), /*#__PURE__*/_jsx(Tooltip, {
818
1082
  title: description,
819
1083
  disableHoverListener: !isDescriptionTruncated,
@@ -841,7 +1105,7 @@ function CourseCard({
841
1105
  children: [selectable && /*#__PURE__*/_jsx(Checkbox, {
842
1106
  checked: checked,
843
1107
  onChange: handleCheckboxClick
844
- }), !disableActions && !isClassroomArchived && /*#__PURE__*/_jsx(Tooltip, {
1108
+ }), type === "course" && !disableActions && !isClassroomArchived && /*#__PURE__*/_jsx(Tooltip, {
845
1109
  title: `${courseName} ${t("more_options")}`,
846
1110
  children: /*#__PURE__*/_jsx(IconButton, {
847
1111
  onClick: handleClickOpenMenu,
@@ -865,7 +1129,8 @@ function CourseCard({
865
1129
  course: course,
866
1130
  topicDifficulties: topicDifficulties,
867
1131
  isPlaysVisible: isPlaysVisible,
868
- exerciseStartTotal: exerciseStartTotal
1132
+ exerciseStartTotal: exerciseStartTotal,
1133
+ type: type
869
1134
  }), /*#__PURE__*/_jsx(CardDescription, {
870
1135
  variant: "body2",
871
1136
  color: "textSecondary",
@@ -873,7 +1138,26 @@ function CourseCard({
873
1138
  children: description
874
1139
  })]
875
1140
  })]
876
- }), !compact && /*#__PURE__*/_jsxs(CardActions, {
1141
+ }), parentCourse?.id && /*#__PURE__*/_jsx(FromCourseLink, {
1142
+ t: t,
1143
+ parentCourse: parentCourse,
1144
+ onClick: viewParentCourse,
1145
+ compact: compact
1146
+ }), !(compact && selectable) && /*#__PURE__*/_jsxs(CardActions, {
1147
+ sx: compact ? {
1148
+ flexWrap: "wrap",
1149
+ gap: 0.5,
1150
+ pt: 0,
1151
+ px: 1.25,
1152
+ pb: 1.25,
1153
+ "& .MuiButton-root": {
1154
+ fontSize: 12,
1155
+ paddingTop: "2px",
1156
+ paddingBottom: "2px",
1157
+ paddingLeft: "12px",
1158
+ paddingRight: "12px"
1159
+ }
1160
+ } : undefined,
877
1161
  children: [selectable && /*#__PURE__*/_jsx(Button, {
878
1162
  onClick: viewCourse,
879
1163
  color: "primary",
@@ -881,7 +1165,19 @@ function CourseCard({
881
1165
  disabled: checked,
882
1166
  "aria-label": "Select Course",
883
1167
  children: checked ? t("selected") : t("select")
884
- }), (isCreator || isCollaborator && !isMember) && !selectable && /*#__PURE__*/_jsxs(_Fragment, {
1168
+ }), type !== "course" && !selectable &&
1169
+ /*#__PURE__*/
1170
+ // Only the "Start …" action here — course navigation is handled by
1171
+ // the "From course" link above the buttons, so a separate "View
1172
+ // course" button would be redundant.
1173
+ _jsx(Button, {
1174
+ onClick: startItem,
1175
+ color: "primary",
1176
+ variant: "outlined",
1177
+ disabled: disableActions,
1178
+ "aria-label": `${t(TYPE_META[type]?.actionKey || "start_course")}: ${courseName}`,
1179
+ children: t(TYPE_META[type]?.actionKey || "start_course")
1180
+ }), type === "course" && (isCreator || isCollaborator && !isMember) && !selectable && /*#__PURE__*/_jsxs(_Fragment, {
885
1181
  children: [!isClassroomArchived && /*#__PURE__*/_jsx(Button, {
886
1182
  onClick: viewCourse,
887
1183
  color: "primary",
@@ -896,7 +1192,7 @@ function CourseCard({
896
1192
  "aria-label": `View Outline: ${courseName}`,
897
1193
  children: t("view_outline")
898
1194
  })]
899
- }), !isCreator && isMember && !selectable && !isClassroomArchived && /*#__PURE__*/_jsxs(_Fragment, {
1195
+ }), type === "course" && !isCreator && isMember && !selectable && !isClassroomArchived && /*#__PURE__*/_jsxs(_Fragment, {
900
1196
  children: [/*#__PURE__*/_jsx(Button, {
901
1197
  onClick: viewCourse,
902
1198
  color: "primary",
@@ -911,7 +1207,7 @@ function CourseCard({
911
1207
  "aria-label": `View Progress: ${courseName}`,
912
1208
  children: t("view_progress")
913
1209
  })]
914
- }), !isCreator && !isMember && !isAdmin && !selectable && /*#__PURE__*/_jsxs(_Fragment, {
1210
+ }), type === "course" && !isCreator && !isMember && !isAdmin && !selectable && /*#__PURE__*/_jsxs(_Fragment, {
915
1211
  children: [/*#__PURE__*/_jsx(Button, {
916
1212
  onClick: startCourse,
917
1213
  color: "primary",
@@ -988,6 +1284,19 @@ function Course(props) {
988
1284
  }
989
1285
  const MemoizedCourse = /*#__PURE__*/memo(Course);
990
1286
  MemoizedCourse.propTypes = {
1287
+ // Result type. Defaults to "course" so existing usages are unaffected.
1288
+ type: PropTypes.oneOf(["course", "topic", "chatbot", "scenario"]),
1289
+ // Parent course for non-course result types (topic/chatbot/scenario).
1290
+ parentCourse: PropTypes.shape({
1291
+ id: PropTypes.string,
1292
+ title: PropTypes.string
1293
+ }),
1294
+ // Navigates to the parent course from the "From course" link / action.
1295
+ handleViewParentCourse: PropTypes.func,
1296
+ // Starts a non-course item (topic/chatbot/scenario).
1297
+ handleStartItem: PropTypes.func,
1298
+ // Force the type badge to render for courses too (e.g. in search results).
1299
+ showTypeBadge: PropTypes.bool,
991
1300
  courseId: PropTypes.string.isRequired,
992
1301
  courseName: PropTypes.string.isRequired,
993
1302
  description: PropTypes.string.isRequired,