@nualang/nualang-ui-components 0.1.1334 → 0.1.1336

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.
@@ -195,7 +195,7 @@ function TopicSettings({
195
195
  handleChange({
196
196
  target: {
197
197
  name: "voicePitch",
198
- value: null
198
+ value: ""
199
199
  }
200
200
  });
201
201
  }
@@ -8,7 +8,7 @@ import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
8
8
  import Avatar from "@mui/material/Avatar";
9
9
  import Box from "@mui/material/Box";
10
10
  import Typography from "@mui/material/Typography";
11
- import { TableSortLabel, Chip } from "@mui/material";
11
+ import { TableSortLabel, Chip, Grid } from "@mui/material";
12
12
  import IconButton from "@mui/material/IconButton";
13
13
  import Tooltip, { tooltipClasses } from "@mui/material/Tooltip";
14
14
  import { green, lightGreen, red, grey, deepOrange, orange, yellow } from "@mui/material/colors";
@@ -17,7 +17,7 @@ import DefaultButton from "../../Misc/DefaultColourButton/DefaultColourButton";
17
17
  import { formatMemberCourseCompletions, formatMemberAssignmentCompletions } from "./utils";
18
18
  import DoneIcon from "@mui/icons-material/Done";
19
19
  import CloseIcon from "@mui/icons-material/Close";
20
- import RemoveIcon from '@mui/icons-material/Remove';
20
+ import RemoveIcon from "@mui/icons-material/Remove";
21
21
  import HtmlTooltipComponent from "../../Misc/HtmlTooltipComponent/HtmlTooltipComponent";
22
22
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
23
23
  const {
@@ -123,7 +123,8 @@ function DataCell({
123
123
  backgroundColor,
124
124
  memberActivityLink,
125
125
  type,
126
- index
126
+ index,
127
+ submittedLate = false
127
128
  }) {
128
129
  const completedTotal = `${completed} of ${total}`;
129
130
  const [tooltipOpen, setTooltipOpen] = useState(false);
@@ -147,7 +148,7 @@ function DataCell({
147
148
  backgroundColor: backgroundColor || theme.palette.divider,
148
149
  color: theme.palette.common.white
149
150
  }),
150
- children: /*#__PURE__*/_jsx(HtmlTooltip, {
151
+ children: /*#__PURE__*/_jsxs(HtmlTooltip, {
151
152
  tabIndex: `${index + 1}`,
152
153
  open: tooltipOpen,
153
154
  disableHoverListener: !type,
@@ -182,7 +183,7 @@ function DataCell({
182
183
  })]
183
184
  }),
184
185
  enterDelay: 1000,
185
- children: /*#__PURE__*/_jsx(Typography, {
186
+ children: [/*#__PURE__*/_jsx(Typography, {
186
187
  variant: "subtitle1",
187
188
  component: "div",
188
189
  sx: theme => ({
@@ -192,21 +193,50 @@ function DataCell({
192
193
  textDecoration: "none"
193
194
  }),
194
195
  children: data
195
- })
196
+ }), submittedLate && /*#__PURE__*/_jsx(Chip, {
197
+ variant: "outlined",
198
+ sx: theme => ({
199
+ color: theme.palette.common.black,
200
+ fontWeight: "bold"
201
+ }),
202
+ size: "small",
203
+ label: t("late_submission")
204
+ })]
196
205
  })
197
206
  });
198
207
  }
199
208
  function ExerciseCell({
200
209
  icon,
201
- backgroundColor
210
+ backgroundColor,
211
+ submittedLate = false,
212
+ daysLate = null,
213
+ t
202
214
  }) {
203
- return /*#__PURE__*/_jsx(Box, {
215
+ return /*#__PURE__*/_jsxs(Box, {
204
216
  component: "td",
205
217
  sx: theme => ({
206
218
  backgroundColor: backgroundColor || theme.palette.divider,
207
219
  color: theme.palette.common.black
208
220
  }),
209
- children: icon
221
+ children: [/*#__PURE__*/_jsx(Grid, {
222
+ container: true
223
+ }), /*#__PURE__*/_jsx(Grid, {
224
+ size: 12,
225
+ children: icon
226
+ }), submittedLate && /*#__PURE__*/_jsx(Grid, {
227
+ size: 12,
228
+ children: /*#__PURE__*/_jsx(Chip, {
229
+ variant: "outlined",
230
+ sx: theme => ({
231
+ color: theme.palette.common.black,
232
+ fontWeight: "bold"
233
+ }),
234
+ size: "small",
235
+ label: t("days_late", {
236
+ count: daysLate
237
+ })
238
+ })
239
+ })]
210
240
  });
211
241
  }
212
242
  function DataCellContainer({
@@ -216,7 +246,9 @@ function DataCellContainer({
216
246
  error,
217
247
  data,
218
248
  memberActivityLink,
219
- index
249
+ index,
250
+ submittedLate = false,
251
+ daysLate = null
220
252
  }) {
221
253
  if (isLoading) {
222
254
  return /*#__PURE__*/_jsx(DataCell, {
@@ -245,7 +277,9 @@ function DataCellContainer({
245
277
  total: data && data.total ? data.total : 0,
246
278
  type: data && data.type ? data.type : "percent",
247
279
  memberActivityLink: memberActivityLink,
248
- index: index
280
+ index: index,
281
+ submittedLate: submittedLate,
282
+ daysLate: daysLate
249
283
  });
250
284
  } else if (data.percentage === -1 || data.total === 0 && data.type === "correct") {
251
285
  return /*#__PURE__*/_jsx(DataCell, {
@@ -256,7 +290,9 @@ function DataCellContainer({
256
290
  total: 0,
257
291
  type: data && data.type ? data.type : "percent",
258
292
  memberActivityLink: memberActivityLink,
259
- index: index
293
+ index: index,
294
+ submittedLate: submittedLate,
295
+ daysLate: daysLate
260
296
  });
261
297
  } else if (data.percentage >= 80) {
262
298
  return /*#__PURE__*/_jsx(DataCell, {
@@ -267,7 +303,9 @@ function DataCellContainer({
267
303
  total: data.total,
268
304
  type: data && data.type ? data.type : "percent",
269
305
  memberActivityLink: memberActivityLink,
270
- index: index
306
+ index: index,
307
+ submittedLate: submittedLate,
308
+ daysLate: daysLate
271
309
  });
272
310
  } else if (data.percentage >= 60) {
273
311
  return /*#__PURE__*/_jsx(DataCell, {
@@ -278,7 +316,9 @@ function DataCellContainer({
278
316
  total: data.total,
279
317
  type: data && data.type ? data.type : "percent",
280
318
  memberActivityLink: memberActivityLink,
281
- index: index
319
+ index: index,
320
+ submittedLate: submittedLate,
321
+ daysLate: daysLate
282
322
  });
283
323
  } else if (data.percentage >= 40) {
284
324
  return /*#__PURE__*/_jsx(DataCell, {
@@ -289,7 +329,9 @@ function DataCellContainer({
289
329
  total: data.total,
290
330
  type: data && data.type ? data.type : "percent",
291
331
  memberActivityLink: memberActivityLink,
292
- index: index
332
+ index: index,
333
+ submittedLate: submittedLate,
334
+ daysLate: daysLate
293
335
  });
294
336
  } else if (data.percentage >= 20) {
295
337
  return /*#__PURE__*/_jsx(DataCell, {
@@ -300,7 +342,9 @@ function DataCellContainer({
300
342
  total: data.total,
301
343
  type: data && data.type ? data.type : "percent",
302
344
  memberActivityLink: memberActivityLink,
303
- index: index
345
+ index: index,
346
+ submittedLate: submittedLate,
347
+ daysLate: daysLate
304
348
  });
305
349
  } else if (data.percentage >= 0) {
306
350
  return /*#__PURE__*/_jsx(DataCell, {
@@ -311,7 +355,9 @@ function DataCellContainer({
311
355
  total: data.total,
312
356
  type: data && data.type ? data.type : "percent",
313
357
  memberActivityLink: memberActivityLink,
314
- index: index
358
+ index: index,
359
+ submittedLate: submittedLate,
360
+ daysLate: daysLate
315
361
  });
316
362
  }
317
363
  return /*#__PURE__*/_jsx(DataCell, {
@@ -322,7 +368,9 @@ function DataCellContainer({
322
368
  total: data.total,
323
369
  type: data && data.type ? data.type : "percent",
324
370
  memberActivityLink: memberActivityLink,
325
- index: index
371
+ index: index,
372
+ submittedLate: submittedLate,
373
+ daysLate: daysLate
326
374
  });
327
375
  }
328
376
  function AssignmentCellData({
@@ -336,6 +384,12 @@ function AssignmentCellData({
336
384
  index
337
385
  }) {
338
386
  let cellData = null;
387
+ let submittedLate = false;
388
+ let daysLate = null;
389
+ if (data?.submittedLate.isLate === true) {
390
+ submittedLate = true;
391
+ daysLate = data.submittedLate.daysLate;
392
+ }
339
393
  if (data) {
340
394
  if (filter === "percentage_complete" && data.percentComplete) {
341
395
  cellData = data.percentComplete;
@@ -355,7 +409,9 @@ function AssignmentCellData({
355
409
  error: error,
356
410
  memberActivityLink: memberActivityLink,
357
411
  index: index,
358
- isNotAssigned: isNotAssigned
412
+ isNotAssigned: isNotAssigned,
413
+ submittedLate: submittedLate,
414
+ daysLate: daysLate
359
415
  });
360
416
  }
361
417
  function CourseCellData({
@@ -430,6 +486,12 @@ function TopicCellData({
430
486
  index
431
487
  }) {
432
488
  let cellData = null;
489
+ let submittedLate = false;
490
+ let daysLate = null;
491
+ if (data?.submittedLate.isLate === true) {
492
+ submittedLate = true;
493
+ daysLate = data.submittedLate.daysLate;
494
+ }
433
495
  if (data) {
434
496
  if (filter === "percentage_complete" && data.percentComplete) {
435
497
  cellData = data.percentComplete;
@@ -448,7 +510,9 @@ function TopicCellData({
448
510
  isLoading: isLoading,
449
511
  error: error,
450
512
  memberActivityLink: memberActivityLink,
451
- index: index
513
+ index: index,
514
+ submittedLate: submittedLate,
515
+ daysLate: daysLate
452
516
  });
453
517
  }
454
518
  function ExerciseCellData({
@@ -471,6 +535,12 @@ function ExerciseCellData({
471
535
  let type = null;
472
536
  let color;
473
537
  let memberActivityLink = null;
538
+ let submittedLate = false;
539
+ let daysLate = null;
540
+ if (data?.submittedLate.isLate === true) {
541
+ submittedLate = true;
542
+ daysLate = data.submittedLate.daysLate;
543
+ }
474
544
  if (isLoading) {
475
545
  return /*#__PURE__*/_jsx(DataCell, {
476
546
  t: t,
@@ -544,7 +614,9 @@ function ExerciseCellData({
544
614
  type: type,
545
615
  backgroundColor: color ?? (type === "completed" ? green[400] : red[400]),
546
616
  memberActivityLink: memberActivityLink,
547
- index: index
617
+ index: index,
618
+ submittedLate: submittedLate,
619
+ daysLate: daysLate
548
620
  });
549
621
  }
550
622
  function RoleplayCellData({
@@ -627,8 +699,8 @@ function TableRow({
627
699
  error,
628
700
  isLoading
629
701
  } = memberCourseCompletionsQuery;
630
- const tableData = useMemo(() => memberCourseCompletionsQuery.isSuccess && Array.isArray(memberCourseCompletionsQuery.data.Items) && memberCourseCompletionsQuery.data.Items.length ? formatMemberCourseCompletions(courses, memberCourseCompletionsQuery.data.Items, reportType === "assignments" && selectedAssignment ? selectedAssignment?.exercises : null, isChallengeModeStudent) : [], [memberCourseCompletionsQuery.data, memberCourseCompletionsQuery.isSuccess, reportType, selectedAssignment, isChallengeModeStudent]);
631
- const assignmentsTableData = useMemo(() => memberCourseCompletionsQuery.isSuccess && Array.isArray(memberCourseCompletionsQuery.data.Items) && memberCourseCompletionsQuery.data.Items.length && assignments && assignments.length ? formatMemberAssignmentCompletions(assignments, courses, memberCourseCompletionsQuery.data.Items, isChallengeModeStudent) : [], [memberCourseCompletionsQuery.data, memberCourseCompletionsQuery.isSuccess, assignments, isChallengeModeStudent]);
702
+ const tableData = useMemo(() => memberCourseCompletionsQuery.isSuccess && Array.isArray(memberCourseCompletionsQuery.data.Items) && memberCourseCompletionsQuery.data.Items.length ? formatMemberCourseCompletions(courses, memberCourseCompletionsQuery.data.Items, reportType === "assignments" && selectedAssignment ? selectedAssignment?.exercises : null, isChallengeModeStudent, selectedAssignment, reportType) : [], [memberCourseCompletionsQuery.data, memberCourseCompletionsQuery.isSuccess, courses, reportType, selectedAssignment, isChallengeModeStudent]);
703
+ const assignmentsTableData = useMemo(() => memberCourseCompletionsQuery.isSuccess && Array.isArray(memberCourseCompletionsQuery.data.Items) && memberCourseCompletionsQuery.data.Items.length && assignments && assignments.length ? formatMemberAssignmentCompletions(assignments, courses, memberCourseCompletionsQuery.data.Items, isChallengeModeStudent) : [], [memberCourseCompletionsQuery.data, memberCourseCompletionsQuery.isSuccess, courses, assignments, isChallengeModeStudent]);
632
704
  let memberActivityLink = `/classrooms/${classroomId}/activity/member/${member.memberId}`;
633
705
  if (currentView === "course") {
634
706
  memberActivityLink = `${memberActivityLink}/${selectedCourse.courseId}`;
@@ -1,5 +1,13 @@
1
1
  import { calcCompletions, calcPercentageCompletion, getScoreValues } from "../../utils";
2
- export const formatMemberCourseCompletions = (courses = [], completions = [], assignmentExercises, isChallengeModeStudent = false) => {
2
+
3
+ // Helper function to calculate how many days late a submission is
4
+ const calculateDaysLate = (submissionTimestamp, dueDateMs) => {
5
+ const daysDifference = (submissionTimestamp - dueDateMs) / (1000 * 60 * 60 * 24);
6
+ if (daysDifference <= 0) return 0; // Not actually late
7
+ return Math.max(1, Math.ceil(daysDifference)); // Round up, minimum 1 day
8
+ };
9
+ export const formatMemberCourseCompletions = (courses = [], completions = [], assignmentExercises, isChallengeModeStudent = false, selectedAssignment = null, reportType = "course") => {
10
+ const dueDateMs = selectedAssignment?.dueDate ? new Date(selectedAssignment.dueDate).getTime() : null;
3
11
  const initialStatsObject = courses.reduce((obj, v) => {
4
12
  const scoreValues = getScoreValues("course", v.courseId, completions);
5
13
  obj[v.courseId] = {
@@ -60,11 +68,26 @@ export const formatMemberCourseCompletions = (courses = [], completions = [], as
60
68
  return topicObj;
61
69
  }, {});
62
70
  const formattedTopics = sectionVal.topics.reduce((topicObj, topicVal) => {
71
+ const topicCompletions = completions.filter(completion => completion.topicId === topicVal.topicId);
63
72
  topicObj[topicVal.topicId] = {
64
73
  percentComplete: topicVal.completion,
65
74
  percentCorrect: topicVal.percentCorrect,
66
75
  avgPronunciationScore: topicVal.avgPronunciationScore,
67
- completions: completions.filter(completion => completion.topicId === topicVal.topicId)
76
+ completions: topicCompletions,
77
+ ...(selectedAssignment && reportType === "assignments" && {
78
+ submittedLate: (() => {
79
+ if (!dueDateMs) return {
80
+ isLate: false
81
+ };
82
+ const earliestCompletion = topicCompletions.reduce((earliest, c) => !earliest || c.createdAt < earliest.createdAt ? c : earliest, null);
83
+ return earliestCompletion && earliestCompletion.createdAt > dueDateMs ? {
84
+ isLate: true,
85
+ daysLate: calculateDaysLate(earliestCompletion.createdAt, dueDateMs)
86
+ } : {
87
+ isLate: false
88
+ };
89
+ })()
90
+ })
68
91
  };
69
92
  return topicObj;
70
93
  }, initialTopicsObject);
@@ -72,11 +95,38 @@ export const formatMemberCourseCompletions = (courses = [], completions = [], as
72
95
  percentComplete: sectionVal.completion,
73
96
  percentCorrect: sectionVal.percentCorrect,
74
97
  avgPronunciationScore: sectionVal.avgPronunciationScore,
75
- topics: formattedTopics
98
+ topics: formattedTopics,
99
+ ...(selectedAssignment && reportType === "assignments" && {
100
+ submittedLate: (() => {
101
+ const lateTopics = Object.values(formattedTopics).filter(t => t.submittedLate?.isLate);
102
+ if (lateTopics.length === 0) return {
103
+ isLate: false
104
+ };
105
+ const maxDaysLate = Math.max(...lateTopics.map(topic => topic.submittedLate.daysLate));
106
+ return {
107
+ isLate: true,
108
+ daysLate: maxDaysLate
109
+ };
110
+ })()
111
+ })
76
112
  };
77
113
  return sectionObj;
78
114
  }, initialSectionsObject);
79
115
  previousValue[currentValue.courseId].sections = formattedSections;
116
+ if (selectedAssignment && reportType === "assignments") {
117
+ const lateSections = Object.values(formattedSections).filter(s => s.submittedLate?.isLate);
118
+ if (lateSections.length === 0) {
119
+ previousValue[currentValue.courseId].submittedLate = {
120
+ isLate: false
121
+ };
122
+ } else {
123
+ const maxDaysLate = Math.max(...lateSections.map(section => section.submittedLate.daysLate));
124
+ previousValue[currentValue.courseId].submittedLate = {
125
+ isLate: true,
126
+ daysLate: maxDaysLate
127
+ };
128
+ }
129
+ }
80
130
  return previousValue;
81
131
  }, initialStatsObject);
82
132
  return stats;
@@ -106,8 +156,21 @@ export const formatMemberAssignmentCompletions = (assignments = [], courses = []
106
156
  total: 0,
107
157
  sum: 0,
108
158
  percentage: 0
159
+ },
160
+ submittedLate: {
161
+ isLate: false
109
162
  }
110
163
  };
164
+ if (assignment.dueDate) {
165
+ const dueDateMs = new Date(assignment.dueDate).getTime();
166
+ const lateCompletion = assignmentCompletions.find(completion => completion.createdAt > dueDateMs);
167
+ if (lateCompletion) {
168
+ accumulator.submittedLate = {
169
+ isLate: true,
170
+ daysLate: calculateDaysLate(lateCompletion.createdAt, dueDateMs)
171
+ };
172
+ }
173
+ }
111
174
  Object.values(assignmentCourseCompletions).forEach(course => {
112
175
  if (course?.percentComplete) {
113
176
  accumulator.percentComplete.completed += course.percentComplete.completed || 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nualang/nualang-ui-components",
3
- "version": "0.1.1334",
3
+ "version": "0.1.1336",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "files": [