@nualang/nualang-ui-components 0.1.1314 → 0.1.1315

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.
@@ -6,6 +6,8 @@ import Refresh from "@mui/icons-material/Refresh";
6
6
  import TeacherCreate from "../../img/teacher-create-2.svg";
7
7
  import Add from "@mui/icons-material/Add";
8
8
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
+ const VISIBILITY_LEAD_MINUTES = 5; // how many minutes before scheduleDate it becomes visible to students
10
+
9
11
  const AssignmentCardsList = ({
10
12
  t = text => text,
11
13
  assignments = [],
@@ -31,8 +33,7 @@ const AssignmentCardsList = ({
31
33
  isChallengeModeStudent
32
34
  }) => {
33
35
  const [lastClickedExerciseId, setLastClickedExerciseId] = useState(null);
34
- const [filter, setFilter] = useState("all"); // Filter state
35
-
36
+ const [filter, setFilter] = useState("all");
36
37
  useEffect(() => {
37
38
  const stored = localStorage.getItem("lastClickedExercise");
38
39
  if (stored) {
@@ -47,20 +48,27 @@ const AssignmentCardsList = ({
47
48
  }
48
49
  }, []);
49
50
  const now = new Date();
50
- const filteredAssignments = assignments.filter(assignment => {
51
+ const safeAssignments = Array.isArray(assignments) ? assignments : [];
52
+ const filteredAssignments = safeAssignments.filter(assignment => {
51
53
  if (!isCreator) {
52
54
  if (Array.isArray(assignment.assignedStudents) && !assignment.assignedStudents.includes(memberId)) {
53
55
  return false;
54
56
  }
55
- if (assignment.scheduleDate && new Date(assignment.scheduleDate) > now) {
56
- return false;
57
+ if (assignment.scheduleDate) {
58
+ const scheduleDateObj = new Date(assignment.scheduleDate);
59
+ const visibleFrom = new Date(scheduleDateObj.getTime() - VISIBILITY_LEAD_MINUTES * 60 * 1000);
60
+ if (visibleFrom > now) {
61
+ return false;
62
+ }
57
63
  }
58
64
  }
59
65
  return true;
60
66
  }).filter(assignment => {
61
67
  if (filter === "all") return true;
62
68
  if (filter === "scheduled") {
63
- return assignment.scheduleDate && new Date(assignment.scheduleDate) > now;
69
+ if (!assignment.scheduleDate) return false;
70
+ const scheduleDateObj = new Date(assignment.scheduleDate);
71
+ return scheduleDateObj > now;
64
72
  }
65
73
  if (filter === "pastDue") {
66
74
  return assignment.dueDate && new Date(assignment.dueDate) < now;
@@ -71,12 +79,12 @@ const AssignmentCardsList = ({
71
79
  return true;
72
80
  }).sort((a, b) => {
73
81
  if (filter === "dueNext") {
74
- return new Date(a.dueDate) - new Date(b.dueDate);
82
+ return new Date(a.dueDate || 0) - new Date(b.dueDate || 0);
75
83
  }
76
84
  if (filter === "scheduled") {
77
- return new Date(a.scheduleDate) - new Date(b.scheduleDate);
85
+ return new Date(a.scheduleDate || 0) - new Date(b.scheduleDate || 0);
78
86
  }
79
- return new Date(b.createdAt) - new Date(a.createdAt);
87
+ return new Date(b.createdAt || 0) - new Date(a.createdAt || 0);
80
88
  });
81
89
  if (isLoadingAssignments) {
82
90
  return /*#__PURE__*/_jsxs(Box, {
@@ -150,7 +158,7 @@ const AssignmentCardsList = ({
150
158
  }, idx))]
151
159
  });
152
160
  }
153
- if (!assignments || assignments.length === 0) {
161
+ if (!safeAssignments.length || filteredAssignments.length === 0) {
154
162
  return /*#__PURE__*/_jsx(Box, {
155
163
  mb: 1,
156
164
  children: /*#__PURE__*/_jsxs(Box, {
@@ -290,9 +298,6 @@ const AssignmentCardsList = ({
290
298
  onClick: refreshAssignments,
291
299
  "aria-label": "refresh",
292
300
  size: "large",
293
- sx: {
294
- size: "large"
295
- },
296
301
  children: /*#__PURE__*/_jsx(Refresh, {
297
302
  fontSize: "inherit"
298
303
  })
@@ -325,8 +330,24 @@ AssignmentCardsList.propTypes = {
325
330
  t: PropTypes.func,
326
331
  assignments: PropTypes.array,
327
332
  isCreator: PropTypes.bool,
328
- onRefresh: PropTypes.func,
333
+ handleCreateAssignment: PropTypes.func,
334
+ getCourses: PropTypes.func,
335
+ getCourseSections: PropTypes.func,
336
+ getRoleplays: PropTypes.func,
337
+ deleteAssignment: PropTypes.func,
338
+ handleEditAssignment: PropTypes.func,
339
+ refreshAssignments: PropTypes.func,
340
+ username: PropTypes.string,
341
+ preferred_username: PropTypes.string,
342
+ progressHelpers: PropTypes.object,
343
+ fetchMemberCourseCompletions: PropTypes.func,
344
+ memberId: PropTypes.string,
345
+ handleViewProgress: PropTypes.func,
346
+ lastAssignmentFetch: PropTypes.any,
347
+ assignmentMembersById: PropTypes.object,
329
348
  isLoadingAssignments: PropTypes.bool,
330
- assignmentMembersById: PropTypes.object
349
+ members: PropTypes.array,
350
+ courses: PropTypes.array,
351
+ isChallengeModeStudent: PropTypes.bool
331
352
  };
332
353
  export default AssignmentCardsList;
@@ -62,7 +62,7 @@ export default function CreateAssignmentDialog({
62
62
  const [submittedExercises, setSubmittedExercises] = useState(initialData.exercises ? initialData.exercises : []);
63
63
  const [members, setMembers] = useState([]);
64
64
  const [assignment, setAssignment] = useState({
65
- classroomId: classroom?.length > 0 ? [classroom] : [],
65
+ classroomId: classroom && classroom.length > 0 ? [classroom] : [],
66
66
  assignedStudents,
67
67
  title: initialData.title ? initialData.title : undefined,
68
68
  instructions: initialData.instructions ? initialData.instructions : t("default_assignment_instructions"),
@@ -174,7 +174,12 @@ export default function CreateAssignmentDialog({
174
174
  let filteredCourses = selectedCourses.filter(course => submittedExercises.some(exercise => exercise.courseSectionTopicId?.split("|")[0] === course.courseId));
175
175
  setAssignment(prev => ({
176
176
  ...prev,
177
- courses: filteredCourses
177
+ courses: filteredCourses.map(course => ({
178
+ courseId: course.courseId,
179
+ courseName: course.courseName,
180
+ description: course.description,
181
+ picture: course.picture
182
+ }))
178
183
  }));
179
184
  const selectedTopicIds = new Set(submittedExercises.map(ex => ex.courseSectionTopicId));
180
185
  const topicNames = Array.from(new Set(filteredCourses.flatMap(course => course.sections?.flatMap(section => section.topics?.flatMap(topic => {
@@ -212,10 +217,12 @@ export default function CreateAssignmentDialog({
212
217
  }));
213
218
  }, [members]);
214
219
  useEffect(() => {
215
- setAssignment(prev => ({
216
- ...prev,
217
- classroomId: [classroom]
218
- }));
220
+ if (classroom && classroom.length > 0 && !assignment.classroomId?.includes(classroom)) {
221
+ setAssignment(prev => ({
222
+ ...prev,
223
+ classroomId: [classroom]
224
+ }));
225
+ }
219
226
  }, [classroom]);
220
227
  const [isSelectExerciseOpen, setIsSelectExerciseOpen] = useState(false);
221
228
  const handleCloseSelectExercise = () => {
@@ -25,7 +25,12 @@ export default function FeedbackCard({
25
25
  setSelectedRecording = () => {},
26
26
  s3Url = "",
27
27
  conversation = {},
28
- existingFeedback = {}
28
+ existingFeedback = {},
29
+ playerRef = {
30
+ current: null
31
+ },
32
+ goToTimestamp = () => {},
33
+ startTimes = {}
29
34
  }) {
30
35
  const [isRecordingDialogOpen, setIsRecordingDialogOpen] = useState(false);
31
36
  const [hasBeenEdited, setHasBeenEdited] = useState(false);
@@ -381,11 +386,11 @@ export default function FeedbackCard({
381
386
  attendeesData: attendeesInThisDiscussion,
382
387
  conversation: conversation,
383
388
  s3Url: s3Url,
384
- playerRef: {
385
- current: null
386
- },
389
+ playerRef: playerRef,
387
390
  aiGrade: aiGrade,
388
- hasBadLanguage: discussionData?.hasBadLanguage || false
391
+ hasBadLanguage: discussionData?.hasBadLanguage || false,
392
+ goToTimestamp: goToTimestamp,
393
+ videoStartTime: startTimes[0]
389
394
  }), /*#__PURE__*/_jsx(PDFViewer, {
390
395
  open: openPDF,
391
396
  handleClose: handleClosePDF,
@@ -26,7 +26,9 @@ export default function GroupFeedbackDialog({
26
26
  handleCreateFeedback = () => {},
27
27
  onFeedbackSubmitted = () => {},
28
28
  editBackgroundImages,
29
- editMeetingImages
29
+ editMeetingImages,
30
+ goToTimestamp = () => {},
31
+ startTimes = {}
30
32
  }) {
31
33
  const discussionsInGroup = scheduleListData.filter(item => item.groupedId === groupedId);
32
34
  const submissionsForGroup = submissionsTableData.filter(item => item.groupedId === groupedId);
@@ -290,7 +292,9 @@ export default function GroupFeedbackDialog({
290
292
  selectedRecording: selectedRecording,
291
293
  s3Url: s3Url,
292
294
  playerRef: playerRef,
293
- conversation: conversation
295
+ conversation: conversation,
296
+ goToTimestamp: goToTimestamp,
297
+ startTimes: startTimes
294
298
  }, discussion.meetingId || discussion.meetingID || index);
295
299
  })]
296
300
  })
@@ -10,12 +10,32 @@ import { red } from "@mui/material/colors";
10
10
  import { useMediaQuery } from "@mui/material";
11
11
  import { useTheme } from "@mui/material/styles";
12
12
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
13
+ const dateDifferenceInSeconds = (date1, date2) => {
14
+ const d1 = new Date(date1);
15
+ const d2 = new Date(date2);
16
+ return Math.abs((d2 - d1) / 1000);
17
+ };
18
+ const formatTimestamp = (timestamp, videoStartTime) => {
19
+ if (!timestamp && timestamp !== 0) return "00:00";
20
+ if (!videoStartTime) return "00:00";
21
+ let numberString = timestamp.toString();
22
+ numberString = numberString.substring(0, 10);
23
+ const formattedTime = Number(numberString);
24
+ const dateObj = new Date(formattedTime * 1000);
25
+ const isoString = dateObj.toISOString();
26
+ const seconds = dateDifferenceInSeconds(videoStartTime, isoString);
27
+ if (isNaN(seconds) || seconds < 0) return "00:00";
28
+ const mins = Math.floor(seconds / 60);
29
+ const secs = Math.floor(seconds % 60);
30
+ return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
31
+ };
13
32
  function TranscriptViewer({
14
33
  conversation = {},
15
34
  goToTimestamp = () => {},
16
35
  attendeesData,
17
36
  t = text => text,
18
- loading = false
37
+ loading = false,
38
+ videoStartTime = null
19
39
  }) {
20
40
  return /*#__PURE__*/_jsx(Box, {
21
41
  sx: {
@@ -69,40 +89,66 @@ function TranscriptViewer({
69
89
  sx: theme => ({
70
90
  display: "flex",
71
91
  flexDirection: "column",
72
- mb: 1,
92
+ mb: 1.5,
73
93
  cursor: "pointer",
74
- p: 1,
75
- borderRadius: 1,
94
+ p: 1.5,
95
+ borderRadius: 2,
96
+ transition: "all 0.2s ease-in-out",
97
+ border: "1px solid transparent",
76
98
  "&:hover": {
77
- backgroundColor: theme.palette.mode === "dark" ? theme.palette.primary.main : "#f9f9f9"
99
+ backgroundColor: theme.palette.mode === "dark" ? theme.palette.primary.dark : theme.palette.primary.light + "15",
100
+ border: `1px solid ${theme.palette.primary.main}`,
101
+ transform: "translateX(4px)"
78
102
  }
79
103
  }),
80
104
  children: [/*#__PURE__*/_jsxs(Box, {
81
105
  sx: {
82
106
  display: "flex",
83
107
  alignItems: "center",
84
- mb: 0.5
108
+ mb: 0.5,
109
+ gap: 1
85
110
  },
86
111
  children: [/*#__PURE__*/_jsx(Avatar, {
87
112
  alt: sentence?.username,
88
113
  src: userImage,
89
114
  sx: {
90
- width: 24,
91
- height: 24,
92
- mr: 1
115
+ width: 28,
116
+ height: 28
93
117
  }
94
118
  }), sentence?.user && /*#__PURE__*/_jsx(Typography, {
95
119
  variant: "subtitle2",
96
120
  fontWeight: "bold",
97
121
  sx: {
98
- whiteSpace: "nowrap"
122
+ whiteSpace: "nowrap",
123
+ flex: 1
99
124
  },
100
125
  children: sentence.user
126
+ }), (sentence.start_time !== undefined || sentence.end_time !== undefined) && videoStartTime && /*#__PURE__*/_jsx(Box, {
127
+ sx: {
128
+ display: "flex",
129
+ alignItems: "center",
130
+ gap: 0.5,
131
+ px: 1,
132
+ py: 0.25,
133
+ backgroundColor: theme => theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.08)" : "rgba(0, 0, 0, 0.06)",
134
+ borderRadius: 1
135
+ },
136
+ children: /*#__PURE__*/_jsx(Typography, {
137
+ variant: "caption",
138
+ sx: {
139
+ fontFamily: "monospace",
140
+ fontWeight: 500,
141
+ color: "text.secondary"
142
+ },
143
+ children: formatTimestamp(sentence.start_time, videoStartTime)
144
+ })
101
145
  })]
102
146
  }), /*#__PURE__*/_jsx(Typography, {
103
147
  variant: "body2",
104
148
  sx: {
105
- ml: 4
149
+ ml: 4.5,
150
+ lineHeight: 1.6,
151
+ color: "text.primary"
106
152
  },
107
153
  children: sentence.text
108
154
  })]
@@ -117,7 +163,8 @@ TranscriptViewer.propTypes = {
117
163
  conversation: PropTypes.object,
118
164
  goToTimestamp: PropTypes.func,
119
165
  t: PropTypes.func,
120
- loading: PropTypes.bool
166
+ loading: PropTypes.bool,
167
+ videoStartTime: PropTypes.string
121
168
  };
122
169
  export default function RecordingDialog({
123
170
  open,
@@ -129,7 +176,8 @@ export default function RecordingDialog({
129
176
  playerRef,
130
177
  conversation,
131
178
  attendeesData,
132
- hasBadLanguage
179
+ hasBadLanguage,
180
+ videoStartTime
133
181
  }) {
134
182
  const theme = useTheme();
135
183
  const isSmallScreen = useMediaQuery(theme.breakpoints.down("sm"));
@@ -227,7 +275,8 @@ export default function RecordingDialog({
227
275
  attendeesData: attendeesData,
228
276
  conversation: conversation,
229
277
  goToTimestamp: goToTimestamp,
230
- loading: loading
278
+ loading: loading,
279
+ videoStartTime: videoStartTime
231
280
  })
232
281
  })]
233
282
  })
@@ -254,5 +303,6 @@ RecordingDialog.propTypes = {
254
303
  s3Url: PropTypes.string,
255
304
  playerRef: PropTypes.object,
256
305
  conversation: PropTypes.array,
257
- handleCreateFeedback: PropTypes.func
306
+ handleCreateFeedback: PropTypes.func,
307
+ videoStartTime: PropTypes.string
258
308
  };
@@ -538,7 +538,8 @@ function Classroom({
538
538
  hasBadLanguage,
539
539
  handleArchiveSubmissions,
540
540
  isVideoChatEnabled,
541
- isVideoChatEnabledInSettings
541
+ isVideoChatEnabledInSettings,
542
+ startTimes
542
543
  } = vchatProps;
543
544
  const isSmallScreen = useMediaQuery("(max-width:410px)");
544
545
  const {
@@ -1041,7 +1042,8 @@ function Classroom({
1041
1042
  setSelectedRecording: setSelectedRecording,
1042
1043
  meetingRecsData: meetingRecsData,
1043
1044
  hasBadLanguage: hasBadLanguage,
1044
- handleArchiveSubmissions: handleArchiveSubmissions
1045
+ handleArchiveSubmissions: handleArchiveSubmissions,
1046
+ startTimes: startTimes
1045
1047
  })
1046
1048
  })
1047
1049
  }), !isCreator && discussions?.length > 0 && /*#__PURE__*/_jsx(Box, {
@@ -1248,7 +1250,7 @@ function Classroom({
1248
1250
  }
1249
1251
  }
1250
1252
  return 0;
1251
- }, [isCreator.tabs?.length]); // only recompute if isCreator or tabs change
1253
+ }, [isCreator, tabs?.length]); // only recompute if isCreator or tabs change
1252
1254
 
1253
1255
  return /*#__PURE__*/_jsxs(_Fragment, {
1254
1256
  children: [/*#__PURE__*/_jsxs("div", {
@@ -36,7 +36,8 @@ const MeetingPromptsList = withStyles(({
36
36
  setSelectedRecording,
37
37
  selectedRecording,
38
38
  getMeeting,
39
- getRecordings
39
+ getRecordings,
40
+ startTimes = {}
40
41
  }) => {
41
42
  const [selectedGroup, setSelectedGroup] = useState({});
42
43
  const [isDialogOpen, setDialogOpen] = useState(false);
@@ -392,7 +393,9 @@ const MeetingPromptsList = withStyles(({
392
393
  handleCreateFeedback: handleCreateFeedback,
393
394
  onFeedbackSubmitted: getRecordings,
394
395
  editBackgroundImages: editBackgroundImages[0],
395
- editMeetingImages: editMeetingImages[0]
396
+ editMeetingImages: editMeetingImages[0],
397
+ goToTimestamp: goToTimestamp,
398
+ startTimes: startTimes
396
399
  }), /*#__PURE__*/_jsx(Dialog, {
397
400
  role: "dialog",
398
401
  open: isRecordingDialogOpen,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nualang/nualang-ui-components",
3
- "version": "0.1.1314",
3
+ "version": "0.1.1315",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "files": [