@nualang/nualang-ui-components 0.1.1434 → 0.1.1435
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/Cards/Bot/Bot.js +1 -1
- package/dist/Cards/Course/Course.js +340 -31
- package/dist/Cards/Course/Course.test.js +124 -6
- package/dist/Cards/Roleplay/Roleplay.js +1 -1
- package/dist/Dialogs/FindTopicsDialog/FindTopicsDialog.js +70 -23
- package/dist/Forms/ContentTypeFilter/ContentTypeFilter.js +214 -0
- package/dist/Forms/ContentTypeFilter/ContentTypeFilter.test.js +141 -0
- package/dist/Lists/Bots/Bots.js +55 -47
- package/dist/Lists/CourseOutline/CourseOutline.js +21 -20
- package/dist/Lists/Courses/Courses.js +71 -4
- package/dist/Lists/Exercises/Exercises.js +34 -1
- package/dist/Lists/ListElements/index.js +15 -1
- package/dist/Lists/Roleplays/Roleplays.js +54 -46
- package/dist/Lists/Topics/Topics.js +5 -16
- package/dist/index.js +2 -1
- package/package.json +1 -1
package/dist/Cards/Bot/Bot.js
CHANGED
|
@@ -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
|
-
//
|
|
542
|
-
//
|
|
543
|
-
//
|
|
544
|
-
//
|
|
545
|
-
enabled:
|
|
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":
|
|
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: "
|
|
801
|
-
pb: 1
|
|
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
|
-
}),
|
|
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
|
-
}),
|
|
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,
|