@nualang/nualang-ui-components 0.1.1377 → 0.1.1379

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.
@@ -227,23 +227,46 @@ function AssignmentBotSelection({
227
227
  return true;
228
228
  });
229
229
  const filteredBots = useCase === "assignment-view" || useCase === "assignment-start" ? visibleBots.filter(bot => selectedExercises.some(ex => ex.botId === bot.botId && ex.courseSectionTopicId === courseSectionTopicId)) : visibleBots;
230
- return filteredBots.map((bot, index) => /*#__PURE__*/_jsx(Bot, {
231
- bot: bot,
232
- courseSectionTopicId: courseSectionTopicId,
233
- t: t,
234
- isExerciseSelected: isExerciseSelected,
235
- handleSelectExercise: handleSelectExercise,
236
- selectedExercises: selectedExercises,
237
- useCase: useCase,
238
- assignment: assignment,
239
- lastClickedExerciseId: lastClickedExerciseId,
240
- memberCourseCompletions: memberCourseCompletions,
241
- topicProgress: topicProgress,
242
- handleRemoveExercise: handleRemoveExercise,
243
- isPreview: isPreview,
244
- isChallengeModeStudent: isChallengeModeStudent,
245
- isCreator: isCreator,
246
- isCreateMode: isCreateMode
247
- }, index));
230
+ const existingBotIds = new Set(visibleBots.map(b => b.botId));
231
+ const orphanedBotIds = useCase === "assignment-view" || useCase === "assignment-start" ? [...new Set(selectedExercises.filter(ex => ex.botId && ex.courseSectionTopicId === courseSectionTopicId && !existingBotIds.has(ex.botId)).map(ex => ex.botId))] : [];
232
+ return /*#__PURE__*/_jsxs(_Fragment, {
233
+ children: [filteredBots.map((bot, index) => /*#__PURE__*/_jsx(Bot, {
234
+ bot: bot,
235
+ courseSectionTopicId: courseSectionTopicId,
236
+ t: t,
237
+ isExerciseSelected: isExerciseSelected,
238
+ handleSelectExercise: handleSelectExercise,
239
+ selectedExercises: selectedExercises,
240
+ useCase: useCase,
241
+ assignment: assignment,
242
+ lastClickedExerciseId: lastClickedExerciseId,
243
+ memberCourseCompletions: memberCourseCompletions,
244
+ topicProgress: topicProgress,
245
+ handleRemoveExercise: handleRemoveExercise,
246
+ isPreview: isPreview,
247
+ isChallengeModeStudent: isChallengeModeStudent,
248
+ isCreator: isCreator,
249
+ isCreateMode: isCreateMode
250
+ }, index)), orphanedBotIds.map(id => /*#__PURE__*/_jsx(Box, {
251
+ sx: {
252
+ display: "flex",
253
+ alignItems: "center",
254
+ pl: 2,
255
+ py: 1.5,
256
+ ml: {
257
+ xs: "16px",
258
+ sm: "72px",
259
+ md: "76px"
260
+ },
261
+ borderLeft: "3px solid",
262
+ borderColor: "warning.main"
263
+ },
264
+ children: /*#__PURE__*/_jsx(Typography, {
265
+ variant: "body2",
266
+ color: "text.secondary",
267
+ children: t("exercise_no_longer_available")
268
+ })
269
+ }, id))]
270
+ });
248
271
  }
249
272
  export default AssignmentBotSelection;
@@ -29,7 +29,8 @@ export default function AssignmentCard({
29
29
  handleViewProgress,
30
30
  lastAssignmentFetch,
31
31
  assignedStudents = [],
32
- isChallengeModeStudent
32
+ isChallengeModeStudent,
33
+ isReadWriteModeStudent
33
34
  }) {
34
35
  const [confirm] = useConfirm(t);
35
36
  const {
@@ -223,7 +224,8 @@ export default function AssignmentCard({
223
224
  completionHelpers: completionHelpers,
224
225
  memberCourseCompletions: memberCourseCompletions,
225
226
  lastClickedExerciseId: lastClickedExerciseId,
226
- isChallengeModeStudent: isChallengeModeStudent
227
+ isChallengeModeStudent: isChallengeModeStudent,
228
+ isReadWriteModeStudent: isReadWriteModeStudent
227
229
  })
228
230
  })]
229
231
  }), /*#__PURE__*/_jsx(Grid, {
@@ -29,6 +29,7 @@ const AssignmentCardsList = ({
29
29
  assignmentMembersById = {},
30
30
  isLoadingAssignments,
31
31
  isChallengeModeStudent,
32
+ isReadWriteModeStudent,
32
33
  scrollableTarget = null
33
34
  }) => {
34
35
  const [lastClickedExerciseId, setLastClickedExerciseId] = useState(null);
@@ -345,7 +346,8 @@ const AssignmentCardsList = ({
345
346
  memberId: memberId,
346
347
  handleViewProgress: handleViewProgress,
347
348
  assignedStudents: assignmentMembersById[assignment.assignmentId] || [],
348
- isChallengeModeStudent: isChallengeModeStudent
349
+ isChallengeModeStudent: isChallengeModeStudent,
350
+ isReadWriteModeStudent: isReadWriteModeStudent
349
351
  }, assignment.assignmentId))
350
352
  })]
351
353
  });
@@ -32,7 +32,8 @@ function Course({
32
32
  lastClickedExerciseId,
33
33
  handleRemoveExercise,
34
34
  isPreview,
35
- isChallengeModeStudent
35
+ isChallengeModeStudent,
36
+ isReadWriteModeStudent
36
37
  }) {
37
38
  const numOfIds = lastClickedExerciseId ? lastClickedExerciseId.split("|") : [];
38
39
  const [open, setOpen] = useState(false);
@@ -88,11 +89,11 @@ function Course({
88
89
  }
89
90
  });
