@nualang/nualang-ui-components 0.1.1314 → 0.1.1316

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 = () => {
@@ -13,6 +13,7 @@ import level2Rubric from '../../Dialogs/PDFViewer/rubrics/level2Rubric.pdf';
13
13
  import level3Rubric from '../../Dialogs/PDFViewer/rubrics/level3Rubric.pdf';
14
14
  import level4Rubric from '../../Dialogs/PDFViewer/rubrics/level4Rubric.pdf';
15
15
  import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
16
+ import NualaCreating from "../../Misc/NualaCreating/NualaCreating";
16
17
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
17
18
  export default function FeedbackCard({
18
19
  t = x => x,
@@ -25,7 +26,12 @@ export default function FeedbackCard({
25
26
  setSelectedRecording = () => {},
26
27
  s3Url = "",
27
28
  conversation = {},
28
- existingFeedback = {}
29
+ existingFeedback = {},
30
+ playerRef = {
31
+ current: null
32
+ },
33
+ goToTimestamp = () => {},
34
+ startTimes = {}
29
35
  }) {
30
36
  const [isRecordingDialogOpen, setIsRecordingDialogOpen] = useState(false);
31
37
  const [hasBeenEdited, setHasBeenEdited] = useState(false);
@@ -192,121 +198,139 @@ export default function FeedbackCard({
192
198
  boxShadow: 3,
193
199
  overflow: "hidden"
194
200
  },
195
- children: [/*#__PURE__*/_jsx(Box, {
201
+ children: [aiJustGenerated ? /*#__PURE__*/_jsxs(Grid, {
196
202
  sx: {
197
- px: 3,
198
- pt: 2,
199
- pb: 1,
200
- fontWeight: 600,
201
- color: "text.secondary"
203
+ ...(!aiJustGenerated && {
204
+ display: "none"
205
+ })
202
206
  },
203
- children: /*#__PURE__*/_jsxs(Grid, {
204
- container: true,
205
- alignItems: "center",
206
- spacing: 3,
207
- children: [/*#__PURE__*/_jsx(Grid, {
208
- size: {
209
- xs: 12,
210
- md: 2.5
211
- },
212
- children: /*#__PURE__*/_jsx(Typography, {
213
- variant: "subtitle2",
214
- children: t("student")
215
- })
216
- }), /*#__PURE__*/_jsx(Grid, {
217
- size: {
218
- xs: 12,
219
- md: 9.5
220
- },
221
- children: /*#__PURE__*/_jsxs(Box, {
222
- sx: {
223
- display: "flex",
224
- alignItems: "center"
207
+ mx: 2,
208
+ size: 12,
209
+ children: [/*#__PURE__*/_jsx(Typography, {
210
+ variant: "h6",
211
+ sx: {
212
+ mt: 2,
213
+ mb: 2
214
+ },
215
+ children: t("generating_feedback")
216
+ }), /*#__PURE__*/_jsx(NualaCreating, {})]
217
+ }) : /*#__PURE__*/_jsxs(_Fragment, {
218
+ children: [/*#__PURE__*/_jsx(Box, {
219
+ sx: {
220
+ px: 3,
221
+ pt: 2,
222
+ pb: 1,
223
+ fontWeight: 600,
224
+ color: "text.secondary"
225
+ },
226
+ children: /*#__PURE__*/_jsxs(Grid, {
227
+ container: true,
228
+ alignItems: "center",
229
+ spacing: 3,
230
+ children: [/*#__PURE__*/_jsx(Grid, {
231
+ size: {
232
+ xs: 12,
233
+ md: 2.5
225
234
  },
226
- children: [/*#__PURE__*/_jsx(Typography, {
235
+ children: /*#__PURE__*/_jsx(Typography, {
227
236
  variant: "subtitle2",
228
- children: t("feedback")
229
- }), /*#__PURE__*/_jsx(Tooltip, {
230
- title: t("rubric_info_desc"),
231
- children: /*#__PURE__*/_jsx(IconButton, {
232
- onClick: handleOpenPDF,
233
- color: "primary",
234
- "aria-label": t("rubric_info"),
235
- size: "small",
236
- children: /*#__PURE__*/_jsx(HelpOutlineIcon, {})
237
- })
238
- })]
239
- })
240
- })]
241
- })
242
- }), /*#__PURE__*/_jsx(CardContent, {
243
- sx: {
244
- px: 3,
245
- pt: 1
246
- },
247
- children: [...selectedMembers].sort((a, b) => {
248
- const aJoined = joinedMembers.includes(a.memberId);
249
- const bJoined = joinedMembers.includes(b.memberId);
250
- return aJoined === bJoined ? 0 : aJoined ? -1 : 1;
251
- }).map(student => {
252
- const isAbsent = hasSubmission && !joinedMembers.includes(student.memberId);
253
- const disabled = !hasSubmission || isAbsent;
254
- const studentFeedback = values.feedbackData[student.memberId] || {};
255
- return /*#__PURE__*/_jsx(Box, {
256
- sx: {
257
- py: 2
258
- },
259
- children: /*#__PURE__*/_jsxs(Grid, {
260
- container: true,
261
- alignItems: "flex-start",
262
- spacing: 3,
263
- children: [/*#__PURE__*/_jsx(Grid, {
264
- size: {
265
- xs: 12,
266
- md: 2.5
267
- },
268
- children: /*#__PURE__*/_jsxs(Box, {
237
+ children: t("student")
238
+ })
239
+ }), /*#__PURE__*/_jsx(Grid, {
240
+ size: {
241
+ xs: 12,
242
+ md: 9.5
243
+ },
244
+ children: /*#__PURE__*/_jsxs(Box, {
245
+ sx: {
269
246
  display: "flex",
270
- alignItems: "center",
271
- gap: 2,
272
- children: [/*#__PURE__*/_jsx(Avatar, {
273
- alt: student.username,
274
- src: student.userImage,
275
- sx: {
276
- width: 48,
277
- height: 48,
278
- filter: isAbsent ? "grayscale(100%)" : "none"
279
- }
280
- }), /*#__PURE__*/_jsx(Typography, {
281
- fontWeight: 600,
282
- sx: {
283
- color: isAbsent ? grey[500] : "text.primary"
284
- },
285
- children: student.username
286
- })]
287
- })
288
- }), /*#__PURE__*/_jsx(Grid, {
289
- size: {
290
- xs: 12,
291
- md: 9.5
247
+ alignItems: "center"
292
248
  },
293
- children: /*#__PURE__*/_jsx(TextField, {
294
- variant: "outlined",
295
- fullWidth: true,
296
- multiline: true,
297
- disabled: disabled,
298
- value: studentFeedback.feedbackText || "",
299
- onChange: e => {
300
- setFieldValue(`feedbackData.${student.memberId}.feedbackText`, e.target.value);
301
- setHasBeenEdited(true);
302
- setIsSubmitted(false);
249
+ children: [/*#__PURE__*/_jsx(Typography, {
250
+ variant: "subtitle2",
251
+ children: t("feedback")
252
+ }), /*#__PURE__*/_jsx(Tooltip, {
253
+ title: t("rubric_info_desc"),
254
+ children: /*#__PURE__*/_jsx(IconButton, {
255
+ onClick: handleOpenPDF,
256
+ color: "primary",
257
+ "aria-label": t("rubric_info"),
258
+ size: "small",
259
+ children: /*#__PURE__*/_jsx(HelpOutlineIcon, {})
260
+ })
261
+ })]
262
+ })
263
+ })]
264
+ })
265
+ }), /*#__PURE__*/_jsx(CardContent, {
266
+ sx: {
267
+ px: 3,
268
+ pt: 1
269
+ },
270
+ children: [...selectedMembers].sort((a, b) => {
271
+ const aJoined = joinedMembers.includes(a.memberId);
272
+ const bJoined = joinedMembers.includes(b.memberId);
273
+ return aJoined === bJoined ? 0 : aJoined ? -1 : 1;
274
+ }).map(student => {
275
+ const isAbsent = hasSubmission && !joinedMembers.includes(student.memberId);
276
+ const disabled = !hasSubmission || isAbsent;
277
+ const studentFeedback = values.feedbackData[student.memberId] || {};
278
+ return /*#__PURE__*/_jsx(Box, {
279
+ sx: {
280
+ py: 2
281
+ },
282
+ children: /*#__PURE__*/_jsxs(Grid, {
283
+ container: true,
284
+ alignItems: "flex-start",
285
+ spacing: 3,
286
+ children: [/*#__PURE__*/_jsx(Grid, {
287
+ size: {
288
+ xs: 12,
289
+ md: 2.5
290
+ },
291
+ children: /*#__PURE__*/_jsxs(Box, {
292
+ display: "flex",
293
+ alignItems: "center",
294
+ gap: 2,
295
+ children: [/*#__PURE__*/_jsx(Avatar, {
296
+ alt: student.username,
297
+ src: student.userImage,
298
+ sx: {
299
+ width: 48,
300
+ height: 48,
301
+ filter: isAbsent ? "grayscale(100%)" : "none"
302
+ }
303
+ }), /*#__PURE__*/_jsx(Typography, {
304
+ fontWeight: 600,
305
+ sx: {
306
+ color: isAbsent ? grey[500] : "text.primary"
307
+ },
308
+ children: student.username
309
+ })]
310
+ })
311
+ }), /*#__PURE__*/_jsx(Grid, {
312
+ size: {
313
+ xs: 12,
314
+ md: 9.5
303
315
  },
304
- placeholder: t("Write your feedback here")
305
- })
306
- })]
307
- })
308
- }, student.memberId);
309
- })
316
+ children: /*#__PURE__*/_jsx(TextField, {
317
+ variant: "outlined",
318
+ fullWidth: true,
319
+ multiline: true,
320
+ disabled: disabled,
321
+ value: studentFeedback.feedbackText || "",
322
+ onChange: e => {
323
+ setFieldValue(`feedbackData.${student.memberId}.feedbackText`, e.target.value);
324
+ setHasBeenEdited(true);
325
+ setIsSubmitted(false);
326
+ },
327
+ placeholder: t("Write your feedback here")
328
+ })
329
+ })]
330
+ })
331
+ }, student.memberId);
332
+ })
333
+ })]
310
334
  }), /*#__PURE__*/_jsxs(CardActions, {
311
335
  sx: {
312
336
  px: 3,
@@ -349,13 +373,13 @@ export default function FeedbackCard({
349
373
  },
350
374
  children: [/*#__PURE__*/_jsx(Button, {
351
375
  variant: "outlined",
352
- disabled: !hasSubmission,
376
+ disabled: !hasSubmission || aiJustGenerated,
353
377
  onClick: handleOpenRecordingDialog,
354
378
  children: t("view_recording")
355
379
  }), /*#__PURE__*/_jsx(Button, {
356
380
  variant: "contained",
357
381
  color: "secondary",
358
- disabled: !hasSubmission,
382
+ disabled: !hasSubmission || aiJustGenerated,
359
383
  onClick: handleGenerateAI,
360
384
  endIcon: /*#__PURE__*/_jsx(AutoFixHighIcon, {}),
361
385
  children: t("generate_feedback")
@@ -381,11 +405,11 @@ export default function FeedbackCard({
381
405
  attendeesData: attendeesInThisDiscussion,
382
406
  conversation: conversation,
383
407
  s3Url: s3Url,
384
- playerRef: {
385
- current: null
386
- },
408
+ playerRef: playerRef,
387
409
  aiGrade: aiGrade,
388
- hasBadLanguage: discussionData?.hasBadLanguage || false
410
+ hasBadLanguage: discussionData?.hasBadLanguage || false,
411
+ goToTimestamp: goToTimestamp,
412
+ videoStartTime: startTimes[0]
389
413
  }), /*#__PURE__*/_jsx(PDFViewer, {
390
414
  open: openPDF,
391
415
  handleClose: handleClosePDF,
@@ -4,9 +4,15 @@ import Avatar from "@mui/material/Avatar";
4
4
  import AvatarGroup from "@mui/material/AvatarGroup";
5
5
  import Chip from "@mui/material/Chip";
6
6
  import Box from "@mui/material/Box";
7
- import { Card, CardActionArea, Divider, Button, Tooltip } from "@mui/material";
7
+ import { Card, CardActionArea, Divider, Button, Tooltip, IconButton } from "@mui/material";
8
8
  import PropTypes from "prop-types";
9
9
  import dayjs from "dayjs";
10
+ import PDFViewer from "../../Dialogs/PDFViewer/PDFViewer";
11
+ import level1Rubric from '../../Dialogs/PDFViewer/rubrics/level1Rubric.pdf';
12
+ import level2Rubric from '../../Dialogs/PDFViewer/rubrics/level2Rubric.pdf';
13
+ import level3Rubric from '../../Dialogs/PDFViewer/rubrics/level3Rubric.pdf';
14
+ import level4Rubric from '../../Dialogs/PDFViewer/rubrics/level4Rubric.pdf';
15
+ import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
10
16
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
11
17
  export default function RecordingCard({
12
18
  isTeacher = false,
@@ -16,6 +22,9 @@ export default function RecordingCard({
16
22
  t = x => x
17
23
  }) {
18
24
  const [promptExpanded, setPromptExpanded] = useState(false);
25
+ const [openPDF, setOpenPDF] = useState(false);
26
+ const [pageNumber, setPageNumber] = useState(1);
27
+ const [scale, setScale] = useState(1);
19
28
  const selectedMembers = userData.filter(o1 => [recordingsListData?.userIds].flat().some(o2 => o1?.memberId === o2));
20
29
  const attendedMembers = selectedMembers.filter(attendee => recordingsListData?.joinedMembers?.includes(attendee?.memberId));
21
30
  const notAttendedMembers = selectedMembers.filter(attendee => !recordingsListData?.joinedMembers?.includes(attendee?.memberId));
@@ -28,6 +37,13 @@ export default function RecordingCard({
28
37
  if (diffDays < 0) dueText = t("past_due");else if (diffDays === 0) dueText = t("due_today");else if (diffDays === 1) dueText = t("due_tomorrow");else dueText = `${t("due_in")} ${diffDays} ${t("days")}`;
29
38
  return `${formattedDate} - ${dueText}`;
30
39
  };
40
+ const rubricMap = {
41
+ 1: level1Rubric,
42
+ 2: level2Rubric,
43
+ 3: level3Rubric,
44
+ 4: level4Rubric
45
+ };
46
+ const pdfUrl = rubricMap[recordingsListData?.level] || level1Rubric;
31
47
  const renderExpandableText = (text, expanded, setExpanded, maxChars = 150) => {
32
48
  if (!text) return null;
33
49
  const isLong = text.length > maxChars;
@@ -50,145 +66,177 @@ export default function RecordingCard({
50
66
  })]
51
67
  });
52
68
  };
53
- return /*#__PURE__*/_jsx(Card, {
54
- sx: {
55
- display: "flex",
56
- marginBottom: 2,
57
- boxShadow: 3
58
- },
59
- children: /*#__PURE__*/_jsx(CardActionArea, {
60
- component: "a",
61
- onClick: handleView,
62
- children: /*#__PURE__*/_jsxs(Box, {
63
- sx: {
64
- p: 2
65
- },
66
- children: [/*#__PURE__*/_jsxs(Box, {
69
+ const handleOpenPDF = () => {
70
+ setOpenPDF(true);
71
+ };
72
+ const handleClosePDF = () => {
73
+ setOpenPDF(false);
74
+ };
75
+ return /*#__PURE__*/_jsxs(_Fragment, {
76
+ children: [/*#__PURE__*/_jsx(Card, {
77
+ sx: {
78
+ display: "flex",
79
+ marginBottom: 2,
80
+ boxShadow: 3
81
+ },
82
+ children: /*#__PURE__*/_jsx(CardActionArea, {
83
+ component: "a",
84
+ onClick: handleView,
85
+ children: /*#__PURE__*/_jsxs(Box, {
67
86
  sx: {
68
- display: "flex",
69
- justifyContent: "space-between",
70
- alignItems: "center"
87
+ p: 2
71
88
  },
72
89
  children: [/*#__PURE__*/_jsxs(Box, {
73
90
  sx: {
74
91
  display: "flex",
75
- alignItems: "center",
76
- gap: 1
92
+ justifyContent: "space-between",
93
+ alignItems: "center"
77
94
  },
78
- children: [/*#__PURE__*/_jsx(Typography, {
79
- variant: "body1",
80
- fontWeight: 600,
95
+ children: [/*#__PURE__*/_jsxs(Box, {
96
+ sx: {
97
+ display: "flex",
98
+ alignItems: "center",
99
+ gap: 1
100
+ },
101
+ children: [/*#__PURE__*/_jsx(Typography, {
102
+ variant: "body1",
103
+ fontWeight: 600,
104
+ sx: {
105
+ color: "text.primary"
106
+ },
107
+ children: recordingsListData?.meetingTopic
108
+ }), recordingsListData?.feedbackText && /*#__PURE__*/_jsx(Chip, {
109
+ label: t("submission_graded"),
110
+ color: "success",
111
+ size: "small"
112
+ })]
113
+ }), /*#__PURE__*/_jsx(Typography, {
114
+ variant: "body2",
115
+ color: "text.secondary",
81
116
  sx: {
82
- color: "text.primary"
117
+ ml: 1.5,
118
+ fontWeight: 500
83
119
  },
84
- children: recordingsListData?.meetingTopic
85
- }), recordingsListData?.feedbackText && /*#__PURE__*/_jsx(Chip, {
86
- label: t("submission_graded"),
87
- color: "success",
88
- size: "small"
120
+ children: formatMeetingDueText(recordingsListData.timestamp, t)
89
121
  })]
90
- }), /*#__PURE__*/_jsx(Typography, {
91
- variant: "body2",
92
- color: "text.secondary",
122
+ }), /*#__PURE__*/_jsx(Divider, {
93
123
  sx: {
94
- ml: 1.5,
95
- fontWeight: 500
96
- },
97
- children: formatMeetingDueText(recordingsListData.timestamp, t)
98
- })]
99
- }), /*#__PURE__*/_jsx(Divider, {
100
- sx: {
101
- my: 1.5
102
- }
103
- }), /*#__PURE__*/_jsxs(Box, {
104
- sx: {
105
- display: "flex",
106
- justifyContent: "space-between",
107
- alignItems: "center",
108
- mb: 1.5
109
- },
110
- children: [/*#__PURE__*/_jsxs(Box, {
124
+ my: 1.5
125
+ }
126
+ }), /*#__PURE__*/_jsxs(Box, {
111
127
  sx: {
112
128
  display: "flex",
129
+ justifyContent: "space-between",
113
130
  alignItems: "center",
114
- gap: 1
131
+ mb: 1.5
115
132
  },
116
- children: [/*#__PURE__*/_jsx(AvatarGroup, {
117
- max: 4,
118
- spacing: -3,
133
+ children: [/*#__PURE__*/_jsxs(Box, {
119
134
  sx: {
120
135
  display: "flex",
121
- "& .MuiAvatar-root": {
122
- width: 40,
123
- height: 40,
124
- fontSize: "1rem"
125
- }
136
+ alignItems: "center",
137
+ gap: 1
126
138
  },
127
- children: attendedMembers.map(attendee => /*#__PURE__*/_jsx(Tooltip, {
128
- title: `${attendee?.username} - Present`,
129
- children: /*#__PURE__*/_jsx(Avatar, {
130
- alt: attendee?.username,
131
- src: attendee?.userImage
132
- })
133
- }, attendee?.memberId))
134
- }), notAttendedMembers.length > 0 && /*#__PURE__*/_jsx(AvatarGroup, {
135
- max: 4,
136
- spacing: -3,
139
+ children: [/*#__PURE__*/_jsx(AvatarGroup, {
140
+ max: 4,
141
+ spacing: -3,
142
+ sx: {
143
+ display: "flex",
144
+ "& .MuiAvatar-root": {
145
+ width: 40,
146
+ height: 40,
147
+ fontSize: "1rem"
148
+ }
149
+ },
150
+ children: attendedMembers.map(attendee => /*#__PURE__*/_jsx(Tooltip, {
151
+ title: `${attendee?.username} - Present`,
152
+ children: /*#__PURE__*/_jsx(Avatar, {
153
+ alt: attendee?.username,
154
+ src: attendee?.userImage
155
+ })
156
+ }, attendee?.memberId))
157
+ }), notAttendedMembers.length > 0 && /*#__PURE__*/_jsx(AvatarGroup, {
158
+ max: 4,
159
+ spacing: -3,
160
+ sx: {
161
+ display: "flex",
162
+ ml: 2,
163
+ "& .MuiAvatar-root": {
164
+ width: 40,
165
+ height: 40,
166
+ fontSize: "1rem",
167
+ filter: "grayscale(100%)"
168
+ }
169
+ },
170
+ children: notAttendedMembers.map(attendee => /*#__PURE__*/_jsx(Tooltip, {
171
+ title: `${attendee?.username} - Not Present`,
172
+ children: /*#__PURE__*/_jsx(Avatar, {
173
+ alt: attendee?.username,
174
+ src: attendee?.userImage
175
+ })
176
+ }, attendee?.memberId))
177
+ })]
178
+ }), /*#__PURE__*/_jsx(Button, {
179
+ variant: "contained",
180
+ color: "primary",
181
+ onClick: e => {
182
+ e.stopPropagation();
183
+ handleView();
184
+ },
185
+ sx: {
186
+ borderRadius: 2
187
+ },
188
+ children: t("view_submission")
189
+ })]
190
+ }), renderExpandableText(recordingsListData?.meetingPrompt, promptExpanded, setPromptExpanded), recordingsListData?.feedbackText && /*#__PURE__*/_jsxs(_Fragment, {
191
+ children: [/*#__PURE__*/_jsx(Divider, {
192
+ sx: {
193
+ my: 1.5,
194
+ width: "100%"
195
+ }
196
+ }), /*#__PURE__*/_jsxs(Box, {
137
197
  sx: {
138
198
  display: "flex",
139
- ml: 2,
140
- "& .MuiAvatar-root": {
141
- width: 40,
142
- height: 40,
143
- fontSize: "1rem",
144
- filter: "grayscale(100%)"
145
- }
199
+ alignItems: "center"
146
200
  },
147
- children: notAttendedMembers.map(attendee => /*#__PURE__*/_jsx(Tooltip, {
148
- title: `${attendee?.username} - Not Present`,
149
- children: /*#__PURE__*/_jsx(Avatar, {
150
- alt: attendee?.username,
151
- src: attendee?.userImage
201
+ children: [/*#__PURE__*/_jsx(Typography, {
202
+ variant: "subtitle2",
203
+ fontWeight: 600,
204
+ children: t("feedback")
205
+ }), /*#__PURE__*/_jsx(Tooltip, {
206
+ title: t("rubric_info_desc"),
207
+ children: /*#__PURE__*/_jsx(IconButton, {
208
+ color: "primary",
209
+ "aria-label": t("rubric_info"),
210
+ size: "small",
211
+ onClick: e => {
212
+ e.stopPropagation();
213
+ handleOpenPDF();
214
+ },
215
+ children: /*#__PURE__*/_jsx(HelpOutlineIcon, {})
152
216
  })
153
- }, attendee?.memberId))
217
+ })]
218
+ }), /*#__PURE__*/_jsx(Typography, {
219
+ variant: "body2",
220
+ color: "text.secondary",
221
+ sx: {
222
+ fontStyle: "italic",
223
+ whiteSpace: "pre-line"
224
+ },
225
+ children: recordingsListData.feedbackText
154
226
  })]
155
- }), /*#__PURE__*/_jsx(Button, {
156
- variant: "contained",
157
- color: "primary",
158
- onClick: e => {
159
- e.stopPropagation();
160
- handleView();
161
- },
162
- sx: {
163
- borderRadius: 2
164
- },
165
- children: t("view_submission")
166
- })]
167
- }), renderExpandableText(recordingsListData?.meetingPrompt, promptExpanded, setPromptExpanded), recordingsListData?.feedbackText && /*#__PURE__*/_jsxs(_Fragment, {
168
- children: [/*#__PURE__*/_jsx(Divider, {
169
- sx: {
170
- my: 1.5,
171
- width: "100%"
172
- }
173
- }), /*#__PURE__*/_jsx(Typography, {
174
- variant: "subtitle2",
175
- fontWeight: 600,
176
- sx: {
177
- mb: 0.5
178
- },
179
- children: t("feedback")
180
- }), /*#__PURE__*/_jsx(Typography, {
181
- variant: "body2",
182
- color: "text.secondary",
183
- sx: {
184
- fontStyle: "italic",
185
- whiteSpace: "pre-line"
186
- },
187
- children: recordingsListData.feedbackText
188
227
  })]
189
- })]
228
+ })
190
229
  })
191
- })
230
+ }), /*#__PURE__*/_jsx(PDFViewer, {
231
+ open: openPDF,
232
+ handleClose: handleClosePDF,
233
+ pdfUrl: pdfUrl,
234
+ pageNumber: pageNumber,
235
+ setPageNumber: setPageNumber,
236
+ scale: scale,
237
+ setScale: setScale,
238
+ t: t
239
+ })]
192
240
  });
193
241
  }
194
242
  RecordingCard.propTypes = {
@@ -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.1316",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "files": [