@nualang/nualang-ui-components 0.1.1333 → 0.1.1335

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.
@@ -145,9 +145,8 @@ export default function BottomBar({
145
145
  placeholder: browserSupportsSpeechRecognition && handleRecord ? isSmallScreen ? t("bot_input_mobile_placeholder") : t("bot_input_placeholder") : placeholder,
146
146
  inputProps: {
147
147
  maxLength: isChallengeBot ? 600 : 200,
148
- sx: theme => ({
148
+ sx: () => ({
149
149
  "&::placeholder": {
150
- color: theme.palette.mode === "light" ? grey[200] : grey[800],
151
150
  opacity: 1
152
151
  }
153
152
  })
@@ -729,7 +729,18 @@ function Bot({
729
729
  return !hidden ? /*#__PURE__*/_jsx(Tab, {
730
730
  id: keyId,
731
731
  label: label,
732
- disabled: disabled
732
+ disabled: disabled,
733
+ sx: {
734
+ color: "#fff !important",
735
+ "&.Mui-selected": {
736
+ color: "#fff !important"
737
+ },
738
+ "&:hover": {
739
+ ...(theme.palette.mode === "light" && {
740
+ backgroundColor: "rgba(144, 238, 144, 0.20) !important"
741
+ })
742
+ }
743
+ }
733
744
  }, keyId) : null;
734
745
  }), /*#__PURE__*/_jsx(Tooltip, {
735
746
  title: hidden ? t("show_menu") : t("hide_menu"),
@@ -758,7 +758,18 @@ function Roleplay({
758
758
  }, i) => !hidden ? /*#__PURE__*/_jsx(Tab, {
759
759
  id: i,
760
760
  label: label,
761
- disabled: disabled
761
+ disabled: disabled,
762
+ sx: {
763
+ color: "#fff !important",
764
+ "&.Mui-selected": {
765
+ color: "#fff !important"
766
+ },
767
+ "&:hover": {
768
+ ...(theme.palette.mode === "light" && {
769
+ backgroundColor: "rgba(144, 238, 144, 0.20) !important"
770
+ })
771
+ }
772
+ }
762
773
  }, i) : null), /*#__PURE__*/_jsx(Tooltip, {
763
774
  title: hidden ? t("show_menu") : t("hide_menu"),
764
775
  children: /*#__PURE__*/_jsx(IconButton, {
@@ -103,6 +103,7 @@ function AppContact({
103
103
  children: /*#__PURE__*/_jsx(TextField, {
104
104
  name: "jobTitle",
105
105
  value: jobTitle || "",
106
+ label: t("you_are_human_check"),
106
107
  onChange: handleChange,
107
108
  autoComplete: "off",
108
109
  tabIndex: -1,
@@ -97,26 +97,6 @@ function ResponsiveTabs({
97
97
  sx: {
98
98
  "@media print": {
99
99
  display: "none"
100
- },
101
- "& .MuiTab-root": {
102
- transition: theme.transitions.create(["background-color", "color"], {
103
- duration: theme.transitions.duration.shorter
104
- }),
105
- "&:hover": {
106
- backgroundColor: theme.palette.action.hover
107
- },
108
- "&.Mui-selected": {
109
- color: theme.palette.primary.main,
110
- "&:hover": {
111
- backgroundColor: theme.palette.primary.light + "20"
112
- }
113
- },
114
- "&.Mui-focusVisible": {
115
- backgroundColor: theme.palette.action.focus
116
- }
117
- },
118
- "& .MuiTabs-indicator": {
119
- height: 3
120
100
  }
121
101
  },
122
102
  children: tabs.map(({
@@ -1023,16 +1023,17 @@ export default function ViewCourse({
1023
1023
  const {
1024
1024
  classes
1025
1025
  } = useStyles();
1026
+ const safeCourse = course || {};
1026
1027
  return /*#__PURE__*/_jsxs("div", {
1027
1028
  className: classes.root,
1028
- children: [isLoading && Object.keys(course).length === 0 && /*#__PURE__*/_jsx(_Fragment, {
1029
+ children: [isLoading && Object.keys(safeCourse).length === 0 && /*#__PURE__*/_jsx(_Fragment, {
1029
1030
  children: /*#__PURE__*/_jsx(Course, {
1030
1031
  t: t,
1031
1032
  isLoading: isLoading,
1032
1033
  isStudent: isStudent,
1033
1034
  ...otherProps
1034
1035
  })
1035
- }), !isLoading && course && Object.keys(course).length && /*#__PURE__*/_jsx(_Fragment, {
1036
+ }), !isLoading && course && Object.keys(safeCourse).length && /*#__PURE__*/_jsx(_Fragment, {
1036
1037
  children: /*#__PURE__*/_jsx(Course, {
1037
1038
  t: t,
1038
1039
  isLoading: isLoading,
@@ -1043,7 +1044,7 @@ export default function ViewCourse({
1043
1044
  isStudent: isStudent,
1044
1045
  ...otherProps
1045
1046
  })
1046
- }), !isLoading && Object.keys(course).length === 0 && /*#__PURE__*/_jsx(CourseNotFound, {
1047
+ }), !isLoading && Object.keys(safeCourse).length === 0 && /*#__PURE__*/_jsx(CourseNotFound, {
1047
1048
  t: t,
1048
1049
  placeholderImageUrl: placeholderImageUrl,
1049
1050
  handleSearch: handleSearch
@@ -1323,16 +1323,17 @@ export default function ViewTopic({
1323
1323
  const {
1324
1324
  classes
1325
1325
  } = useStyles();
1326
+ const safeTopic = topic || {};
1326
1327
  return /*#__PURE__*/_jsxs("div", {
1327
1328
  className: classes.root,
1328
- children: [isLoading && Object.keys(topic).length === 0 && /*#__PURE__*/_jsx(_Fragment, {
1329
+ children: [isLoading && Object.keys(safeTopic).length === 0 && /*#__PURE__*/_jsx(_Fragment, {
1329
1330
  children: /*#__PURE__*/_jsx(Topic, {
1330
1331
  t: t,
1331
1332
  isLoading: isLoading,
1332
1333
  courseSettings: courseSettings,
1333
1334
  ...otherProps
1334
1335
  })
1335
- }), !isLoading && topic && Object.keys(topic).length && /*#__PURE__*/_jsx(_Fragment, {
1336
+ }), !isLoading && topic && Object.keys(safeTopic).length && /*#__PURE__*/_jsx(_Fragment, {
1336
1337
  children: /*#__PURE__*/_jsx(Topic, {
1337
1338
  t: t,
1338
1339
  isLoading: isLoading,
@@ -1358,7 +1359,7 @@ export default function ViewTopic({
1358
1359
  isClassroomCreator: isClassroomCreator,
1359
1360
  ...otherProps
1360
1361
  })
1361
- }), !isLoading && Object.keys(topic).length === 0 && /*#__PURE__*/_jsx(TopicNotFound, {
1362
+ }), !isLoading && Object.keys(safeTopic).length === 0 && /*#__PURE__*/_jsx(TopicNotFound, {
1362
1363
  t: t,
1363
1364
  isLoading: isLoading,
1364
1365
  handleViewCourse: handleViewCourse
@@ -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.1333",
3
+ "version": "0.1.1335",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "files": [