90
91
  if (courseSections && memberCourseCompletions && selectedExercises) {
91
- const sectionsWithProgress = calcCompletions(courseSections.filter(section => assignedSectionIds.has(section.sectionId)), memberCourseCompletions, selectedExercises, isChallengeModeStudent);
92
+ const sectionsWithProgress = calcCompletions(courseSections.filter(section => assignedSectionIds.has(section.sectionId)), memberCourseCompletions, selectedExercises, isChallengeModeStudent, isReadWriteModeStudent);
92
93
  const percentCompletion = calcPercentageCompletion(sectionsWithProgress);
93
94
  setCourseSectionCompletion(percentCompletion);
94
95
  }
95
- }, [courseSections, memberCourseCompletions, selectedExercises]);
96
+ }, [courseSections, memberCourseCompletions, selectedExercises, isChallengeModeStudent, isReadWriteModeStudent]);
96
97
  return /*#__PURE__*/_jsx(_Fragment, {
97
98
  children: useCase === "assignment-select" ? /*#__PURE__*/_jsxs(List, {
98
99
  disablePadding: true,
@@ -178,7 +179,8 @@ function Course({
178
179
  completionHelpers: completionHelpers,
179
180
  memberCourseCompletions: memberCourseCompletions,
180
181
  lastClickedExerciseId: lastClickedExerciseId,
181
- isChallengeModeStudent: isChallengeModeStudent
182
+ isChallengeModeStudent: isChallengeModeStudent,
183
+ isReadWriteModeStudent: isReadWriteModeStudent
182
184
  })
183
185
  })
184
186
  })]
@@ -209,7 +211,8 @@ function Course({
209
211
  course: course,
210
212
  handleRemoveExercise: handleRemoveExercise,
211
213
  isPreview: isPreview,
212
- isChallengeModeStudent: isChallengeModeStudent
214
+ isChallengeModeStudent: isChallengeModeStudent,
215
+ isReadWriteModeStudent: isReadWriteModeStudent
213
216
  })
214
217
  })
215
218
  });
@@ -237,7 +240,8 @@ function AssignmentCourseSelection({
237
240
  lastClickedExerciseId,
238
241
  handleRemoveExercise,
239
242
  isPreview,
240
- isChallengeModeStudent
243
+ isChallengeModeStudent,
244
+ isReadWriteModeStudent
241
245
  }) {
242
246
  return /*#__PURE__*/_jsx("div", {
243
247
  children: courses.map((course, index) => /*#__PURE__*/_jsx(Course, {
@@ -264,7 +268,8 @@ function AssignmentCourseSelection({
264
268
  lastClickedExerciseId: lastClickedExerciseId,
265
269
  handleRemoveExercise: handleRemoveExercise,
266
270
  isPreview: isPreview,
267
- isChallengeModeStudent: isChallengeModeStudent
271
+ isChallengeModeStudent: isChallengeModeStudent,
272
+ isReadWriteModeStudent: isReadWriteModeStudent
268
273
  }, course.courseId || index))
269
274
  });
270
275
  }
@@ -888,15 +888,16 @@ function SectionList({
888
888
  handleRemoveExercise,
889
889
  isPreview,
890
890
  isChallengeModeStudent,
891
+ isReadWriteModeStudent,
891
892
  ...otherProps
892
893
  }) {
893
894
  const [courseSectionWithProgress, setCourseSectionWithProgress] = useState([]);
894
895
  useEffect(() => {
895
896
  if (sections && memberCourseCompletions && selectedExercises) {
896
- const sectionsWithProgress = calcCompletions(sections, memberCourseCompletions, selectedExercises, isChallengeModeStudent);
897
+ const sectionsWithProgress = calcCompletions(sections, memberCourseCompletions, selectedExercises, isChallengeModeStudent, isReadWriteModeStudent);
897
898
  setCourseSectionWithProgress(sectionsWithProgress);
898
899
  }
899
- }, [sections, memberCourseCompletions, selectedExercises, isChallengeModeStudent]);
900
+ }, [sections, memberCourseCompletions, selectedExercises, isChallengeModeStudent, isReadWriteModeStudent]);
900
901
  const courseSections = courseSectionWithProgress && courseSectionWithProgress.length ? courseSectionWithProgress : sections;
901
902
  return /*#__PURE__*/_jsx(_Fragment, {
902
903
  children: courseSections.length > 0 ? courseSections.map((section, index) => {
@@ -967,6 +968,7 @@ function AssignmentExerciseSelection({
967
968
  handleRemoveExercise,
968
969
  isPreview,
969
970
  isChallengeModeStudent,
971
+ isReadWriteModeStudent,
970
972
  ...otherProps
971
973
  }) {
972
974
  if (isLoading) {
@@ -998,6 +1000,7 @@ function AssignmentExerciseSelection({
998
1000
  handleRemoveExercise: handleRemoveExercise,
999
1001
  isPreview: isPreview,
1000
1002
  isChallengeModeStudent: isChallengeModeStudent,
1003
+ isReadWriteModeStudent: isReadWriteModeStudent,
1001
1004
  ...otherProps
1002
1005
  })
1003
1006
  });
@@ -1030,6 +1033,7 @@ function AssignmentExerciseSelection({
1030
1033
  handleRemoveExercise: handleRemoveExercise,
1031
1034
  isPreview: isPreview,
1032
1035
  isChallengeModeStudent: isChallengeModeStudent,
1036
+ isReadWriteModeStudent: isReadWriteModeStudent,
1033
1037
  ...otherProps
1034
1038
  })
1035
1039
  });
@@ -21,7 +21,8 @@ function AssignmentExerciseSelector({
21
21
  lastClickedExerciseId,
22
22
  handleRemoveExercise,
23
23
  isPreview,
24
- isChallengeModeStudent
24
+ isChallengeModeStudent,
25
+ isReadWriteModeStudent
25
26
  }) {
26
27
  return /*#__PURE__*/_jsx(Box, {
27
28
  children: /*#__PURE__*/_jsx(AssignmentCourseSelection, {
@@ -44,7 +45,8 @@ function AssignmentExerciseSelector({
44
45
  lastClickedExerciseId: lastClickedExerciseId,
45
46
  handleRemoveExercise: handleRemoveExercise,
46
47
  isPreview: isPreview,
47
- isChallengeModeStudent: isChallengeModeStudent
48
+ isChallengeModeStudent: isChallengeModeStudent,
49
+ isReadWriteModeStudent: isReadWriteModeStudent
48
50
  })
49
51
  });
50
52
  }
@@ -418,23 +418,46 @@ export default function AssignmentPhraseListSelection({
418
418
  preferred_username,
419
419
  isCreator
420
420
  }) {
421
- const selectedPhraseLists = new Set(selectedExercises?.map(exercise => exercise.phraseListId) || []);
422
- const filteredPhraseLists = !viewOnly ? phraseLists : phraseLists.filter(phraseList => selectedPhraseLists.has(phraseList.phraseListId));
423
- return filteredPhraseLists.map((phraseList, index) => /*#__PURE__*/_jsx(PhraseList, {
424
- t: t,
425
- phraseList: phraseList,
426
- courseSectionTopicId: courseSectionTopicId,
427
- assignment: assignment,
428
- useCase: useCase,
429
- selectedExercises: selectedExercises,
430
- handleSelectExercise: handleSelectExercise,
431
- handleRemoveExercise: handleRemoveExercise,
432
- isPreview: isPreview,
433
- viewOnly: viewOnly,
434
- progressHelpers: progressHelpers,
435
- completionHelpers: completionHelpers,
436
- username: username,
437
- preferred_username: preferred_username,
438
- isCreator: isCreator
439
- }, index));
421
+ const selectedPhraseListIds = new Set(selectedExercises?.map(exercise => exercise.phraseListId) || []);
422
+ const filteredPhraseLists = !viewOnly ? phraseLists : phraseLists.filter(phraseList => selectedPhraseListIds.has(phraseList.phraseListId));
423
+ const existingPhraseListIds = new Set(phraseLists.map(pl => pl.phraseListId));
424
+ const orphanedPhraseListIds = useCase === "assignment-view" || useCase === "assignment-start" ? [...new Set((selectedExercises || []).filter(e => e.phraseListId && e.courseSectionTopicId === courseSectionTopicId && !existingPhraseListIds.has(e.phraseListId)).map(e => e.phraseListId))] : [];
425
+ return /*#__PURE__*/_jsxs(_Fragment, {
426
+ children: [filteredPhraseLists.map((phraseList, index) => /*#__PURE__*/_jsx(PhraseList, {
427
+ t: t,
428
+ phraseList: phraseList,
429
+ courseSectionTopicId: courseSectionTopicId,
430
+ assignment: assignment,
431
+ useCase: useCase,
432
+ selectedExercises: selectedExercises,
433
+ handleSelectExercise: handleSelectExercise,
434
+ handleRemoveExercise: handleRemoveExercise,
435
+ isPreview: isPreview,
436
+ viewOnly: viewOnly,
437
+ progressHelpers: progressHelpers,
438
+ completionHelpers: completionHelpers,
439
+ username: username,
440
+ preferred_username: preferred_username,
441
+ isCreator: isCreator
442
+ }, index)), orphanedPhraseListIds.map(id => /*#__PURE__*/_jsx(Box, {
443
+ sx: {
444
+ display: "flex",
445
+ alignItems: "center",
446
+ pl: 2,
447
+ py: 1.5,
448
+ ml: {
449
+ xs: "16px",
450
+ sm: "72px",
451
+ md: "76px"
452
+ },
453
+ borderLeft: "3px solid",
454
+ borderColor: "warning.main"
455
+ },
456
+ children: /*#__PURE__*/_jsx(Typography, {
457
+ variant: "body2",
458
+ color: "text.secondary",
459
+ children: t("exercise_no_longer_available")
460
+ })
461
+ }, id))]
462
+ });
440
463
  }
@@ -1,5 +1,5 @@
1
1
  import { useState, useEffect } from "react";
2
- import { Box, ListItemText, ListItemButton, Avatar, ListItemIcon, Tooltip, Collapse, Checkbox, Badge, IconButton } from "@mui/material";
2
+ import { Box, ListItemText, ListItemButton, Avatar, ListItemIcon, Tooltip, Collapse, Checkbox, Badge, IconButton, Typography } from "@mui/material";
3
3
  import ClearIcon from "@mui/icons-material/Clear";
4
4
  import MessageIcon from "@mui/icons-material/Message";
5
5
  import RateReviewIcon from "@mui/icons-material/RateReview";
@@ -440,24 +440,47 @@ function AssignmentRoleplaySelection({
440
440
  const filteredCompletions = Array.isArray(memberCourseCompletions) && memberCourseCompletions.length > 0 ? memberCourseCompletions.filter(completion => completion.courseSectionTopicId === courseSectionTopicId) : [];
441
441
  const selectedRoleplayIds = new Set(selectedExercises.map(sE => sE.roleplayId));
442
442
  const filteredRoleplays = !viewOnly ? roleplays : roleplays.filter(e => selectedRoleplayIds.has(e.roleplayId));
443
- return filteredRoleplays.map(roleplay => /*#__PURE__*/_jsx(Roleplay, {
444
- roleplay: roleplay,
445
- isRoleplaySelected: isRoleplaySelected,
446
- setIsRoleplaySelected: setIsRoleplaySelected,
447
- courseSectionTopicId: courseSectionTopicId,
448
- t: t,
449
- isExerciseSelected: isExerciseSelected,
450
- handleSelectExercise: handleSelectExercise,
451
- selectedExercises: selectedExercises,
452
- useCase: useCase,
453
- assignment: assignment,
454
- memberCourseCompletions: filteredCompletions,
455
- topicProgress: topicProgress,
456
- topics: topics,
457
- isCreator: isCreator,
458
- lastClickedExerciseId: lastClickedExerciseId,
459
- handleRemoveExercise: handleRemoveExercise,
460
- isPreview: isPreview
461
- }, roleplay.roleplayId));
443
+ const existingRoleplayIds = new Set(roleplays.map(r => r.roleplayId));
444
+ const orphanedRoleplayIds = useCase === "assignment-view" || useCase === "assignment-start" ? [...new Set(selectedExercises.filter(e => e.roleplayId && e.courseSectionTopicId === courseSectionTopicId && !existingRoleplayIds.has(e.roleplayId)).map(e => e.roleplayId))] : [];
445
+ return /*#__PURE__*/_jsxs(_Fragment, {
446
+ children: [filteredRoleplays.map(roleplay => /*#__PURE__*/_jsx(Roleplay, {
447
+ roleplay: roleplay,
448
+ isRoleplaySelected: isRoleplaySelected,
449
+ setIsRoleplaySelected: setIsRoleplaySelected,
450
+ courseSectionTopicId: courseSectionTopicId,
451
+ t: t,
452
+ isExerciseSelected: isExerciseSelected,
453
+ handleSelectExercise: handleSelectExercise,
454
+ selectedExercises: selectedExercises,
455
+ useCase: useCase,
456
+ assignment: assignment,
457
+ memberCourseCompletions: filteredCompletions,
458
+ topicProgress: topicProgress,
459
+ topics: topics,
460
+ isCreator: isCreator,
461
+ lastClickedExerciseId: lastClickedExerciseId,
462
+ handleRemoveExercise: handleRemoveExercise,
463
+ isPreview: isPreview
464
+ }, roleplay.roleplayId)), orphanedRoleplayIds.map(id => /*#__PURE__*/_jsx(Box, {
465
+ sx: {
466
+ display: "flex",
467
+ alignItems: "center",
468
+ pl: 2,
469
+ py: 1.5,
470
+ ml: {
471
+ xs: "16px",
472
+ sm: "72px",
473
+ md: "76px"
474
+ },
475
+ borderLeft: "3px solid",
476
+ borderColor: "warning.main"
477
+ },
478
+ children: /*#__PURE__*/_jsx(Typography, {
479
+ variant: "body2",
480
+ color: "text.secondary",
481
+ children: t("exercise_no_longer_available")
482
+ })
483
+ }, id))]
484
+ });
462
485
  }
463
486
  export default AssignmentRoleplaySelection;
@@ -22,7 +22,8 @@ function AssignmentSelectExercise({
22
22
  completionHelpers,
23
23
  isCreator,
24
24
  memberCourseCompletions,
25
- isChallengeModeStudent
25
+ isChallengeModeStudent,
26
+ isReadWriteModeStudent
26
27
  }) {
27
28
  const [isExerciseSelected, setIsExerciseSelected] = useState(false);
28
29
  const handleSelectExercise = (exercises = []) => {
@@ -101,7 +102,8 @@ function AssignmentSelectExercise({
101
102
  completionHelpers: completionHelpers,
102
103
  isCreator: isCreator,
103
104
  memberCourseCompletions: memberCourseCompletions,
104
- isChallengeModeStudent: isChallengeModeStudent
105
+ isChallengeModeStudent: isChallengeModeStudent,
106
+ isReadWriteModeStudent: isReadWriteModeStudent
105
107
  })
106
108
  })]
107
109
  });
@@ -743,10 +743,10 @@ function Translator({
743
743
  wordList: currentPhrase ? removeExtraWhiteSpaces(currentPhrase.phrase).split(" ") : [],
744
744
  disableTranslation: false,
745
745
  isMessage: false,
746
- muteSound: isReadWriteModeStudent ? true : muteSound,
746
+ muteSound: muteSound,
747
747
  t: t,
748
748
  languageTag: languageTag
749
- }), learnLang !== "irish" && !isReadWriteModeStudent && /*#__PURE__*/_jsx(Box, {
749
+ }), learnLang !== "irish" && /*#__PURE__*/_jsx(Box, {
750
750
  children: /*#__PURE__*/_jsx(WaveFormLite, {
751
751
  src: pollyResult ? pollyResult : "",
752
752
  controls: false,
@@ -21,10 +21,6 @@ function CreateTopic({
21
21
  learnLang: "",
22
22
  tags: [],
23
23
  visibility: "private",
24
- shuffleEnabled: "enabled",
25
- voice: "",
26
- voiceLanguageCode: "",
27
- voicePitch: "",
28
24
  topicCode: "",
29
25
  notes: ""
30
26
  },
@@ -18,9 +18,6 @@ const useStyles = makeStyles()(theme => ({
18
18
  },
19
19
  formContent: {
20
20
  padding: theme.spacing(3)
21
- },
22
- group: {
23
- flexDirection: "row"
24
21
  }
25
22
  }));
26
23
  export default function UpdateTopic({
@@ -31,10 +28,6 @@ export default function UpdateTopic({
31
28
  description: "",
32
29
  difficulty: "",
33
30
  visibility: "",
34
- shuffleEnabled: "enabled",
35
- voice: "",
36
- voiceLanguageCode: "",
37
- voicePitch: "",
38
31
  notes: "",
39
32
  topicGoal: "",
40
33
  tags: []
@@ -45,10 +38,6 @@ export default function UpdateTopic({
45
38
  subscription = {
46
39
  isUpgradePossible: true
47
40
  },
48
- voices = {},
49
- handleSpeak,
50
- learnLang,
51
- isUserInternal,
52
41
  verificationStatus
53
42
  }) {
54
43
  const {
@@ -59,10 +48,6 @@ export default function UpdateTopic({
59
48
  description,
60
49
  difficulty,
61
50
  visibility,
62
- shuffleEnabled,
63
- voice,
64
- voicePitch,
65
- voiceLanguageCode,
66
51
  picture,
67
52
  banner,
68
53
  tags,
@@ -81,7 +66,6 @@ export default function UpdateTopic({
81
66
  description,
82
67
  difficulty,
83
68
  visibility,
84
- shuffleEnabled,
85
69
  picture,
86
70
  topicGoal,
87
71
  tags
@@ -142,11 +126,7 @@ export default function UpdateTopic({
142
126
  })
143
127
  }), /*#__PURE__*/_jsx("br", {}), /*#__PURE__*/_jsx(Formik, {
144
128
  initialValues: {
145
- visibility,
146
- shuffleEnabled,
147
- voice,
148
- voiceLanguageCode,
149
- voicePitch
129
+ visibility
150
130
  },
151
131
  enableReinitialize: enableReinitialize,
152
132
  validationSchema: TopicSettings.validationSchema,
@@ -186,12 +166,7 @@ export default function UpdateTopic({
186
166
  dirty: dirty,
187
167
  values: values,
188
168
  ...otherProps,
189
- isUpdating: true,
190
- subscription: subscription,
191
- voiceOptions: voices,
192
- handleSpeak: handleSpeak,
193
- learnLang: learnLang,
194
- isUserInternal: isUserInternal
169
+ subscription: subscription
195
170
  })
196
171
  }), /*#__PURE__*/_jsx(Divider, {}), /*#__PURE__*/_jsx(CardActions, {
197
172
  className: classes.padding,
@@ -1019,10 +1019,6 @@ function Topic({
1019
1019
  onSubmit: handleUpdateTopic,
1020
1020
  fileSizeLimit: fileSizeLimit,
1021
1021
  subscription: subscription,
1022
- voices: voices,
1023
- handleSpeak: handleSpeak,
1024
- learnLang: learnLang,
1025
- isUserInternal: isUserInternal,
1026
1022
  verificationStatus: verificationStatus
1027
1023
  })
1028
1024
  })]
@@ -48,6 +48,8 @@ export default function ProgressTableRow(props) {
48
48
  assignedLabels
49
49
  } = member;
50
50
  const isChallengeModeStudent = assignedLabels?.isChallengeModeStudent;
51
+ const isReadWriteModeStudent = assignedLabels?.isReadWriteModeStudent;
52
+ const assignmentDueDateMs = selectedAssignment?.dueDate ? new Date(selectedAssignment.dueDate).getTime() : null;
51
53
  const memberCourseCompletionsQuery = useQuery({
52
54
  queryKey: ["memberCourseProgress", memberId, courseIds],
53
55
  queryFn: async () => {
@@ -61,8 +63,8 @@ export default function ProgressTableRow(props) {
61
63
  error,
62
64
  isLoading
63
65
  } = memberCourseCompletionsQuery;
64
- const data = 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]);
65
- 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]);
66
+ const data = 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, isReadWriteModeStudent) : {}, [memberCourseCompletionsQuery.data, memberCourseCompletionsQuery.isSuccess, courses, reportType, selectedAssignment, isChallengeModeStudent, isReadWriteModeStudent]);
67
+ const assignmentsTableData = useMemo(() => memberCourseCompletionsQuery.isSuccess && Array.isArray(memberCourseCompletionsQuery.data.Items) && memberCourseCompletionsQuery.data.Items.length && assignments && assignments.length ? formatMemberAssignmentCompletions(assignments, courses, memberCourseCompletionsQuery.data.Items, isChallengeModeStudent, isReadWriteModeStudent) : {}, [memberCourseCompletionsQuery.data, memberCourseCompletionsQuery.isSuccess, courses, assignments, isChallengeModeStudent, isReadWriteModeStudent]);
66
68
  let memberActivityLink = `/classrooms/${classroomId}/activity/member/${member.memberId}`;
67
69
  if (currentView === "course") {
68
70
  memberActivityLink = `${memberActivityLink}/${selectedCourse.courseId}`;
@@ -205,7 +207,8 @@ export default function ProgressTableRow(props) {
205
207
  phraseListId: phraseList.phraseListId,
206
208
  isLoading: isLoading,
207
209
  error: error,
208
- index: i
210
+ index: i,
211
+ assignmentDueDateMs: assignmentDueDateMs
209
212
  }, `phrase-list-cell-${i}`);
210
213
  }), (currentView === "phrase-list" || currentView === "assignment-phrase-list") && ["translation", "listening", "pronunciation"].map((exercise, i) => {
211
214
  const topicData = getTopicData(data, selectedTopic, selectedCourse, selectedSection);
@@ -220,7 +223,8 @@ export default function ProgressTableRow(props) {
220
223
  sectionId: selectedSection?.sectionId || selectedTopic?.courseSectionId?.split("|")[1],
221
224
  topicId: selectedTopic?.topicId,
222
225
  memberId: member.memberId,
223
- index: i
226
+ index: i,
227
+ assignmentDueDateMs: assignmentDueDateMs
224
228
  }, `phrase-list-exercise-cell-${i}`);
225
229
  }), (currentView === "topic" || currentView === "assignment-topic") && tableRoleplays.map((roleplay, i) => {
226
230
  const topicData = getTopicData(data, selectedTopic, selectedCourse, selectedSection);
@@ -235,7 +239,9 @@ export default function ProgressTableRow(props) {
235
239
  sectionId: selectedSection?.sectionId || selectedTopic?.courseSectionId?.split("|")[1],
236
240
  topicId: selectedTopic?.topicId,
237
241
  memberId: member.memberId,
238
- index: i
242
+ index: i,
243
+ assignmentDueDateMs: assignmentDueDateMs,
244
+ showDaysLate: false
239
245
  }, `roleplay-cell-${i}`);
240
246
  }), (currentView === "topic" || currentView === "assignment-topic") && tableBots.map((bot, i) => {
241
247
  const topicData = getTopicData(data, selectedTopic, selectedCourse, selectedSection);
@@ -252,7 +258,9 @@ export default function ProgressTableRow(props) {
252
258
  sectionId: selectedSection?.sectionId || selectedTopic?.courseSectionId?.split("|")[1],
253
259
  topicId: selectedTopic?.topicId,
254
260
  memberId: member.memberId,
255
- index: i
261
+ index: i,
262
+ assignmentDueDateMs: assignmentDueDateMs,
263
+ showDaysLate: false
256
264
  }, `bot-cell-${i}`);
257
265
  }), (currentView === "roleplay" || currentView === "assignment-roleplay") && tableRoleplayGames?.length > 0 && tableRoleplayGames.map((game, i) => {
258
266
  const topicData = getTopicData(data, selectedTopic, selectedCourse, selectedSection);
@@ -267,7 +275,8 @@ export default function ProgressTableRow(props) {
267
275
  sectionId: selectedSection?.sectionId || selectedTopic?.courseSectionId?.split("|")[1],
268
276
  topicId: selectedTopic?.topicId,
269
277
  memberId: member.memberId,
270
- index: i
278
+ index: i,
279
+ assignmentDueDateMs: assignmentDueDateMs
271
280
  }, `roleplay-game-cell-${i}`);
272
281
  }), currentView === "assignments" && assignments.map((assignment, i) => /*#__PURE__*/_jsx(AssignmentCellData, {
273
282
  t: t,
@@ -18,6 +18,7 @@ export function ExerciseCell({
18
18
  backgroundColor,
19
19
  submittedLate = false,
20
20
  daysLate = 0,
21
+ showDaysLate = true,
21
22
  t
22
23
  }) {
23
24
  return /*#__PURE__*/_jsx(Box, {
@@ -41,9 +42,9 @@ export function ExerciseCell({
41
42
  fontWeight: "bold"
42
43
  }),
43
44
  size: "small",
44
- label: t("days_late", {
45
+ label: showDaysLate ? t("days_late", {
45
46
  count: daysLate
46
- })
47
+ }) : t("late_submission")
47
48
  })
48
49
  })]
49
50
  })
@@ -219,6 +220,12 @@ export function AssignmentCellData(props) {
219
220
  index
220
221
  } = props;
221
222
  const cellData = getCellDataForFilter(data, filter);
223
+ let submittedLate = false;
224
+ let daysLate = null;
225
+ if (data?.submittedLate?.isLate === true) {
226
+ submittedLate = true;
227
+ daysLate = data.submittedLate.daysLate;
228
+ }
222
229
  return /*#__PURE__*/_jsx(DataCellContainer, {
223
230
  t: t,
224
231
  data: cellData,
@@ -226,7 +233,9 @@ export function AssignmentCellData(props) {
226
233
  isNotAssigned: isNotAssigned,
227
234
  error: error,
228
235
  memberActivityLink: memberActivityLink,
229
- index: index
236
+ index: index,
237
+ submittedLate: submittedLate,
238
+ daysLate: daysLate
230
239
  });
231
240
  }
232
241
  export function CourseCellData(props) {
@@ -268,13 +277,21 @@ export function SectionCellData(props) {
268
277
  index
269
278
  } = props;
270
279
  const cellData = getCellDataForFilter(data, filter);
280
+ let submittedLate = false;
281
+ let daysLate = null;
282
+ if (data?.submittedLate?.isLate === true) {
283
+ submittedLate = true;
284
+ daysLate = data.submittedLate.daysLate;
285
+ }
271
286
  return /*#__PURE__*/_jsx(DataCellContainer, {
272
287
  t: t,
273
288
  data: cellData,
274
289
  isLoading: isLoading,
275
290
  error: error,
276
291
  memberActivityLink: memberActivityLink,
277
- index: index
292
+ index: index,
293
+ submittedLate: submittedLate,
294
+ daysLate: daysLate
278
295
  });
279
296
  }
280
297
  export function TopicCellData(props) {
@@ -321,7 +338,9 @@ export function ExerciseCellData(props) {
321
338
  phraseListId,
322
339
  index,
323
340
  isChallengeModeStudent = false,
324
- botIsChallengeModeOnly = false
341
+ botIsChallengeModeOnly = false,
342
+ assignmentDueDateMs = null,
343
+ showDaysLate = true
325
344
  } = props;
326
345
  let icon = null;
327
346
  let type = null;
@@ -329,10 +348,6 @@ export function ExerciseCellData(props) {
329
348
  let memberActivityLink = null;
330
349
  let submittedLate = false;
331
350
  let daysLate = null;
332
- if (data?.submittedLate?.isLate === true) {
333
- submittedLate = true;
334
- daysLate = data.submittedLate.daysLate;
335
- }
336
351
  if (isLoading) return /*#__PURE__*/_jsx(DataCell, {
337
352
  t: t,
338
353
  data: "",
@@ -360,6 +375,13 @@ export function ExerciseCellData(props) {
360
375
  } else {
361
376
  completions = data?.completions.filter(completion => completion.exercise === exercise);
362
377
  }
378
+ if (assignmentDueDateMs && completions?.length > 0) {
379
+ const earliestCompletion = [...completions].sort((a, b) => a.createdAt - b.createdAt)[0];
380
+ if (earliestCompletion.createdAt > assignmentDueDateMs) {
381
+ submittedLate = true;
382
+ daysLate = Math.max(1, Math.ceil((earliestCompletion.createdAt - assignmentDueDateMs) / (1000 * 60 * 60 * 24)));
383
+ }
384
+ }
363
385
  if (exercise === "roleplays" && roleplayId && completions?.length > 0) {
364
386
  const roleplayCompletionData = data?.percentComplete["roleplays"].completedCounts.find(c => c.roleplayId === roleplayId);
365
387
  icon = /*#__PURE__*/_jsx(Typography, {
@@ -418,7 +440,8 @@ export function ExerciseCellData(props) {
418
440
  memberActivityLink: memberActivityLink,
419
441
  index: index,
420
442
  submittedLate: submittedLate,
421
- daysLate: daysLate
443
+ daysLate: daysLate,
444
+ showDaysLate: showDaysLate
422
445
  });
423
446
  }
424
447
  export function RoleplayCellData({
@@ -461,7 +484,8 @@ export function PhraseListCellData({
461
484
  phraseListId,
462
485
  isLoading,
463
486
  error,
464
- index
487
+ index,
488
+ assignmentDueDateMs = null
465
489
  }) {
466
490
  if (isLoading) return /*#__PURE__*/_jsx(DataCell, {
467
491
  t: t,
@@ -474,8 +498,10 @@ export function PhraseListCellData({
474
498
  index: index
475
499
  });
476
500
  const phraseListExercises = ["translation", "listening", "pronunciation"];
477
- const completedCount = phraseListExercises.filter(exercise => data?.completions?.some(c => c.exercise === exercise && (c.phraseListId === phraseListId || !c.phraseListId && phraseListId?.startsWith('leg-')))).length;
501
+ const phraseListCompletions = data?.completions?.filter(c => phraseListExercises.includes(c.exercise) && (c.phraseListId === phraseListId || !c.phraseListId && phraseListId?.startsWith('leg-'))) ?? [];
502
+ const completedCount = phraseListExercises.filter(exercise => phraseListCompletions.some(c => c.exercise === exercise)).length;
478
503
  const total = phraseListExercises.length;
504
+ const submittedLate = assignmentDueDateMs != null && phraseListCompletions.some(c => c.createdAt > assignmentDueDateMs);
479
505
  let color;
480
506
  if (completedCount === total) color = green[400];else if (completedCount > 0) color = yellow[400];else color = red[400];
481
507
  return /*#__PURE__*/_jsx(ExerciseCell, {
@@ -485,6 +511,8 @@ export function PhraseListCellData({
485
511
  children: `${completedCount} / ${total}`
486
512
  }),
487
513
  backgroundColor: color,
488
- index: index
514
+ index: index,
515
+ submittedLate: submittedLate,
516
+ showDaysLate: false
489
517
  });
490
518
  }
@@ -268,71 +268,60 @@ describe("TopicCellData", () => {
268
268
  });
269
269
 
270
270
  // ---------------------------------------------------------------------------
271
- // ExerciseCellData — reads submittedLate from data.submittedLate; shows
272
- // the daysLate count chip inside ExerciseCell (not the "late_submission" string)
271
+ // ExerciseCellData — computes submittedLate from assignmentDueDateMs vs
272
+ // completion timestamps; shows the daysLate count chip inside ExerciseCell
273
273
  // ---------------------------------------------------------------------------
274
274
  describe("ExerciseCellData", () => {
275
275
  const baseCourseId = "1";
276
276
  const baseSectionId = "54321";
277
277
  const baseTopicId = "154321";
278
- const makeData = submittedLate => ({
278
+ const completionAt = 1578268085029;
279
+ const dueDateBefore = completionAt - 5 * 24 * 60 * 60 * 1000; // 5 days before completion
280
+ const dueDateAfter = completionAt + 1 * 24 * 60 * 60 * 1000; // 1 day after completion
281
+
282
+ const makeData = () => ({
279
283
  completions: [{
280
284
  exercise: "translation",
281
- createdAt: 1578268085029
285
+ createdAt: completionAt
282
286
  }],
283
287
  percentComplete: {
284
288
  translation: {
285
289
  completedCount: 1
286
290
  }
287
- },
288
- submittedLate
291
+ }
289
292
  });
290
- it("shows days_late chip when data.submittedLate.isLate is true", () => {
293
+ it("shows days_late chip when completion is after assignmentDueDateMs", () => {
291
294
  wrap(/*#__PURE__*/_jsx(ExerciseCellData, {
292
295
  t: t,
293
- data: makeData({
294
- isLate: true,
295
- daysLate: 5
296
- }),
296
+ data: makeData(),
297
297
  exercise: "translation",
298
298
  courseId: baseCourseId,
299
299
  sectionId: baseSectionId,
300
300
  topicId: baseTopicId,
301
301
  memberId: "490230",
302
- index: 0
302
+ index: 0,
303
+ assignmentDueDateMs: dueDateBefore
303
304
  }));
304
305
  expect(screen.getByText("5 days_late")).toBeTruthy();
305
306
  });
306
- it("does not show days_late chip when data.submittedLate.isLate is false", () => {
307
+ it("does not show days_late chip when completion is before assignmentDueDateMs", () => {
307
308
  wrap(/*#__PURE__*/_jsx(ExerciseCellData, {
308
309
  t: t,
309
- data: makeData({
310
- isLate: false
311
- }),
310
+ data: makeData(),
312
311
  exercise: "translation",
313
312
  courseId: baseCourseId,
314
313
  sectionId: baseSectionId,
315
314
  topicId: baseTopicId,
316
315
  memberId: "490230",
317
- index: 0
316
+ index: 0,
317
+ assignmentDueDateMs: dueDateAfter
318
318
  }));
319
319
  expect(screen.queryByText(/days_late/i)).toBeNull();
320
320
  });
321
- it("does not show days_late chip when data has no submittedLate field", () => {
322
- const data = {
323
- completions: [{
324
- exercise: "translation",
325
- createdAt: 1578268085029
326
- }],
327
- percentComplete: {
328
- translation: {
329
- completedCount: 1
330
- }
331
- }
332
- };
321
+ it("does not show days_late chip when assignmentDueDateMs is not provided", () => {
333
322
  wrap(/*#__PURE__*/_jsx(ExerciseCellData, {
334
323
  t: t,
335
- data: data,
324
+ data: makeData(),
336
325
  exercise: "translation",
337
326
  courseId: baseCourseId,
338
327
  sectionId: baseSectionId,
@@ -30,12 +30,12 @@ export function descendingComparator(a, b, property, opts = {}) {
30
30
  const aCompletions = queryClient.getQueryData(["memberCourseProgress", a.memberId, currentCourseIds])?.Items ?? [];
31
31
  const bCompletions = queryClient.getQueryData(["memberCourseProgress", b.memberId, currentCourseIds])?.Items ?? [];
32
32
  if (currentView === "assignments") {
33
- a.completions = opts.formatMemberAssignmentCompletions(assignments, courses, aCompletions, a?.assignedLabels?.isChallengeModeStudent);
34
- b.completions = opts.formatMemberAssignmentCompletions(assignments, courses, bCompletions, b?.assignedLabels?.isChallengeModeStudent);
33
+ a.completions = opts.formatMemberAssignmentCompletions(assignments, courses, aCompletions, a?.assignedLabels?.isChallengeModeStudent, a?.assignedLabels?.isReadWriteModeStudent);
34
+ b.completions = opts.formatMemberAssignmentCompletions(assignments, courses, bCompletions, b?.assignedLabels?.isChallengeModeStudent, b?.assignedLabels?.isReadWriteModeStudent);
35
35
  } else {
36
36
  const assignmentEx = reportType === "assignments" && selectedAssignment ? selectedAssignment?.exercises : null;
37
- a.completions = opts.formatMemberCourseCompletions(courses, aCompletions, assignmentEx, a?.assignedLabels?.isChallengeModeStudent);
38
- b.completions = opts.formatMemberCourseCompletions(courses, bCompletions, assignmentEx, b?.assignedLabels?.isChallengeModeStudent);
37
+ a.completions = opts.formatMemberCourseCompletions(courses, aCompletions, assignmentEx, a?.assignedLabels?.isChallengeModeStudent, null, "course", a?.assignedLabels?.isReadWriteModeStudent);
38
+ b.completions = opts.formatMemberCourseCompletions(courses, bCompletions, assignmentEx, b?.assignedLabels?.isChallengeModeStudent, null, "course", b?.assignedLabels?.isReadWriteModeStudent);
39
39
  }
40
40
  if (currentView === "all_courses" || currentView === "assignments") {
41
41
  a1 = a.completions[property][percentageAccessor].percentage;
@@ -6,7 +6,7 @@ function calculateDaysLate(submissionTimestamp, dueDateMs) {
6
6
  if (daysDifference <= 0) return 0; // Not actually late
7
7
  return Math.max(1, Math.ceil(daysDifference)); // Round up, minimum 1 day
8
8
  }
9
- export const formatMemberCourseCompletions = (courses = [], completions = [], assignmentExercises, isChallengeModeStudent = false, selectedAssignment = null, reportType = "course") => {
9
+ export const formatMemberCourseCompletions = (courses = [], completions = [], assignmentExercises, isChallengeModeStudent = false, selectedAssignment = null, reportType = "course", isReadWriteModeStudent = false) => {
10
10
  const dueDateMs = selectedAssignment?.dueDate ? new Date(selectedAssignment.dueDate).getTime() : null;
11
11
  const initialStatsObject = courses.reduce((obj, v) => {
12
12
  const scoreValues = getScoreValues("course", v.courseId, completions);
@@ -25,7 +25,7 @@ export const formatMemberCourseCompletions = (courses = [], completions = [], as
25
25
  return obj;
26
26
  }, {});
27
27
  const stats = courses.reduce((previousValue, currentValue) => {
28
- const sectionsWithProgress = calcCompletions(currentValue.sections, completions, assignmentExercises, isChallengeModeStudent);
28
+ const sectionsWithProgress = calcCompletions(currentValue.sections, completions, assignmentExercises, isChallengeModeStudent, isReadWriteModeStudent);
29
29
  const percentCompletion = calcPercentageCompletion(sectionsWithProgress);
30
30
  previousValue[currentValue.courseId].percentComplete = percentCompletion;
31
31
  const initialSectionsObject = sectionsWithProgress.reduce((sectionObj, sectionVal) => {
@@ -79,10 +79,21 @@ export const formatMemberCourseCompletions = (courses = [], completions = [], as
79
79
  if (!dueDateMs) return {
80
80
  isLate: false
81
81
  };
82
- const earliestCompletion = topicCompletions.reduce((earliest, c) => !earliest || c.createdAt < earliest.createdAt ? c : earliest, null);
83
- return earliestCompletion && earliestCompletion.createdAt > dueDateMs ? {
82
+ const courseSectionTopicId = `${topicVal.courseSectionId}|${topicVal.topicId}`;
83
+ const topicAssignmentExercises = assignmentExercises?.filter(e => e.courseSectionTopicId === courseSectionTopicId);
84
+ if (!topicAssignmentExercises?.length) return {
85
+ isLate: false
86
+ };
87
+ const relevantCompletions = topicCompletions.filter(c => topicAssignmentExercises.some(e => {
88
+ if (e.name && c.exercise !== e.name) return false;
89
+ if (e.roleplayId && c.roleplayId !== e.roleplayId) return false;
90
+ if (e.botId && c.botId !== e.botId) return false;
91
+ return true;
92
+ }));
93
+ const lateCompletion = relevantCompletions.find(c => c.createdAt > dueDateMs);
94
+ return lateCompletion ? {
84
95
  isLate: true,
85
- daysLate: calculateDaysLate(earliestCompletion.createdAt, dueDateMs)
96
+ daysLate: calculateDaysLate(lateCompletion.createdAt, dueDateMs)
86
97
  } : {
87
98
  isLate: false
88
99
  };
@@ -131,7 +142,7 @@ export const formatMemberCourseCompletions = (courses = [], completions = [], as
131
142
  }, initialStatsObject);
132
143
  return stats;
133
144
  };
134
- export const formatMemberAssignmentCompletions = (assignments = [], courses = [], completions = [], isChallengeModeStudent = false) => {
145
+ export const formatMemberAssignmentCompletions = (assignments = [], courses = [], completions = [], isChallengeModeStudent = false, isReadWriteModeStudent = false) => {
135
146
  const stats = assignments.reduce((obj, assignment) => {
136
147
  const assignmentCompletions = completions.filter(completion => assignment.exercises?.some(e => {
137
148
  if (!e.name && !e.roleplayId && !e.botId && !e.courseSectionTopicId) return false;
@@ -141,7 +152,7 @@ export const formatMemberAssignmentCompletions = (assignments = [], courses = []
141
152
  if (e.courseSectionTopicId && `${completion.courseSectionId}|${completion.topicId}` !== e.courseSectionTopicId) return false;
142
153
  return true;
143
154
  }));
144
- const assignmentCourseCompletions = formatMemberCourseCompletions(courses, assignmentCompletions, assignment?.exercises, isChallengeModeStudent);
155
+ const assignmentCourseCompletions = formatMemberCourseCompletions(courses, assignmentCompletions, assignment?.exercises, isChallengeModeStudent, null, "course", isReadWriteModeStudent);
145
156
  const accumulator = {
146
157
  percentComplete: {
147
158
  completed: 0,
@@ -275,7 +275,7 @@ export default function useExerciseState({
275
275
  }
276
276
  }, [exerciseName, questions]);
277
277
  useEffect(() => {
278
- if (open && currentPhrase && currentPhrase.phrase !== "" && !isCompletionModalOpen && learnLang && learnLang !== "irish" && isTourFinished && !isReadWriteModeStudent && exerciseName !== "meaning") {
278
+ if (open && currentPhrase && currentPhrase.phrase !== "" && !isCompletionModalOpen && learnLang && learnLang !== "irish" && isTourFinished && exerciseName !== "meaning") {
279
279
  const {
280
280
  voice,
281
281
  voiceLanguageCode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nualang/nualang-ui-components",
3
- "version": "0.1.1377",
3
+ "version": "0.1.1379",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "files": [