@nualang/nualang-ui-components 0.1.1380 → 0.1.1383

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.
@@ -36,6 +36,8 @@ function Course({
36
36
  isReadWriteModeStudent
37
37
  }) {
38
38
  const numOfIds = lastClickedExerciseId ? lastClickedExerciseId.split("|") : [];
39
+ const assignmentExercises = assignment?.exercises;
40
+ const filteredSelectedExercises = useMemo(() => (assignmentExercises ?? selectedExercises)?.filter(exercise => exercise?.roleplayId || exercise?.courseSectionTopicId?.split("|")[0] === course?.courseId), [assignmentExercises, selectedExercises, course?.courseId]);
39
41
  const [open, setOpen] = useState(false);
40
42
  useEffect(() => {
41
43
  if (lastClickedExerciseId && (numOfIds.length === 4 && lastClickedExerciseId.endsWith(`|${assignment?.assignmentId}`) || numOfIds.length === 5 && lastClickedExerciseId.split("|")[3] === assignment?.assignmentId)) {
@@ -58,21 +60,13 @@ function Course({
58
60
  enabled: !!course.courseId && isDialogOpen
59
61
  });
60
62
  useEffect(() => {
61
- assignment?.exercises?.forEach(exercise => {
63
+ filteredSelectedExercises?.forEach(exercise => {
62
64
  if (exercise.courseSectionTopicId) {
63
65
  const sectionId = exercise.courseSectionTopicId.split("|")[1];
64
66
  setSelectedSectionIds(prev => [...prev, sectionId]);
65
67
  }
66
68
  });
67
- }, [selectedExercises]);
68
- useEffect(() => {
69
- selectedExercises?.forEach(exercise => {
70
- if (exercise.courseSectionTopicId) {
71
- const sectionId = exercise.courseSectionTopicId.split("|")[1];
72
- setSelectedSectionIds(prev => [...prev, sectionId]);
73
- }
74
- });
75
- }, [selectedExercises]);
69
+ }, [filteredSelectedExercises]);
76
70
  const courseSections = useMemo(() => sectionsQuery.isSuccess && Array.isArray(sectionsQuery.data.Items) && sectionsQuery.data.Items.length ? sectionsQuery.data.Items : [], [sectionsQuery.data, sectionsQuery.isSuccess]);
77
71
  const isCourseEmpty = courseSections.length === 0 && !sectionsQuery.isLoading ? true : false;
78
72
  useEffect(() => {
@@ -82,18 +76,18 @@ function Course({
82
76
  }, [courseSections, selectedSectionIds, viewOnly]);
83
77
  useEffect(() => {
84
78
  const assignedSectionIds = new Set();
85
- assignment?.exercises?.forEach(exercise => {
79
+ assignmentExercises?.forEach(exercise => {
86
80
  if (exercise.courseSectionTopicId) {
87
81
  const sectionId = exercise.courseSectionTopicId.split("|")[1];
88
82
  assignedSectionIds.add(sectionId);
89
83
  }
90
84
  });
91
- if (courseSections && memberCourseCompletions && selectedExercises) {
92
- const sectionsWithProgress = calcCompletions(courseSections.filter(section => assignedSectionIds.has(section.sectionId)), memberCourseCompletions, selectedExercises, isChallengeModeStudent, isReadWriteModeStudent);
85
+ if (courseSections && memberCourseCompletions && filteredSelectedExercises) {
86
+ const sectionsWithProgress = calcCompletions(courseSections.filter(section => assignedSectionIds.has(section.sectionId)), memberCourseCompletions, filteredSelectedExercises, isChallengeModeStudent, isReadWriteModeStudent);
93
87
  const percentCompletion = calcPercentageCompletion(sectionsWithProgress);
94
88
  setCourseSectionCompletion(percentCompletion);
95
89
  }
96
- }, [courseSections, memberCourseCompletions, selectedExercises, isChallengeModeStudent, isReadWriteModeStudent]);
90
+ }, [courseSections, memberCourseCompletions, filteredSelectedExercises, isChallengeModeStudent, isReadWriteModeStudent, assignmentExercises]);
97
91
  return /*#__PURE__*/_jsx(_Fragment, {
98
92
  children: useCase === "assignment-select" ? /*#__PURE__*/_jsxs(List, {
99
93
  disablePadding: true,
@@ -167,7 +161,7 @@ function Course({
167
161
  setIsExerciseSelected: setIsExerciseSelected,
168
162
  getRoleplays: getRoleplays,
169
163
  handleSelectExercise: handleSelectExercise,
170
- selectedExercises: selectedExercises,
164
+ selectedExercises: filteredSelectedExercises,
171
165
  useCase: useCase,
172
166
  assignment: assignment,
173
167
  handleStartExercise: handleStartExercise,
@@ -255,7 +249,7 @@ function AssignmentCourseSelection({
255
249
  isExerciseSelected: isExerciseSelected,
256
250
  setIsExerciseSelected: setIsExerciseSelected,
257
251
  handleSelectExercise: handleSelectExercise,
258
- selectedExercises: assignment ? assignment.exercises?.filter(exercise => exercise?.roleplayId || exercise?.courseSectionTopicId?.split("|")[0] === course?.courseId) : selectedExercises?.filter(exercise => exercise?.roleplayId || exercise?.courseSectionTopicId?.split("|")[0] === course?.courseId),
252
+ selectedExercises: selectedExercises,
259
253
  useCase: useCase,
260
254
  assignment: assignment,
261
255
  handleStartExercise: handleStartExercise,
@@ -13,7 +13,7 @@ import OndemandVideoIcon from "@mui/icons-material/OndemandVideo";
13
13
  import AssignmentRoleplaySelection from "../AssignmentRoleplaySelection/AssignmentRoleplaySelection";
14
14
  import { CardInfoSectionItem } from "../../Cards/CardElements";
15
15
  import { getCourseSectionTopicProgress } from "@nualang/nualang-api-and-queries/APIs/Progress";
16
- import { progress as progressQuerys, courses as courseQuerys } from "@nualang/nualang-api-and-queries/Queries";
16
+ import { progress as progressQuerys } from "@nualang/nualang-api-and-queries/Queries";
17
17
  import { useNavigate, useParams } from "react-router";
18
18
  import { ProgressBadge, InProgressBadge } from "../../Lists/Exercises/Exercises";
19
19
  import ColorLinearProgress from "../../Misc/ColorLinearProgress/ColorLinearProgress";
@@ -384,25 +384,14 @@ function Topic({
384
384
  handleRemoveExercise,
385
385
  isPreview,
386
386
  isChallengeModeStudent,
387
+ phraseLists: rawPhraseLists = [],
387
388
  ...otherProps
388
389
  }) {
389
390
  const theme = useTheme();
390
391
  const isLargeScreen = useMediaQuery(theme.breakpoints.up("sm"));
391
392
  const topicRef = useRef(null);
392
393
  const numOfIds = lastClickedExerciseId ? lastClickedExerciseId.split("|") : [];
393
- const phraseListsQuery = courseQuerys.useCourseSectionTopicPhraseLists(undefined, {
394
- courseId,
395
- sectionId,
396
- topicId,
397
- filters: {
398
- includePhrases: "true"
399
- }
400
- }, {
401
- enabled: !!courseId && !!sectionId && !!topicId
402
- });
403
- const phraseLists = phraseListsQuery.isSuccess && phraseListsQuery.data && Array.isArray(phraseListsQuery.data.Items) ? phraseListsQuery.data.Items.filter(pl => pl.phrases && pl.phrases.length > 0) : [];
404
-
405
- // Define a single variable for clarity
394
+ const phraseLists = rawPhraseLists.filter(pl => pl.phrases && pl.phrases.length > 0);
406
395
  const matchesLastClickedExercise = lastClickedExerciseId && (numOfIds.length === 4 && lastClickedExerciseId.endsWith(`|${assignment.assignmentId}`) && numOfIds[2] === topicId || numOfIds.length === 5 && numOfIds[3] === assignment.assignmentId && numOfIds[2] === topicId);
407
396
  const [isExerciseListOpen, setExerciseListOpen] = useState(false);
408
397
  useEffect(() => {
@@ -934,7 +934,6 @@ function Exercises(props) {
934
934
  t = text => text,
935
935
  isLoading,
936
936
  phrases = [],
937
- phraseLists = [],
938
937
  completions = [],
939
938
  roleplays = [],
940
939
  bots = [],
@@ -974,7 +973,8 @@ function Exercises(props) {
974
973
  updatePhraseLists,
975
974
  courseSettings,
976
975
  parentClassroom,
977
- isChallengeModeStudent
976
+ isChallengeModeStudent,
977
+ topicInfo
978
978
  } = props;
979
979
  const params = useParams();
980
980
  const {
@@ -1033,10 +1033,10 @@ function Exercises(props) {
1033
1033
  // ── phraseLists drag state ──
1034
1034
  const [phraseListsArray, setPhraseListsArray] = useState([]);
1035
1035
  useEffect(() => {
1036
- if (phraseLists && Array.isArray(phraseLists)) {
1037
- setPhraseListsArray([...phraseLists].sort((a, b) => (a.idx || 0) - (b.idx || 0)));
1036
+ if (topicInfo?.phraseLists && Array.isArray(topicInfo.phraseLists)) {
1037
+ setPhraseListsArray([...topicInfo.phraseLists].sort((a, b) => (a.idx || 0) - (b.idx || 0)));
1038
1038
  }
1039
- }, [JSON.stringify(phraseLists)]);
1039
+ }, [JSON.stringify(topicInfo?.phraseLists)]);
1040
1040
  const onDropPhraseList = () => {
1041
1041
  if (updatePhraseLists) updatePhraseLists(phraseListsArray);
1042
1042
  };
@@ -430,7 +430,6 @@ function Topic({
430
430
  isClassroomCreator,
431
431
  isVideoChatEnabled,
432
432
  isVideoChatEnabledInSettings,
433
- phraseLists = [],
434
433
  handleCreatePhraseList,
435
434
  handleUpdatePhraseList,
436
435
  handleDeletePhraseList,
@@ -718,7 +717,6 @@ function Topic({
718
717
  isMember: isMember,
719
718
  isChallengeModeStudent: isChallengeModeStudent,
720
719
  phrases: phrases,
721
- phraseLists: phraseLists,
722
720
  completions: completions,
723
721
  roleplays: roleplays,
724
722
  bots: bots,
@@ -28,7 +28,6 @@ function Progress({
28
28
  memberId,
29
29
  username,
30
30
  getCourseSections = () => {},
31
- getCourseSectionTopicPhraseLists = () => {},
32
31
  submissions,
33
32
  courseIds,
34
33
  featureFlags,
@@ -43,8 +42,7 @@ function Progress({
43
42
  const isSmallScreen = useMediaQuery(theme.breakpoints.down("sm"));
44
43
  const {
45
44
  useQueryClient,
46
- useQueries,
47
- useQuery
45
+ useQueries
48
46
  } = ReactQuery;
49
47
  const queryClient = useQueryClient();
50
48
  const [selectedAssignment, setSelectedAssignment] = useState(null);
@@ -55,35 +53,11 @@ function Progress({
55
53
  const [selectedPhraseList, setSelectedPhraseList] = useState(null);
56
54
  const [roleplays, setRoleplays] = useState([]);
57
55
  const [bots, setBots] = useState([]);
58
- const [phraseLists, setPhraseLists] = useState([]);
59
56
  const reportFilters = ["percentage_complete", "correct_answer_percentage", "avg_pronunciation_score"];
60
57
  const [filter, setFilter] = useState(reportFilters[0]);
61
58
  const reportTypes = useMemo(() => isVideoChatEnabled ? ["courses", "assignments", "discuss"] : ["courses", "assignments"], [isVideoChatEnabled]);
62
59
  const availableReportTypes = useMemo(() => memberId ? ["courses", "assignments"] : reportTypes, [memberId, reportTypes]);
63
60
  const [reportType, setReportType] = useState(availableReportTypes[0]);
64
- useEffect(() => {
65
- if (selectedTopic) {
66
- setRoleplays(selectedTopic?.roleplays);
67
- setBots(selectedTopic?.bots);
68
- } else {
69
- setSelectedPhraseList(null);
70
- setPhraseLists([]);
71
- }
72
- }, [selectedTopic]);
73
- const selectedTopicCoords = selectedTopic ? [...selectedTopic.courseSectionId.split("|"), selectedTopic.topicId] : [];
74
- const topicPhraseListsQuery = useQuery({
75
- queryKey: ["courseSectionTopicPhraseLists", ...selectedTopicCoords],
76
- queryFn: () => {
77
- const [courseId, sectionId] = selectedTopic.courseSectionId.split("|");
78
- return getCourseSectionTopicPhraseLists(courseId, sectionId, selectedTopic.topicId);
79
- },
80
- enabled: !!selectedTopic
81
- });
82
- useEffect(() => {
83
- if (topicPhraseListsQuery.isSuccess && topicPhraseListsQuery.data?.Items) {
84
- setPhraseLists(topicPhraseListsQuery.data.Items);
85
- }
86
- }, [topicPhraseListsQuery.data, topicPhraseListsQuery.isSuccess]);
87
61
  const currentCourses = reportType === "assignments" ? assignedCourses : courses;
88
62
  const courseSectionsQueries = useQueries({
89
63
  queries: (currentCourses || []).map(({
@@ -112,6 +86,15 @@ function Progress({
112
86
  return [];
113
87
  }
114
88
  }, [currentCourses, courseSectionsQueriesIsLoading]);
89
+ useEffect(() => {
90
+ if (selectedTopic) {
91
+ setSelectedPhraseList(null);
92
+ setRoleplays(selectedTopic?.roleplays);
93
+ setBots(selectedTopic?.bots);
94
+ } else {
95
+ setSelectedPhraseList(null);
96
+ }
97
+ }, [selectedTopic, coursesWithSections]);
115
98
  const deleteCourseId = () => {
116
99
  if ("URLSearchParams" in window) {
117
100
  const searchParams = searchStringToObj(window.location.search);
@@ -147,8 +130,7 @@ function Progress({
147
130
  selectedRoleplay,
148
131
  setSelectedRoleplay,
149
132
  selectedPhraseList,
150
- setSelectedPhraseList,
151
- phraseLists
133
+ setSelectedPhraseList
152
134
  });
153
135
  const [selectedMembers, setSelectedMembers] = useState([]);
154
136
  const assignedStudents = assignmentMembersById && assignmentMembersById[selectedAssignment?.assignmentId] || [];
@@ -417,7 +399,6 @@ function Progress({
417
399
  setSelectedRoleplay: setSelectedRoleplay,
418
400
  selectedPhraseList: selectedPhraseList,
419
401
  setSelectedPhraseList: setSelectedPhraseList,
420
- phraseLists: phraseLists,
421
402
  selectedAssignment: selectedAssignment,
422
403
  setSelectedAssignment: setSelectedAssignment,
423
404
  assignments: assignments,
@@ -497,7 +478,6 @@ function Progress({
497
478
  setSelectedRoleplay: setSelectedRoleplay,
498
479
  selectedPhraseList: selectedPhraseList,
499
480
  setSelectedPhraseList: setSelectedPhraseList,
500
- phraseLists: phraseLists,
501
481
  selectedAssignment: selectedAssignment,
502
482
  setSelectedAssignment: setSelectedAssignment,
503
483
  assignments: assignments,
@@ -1,4 +1,4 @@
1
- import { useState } from "react";
1
+ import { useState, useMemo } from "react";
2
2
  import PropTypes from "prop-types";
3
3
  import { Link as RouterLink } from "react-router";
4
4
  import { ReactQuery } from "@nualang/nualang-api-and-queries/Queries";
@@ -450,26 +450,26 @@ function MemberListItem({
450
450
  bots,
451
451
  selectedRoleplay,
452
452
  setSelectedRoleplay,
453
- phraseLists,
454
453
  selectedPhraseList,
455
454
  setSelectedPhraseList
456
455
  }) {
456
+ const phraseLists = selectedTopic?.phraseLists ?? [];
457
457
  const {
458
458
  memberId
459
459
  } = member;
460
460
  const courseIds = courses.map(c => c.courseId).toString();
461
- const {
462
- data = {},
463
- error,
464
- isLoading
465
- } = useQuery({
461
+ const memberCourseCompletionsQuery = useQuery({
466
462
  queryKey: ["memberCourseProgress", memberId, courseIds],
467
463
  queryFn: async () => {
468
- const memberCourseCompletions = await fetchMemberCourseCompletions(memberId, courseIds);
469
- return formatMemberCourseCompletions(courses, memberCourseCompletions.Items);
464
+ return fetchMemberCourseCompletions(memberId, courseIds);
470
465
  },
471
466
  staleTime: 500000
472
467
  });
468
+ const {
469
+ error,
470
+ isLoading
471
+ } = memberCourseCompletionsQuery;
472
+ const data = useMemo(() => memberCourseCompletionsQuery.isSuccess && Array.isArray(memberCourseCompletionsQuery.data?.Items) ? formatMemberCourseCompletions(courses, memberCourseCompletionsQuery.data.Items, null, false, null, "course", false) : {}, [memberCourseCompletionsQuery.data, memberCourseCompletionsQuery.isSuccess, courses]);
473
473
  let memberActivityLink = `/classrooms/${classroomId}/activity/member/${member.memberId}`;
474
474
  if (currentView === "course") {
475
475
  memberActivityLink = `${memberActivityLink}/${selectedCourse.courseId}`;
@@ -482,7 +482,8 @@ function MemberListItem({
482
482
  const handleClick = () => {
483
483
  setOpen(!open);
484
484
  };
485
- const exercisesArray = exercises;
485
+ const hasMeaningForSelectedPhraseList = selectedPhraseList?.phrases?.some(p => p.definitions?.length > 0) && !selectedPhraseList?.hiddenPhraseGroupGames?.meaning;
486
+ const exercisesArray = [...exercises, ...(hasMeaningForSelectedPhraseList ? ["meaning"] : [])];
486
487
  return /*#__PURE__*/_jsxs(_Fragment, {
487
488
  children: [/*#__PURE__*/_jsxs(ListItem, {
488
489
  secondaryAction: /*#__PURE__*/_jsx(IconButton, {
@@ -563,7 +564,8 @@ function MemberListItem({
563
564
  }, `student-progress-data-cell-${i}`);
564
565
  }), currentView === "topic" && phraseLists.map((phraseList, i) => {
565
566
  const topicData = getTopicData(data, selectedTopic, selectedCourse, selectedSection);
566
- const phraseListExercises = ["translation", "listening", "pronunciation"];
567
+ const hasMeaning = phraseList?.phrases?.some(p => p.definitions?.length > 0) && !phraseList?.hiddenPhraseGroupGames?.meaning;
568
+ const phraseListExercises = ["translation", "listening", "pronunciation", ...(hasMeaning ? ["meaning"] : [])];
567
569
  const completedCount = phraseListExercises.filter(exercise => topicData?.completions?.some(c => c.exercise === exercise && (c.phraseListId === phraseList.phraseListId || !c.phraseListId && phraseList.phraseListId?.startsWith('leg-')))).length;
568
570
  return /*#__PURE__*/_jsx(MenuItem, {
569
571
  children: /*#__PURE__*/_jsx(ListItem, {
@@ -655,7 +657,6 @@ function ProgressList({
655
657
  setSelectedRoleplay,
656
658
  selectedPhraseList,
657
659
  setSelectedPhraseList,
658
- phraseLists = [],
659
660
  roleplays,
660
661
  bots,
661
662
  deleteCourseId
@@ -900,7 +901,6 @@ function ProgressList({
900
901
  bots: bots,
901
902
  selectedRoleplay: selectedRoleplay,
902
903
  setSelectedRoleplay: setSelectedRoleplay,
903
- phraseLists: phraseLists,
904
904
  selectedPhraseList: selectedPhraseList,
905
905
  setSelectedPhraseList: setSelectedPhraseList
906
906
  }, `student-progress-list-item-${i}`))
@@ -48,10 +48,10 @@ function ProgressTable({
48
48
  featureFlags,
49
49
  reportType,
50
50
  assignedCourseIds,
51
- phraseLists = [],
52
51
  selectedPhraseList,
53
52
  setSelectedPhraseList
54
53
  }) {
54
+ const phraseLists = selectedTopic?.phraseLists ?? [];
55
55
  const selectedAssignmentTopics = (selectedAssignment?.exercises || [])?.map(e => e.courseSectionTopicId);
56
56
  const currentCourseIds = reportType === "assignments" ? assignedCourseIds : courseIds;
57
57
  const assignmentTopics = courses.flatMap(c => c.sections.flatMap(s => s.topics)).filter(t => selectedAssignmentTopics?.includes(`${t.courseSectionId}|${t.topicId}`));
@@ -62,6 +62,11 @@ function ProgressTable({
62
62
  const tableRoleplays = assignmentExercises && assignmentExercises.length ? roleplays.filter(r => assignmentExercises.some(ae => ae.roleplayId === r.roleplayId)) : roleplays;
63
63
  const tableRoleplayGames = assignmentExercises && assignmentExercises.length && selectedRoleplay ? roleplayGames.filter(rg => assignmentExercises.some(ae => ae.roleplayId === selectedRoleplay.roleplayId && ae.name === `roleplay-${rg}`)) : roleplayGames;
64
64
  const tableBots = assignmentExercises && assignmentExercises.length ? bots.filter(b => assignmentExercises.some(ae => ae.botId === b.botId)) : bots;
65
+ const tablePhraseLists = assignmentExercises && assignmentExercises.length ? phraseLists.filter(pl => assignmentExercises.some(ae => ae.phraseListId === pl.phraseListId)) : phraseLists;
66
+ const hasMeaningForSelectedPhraseList = selectedPhraseList?.phrases?.some(p => p.definitions?.length > 0) && !selectedPhraseList?.hiddenPhraseGroupGames?.meaning;
67
+ const allPhraseListExercises = ["translation", "listening", "pronunciation", ...(hasMeaningForSelectedPhraseList ? ["meaning"] : [])];
68
+ const assignmentPhraseListExercises = reportType === "assignments" && selectedPhraseList ? (selectedAssignment?.exercises || []).filter(e => e.phraseListId === selectedPhraseList.phraseListId) : [];
69
+ const tablePhraseListExercises = assignmentPhraseListExercises.length ? allPhraseListExercises.filter(e => assignmentPhraseListExercises.some(ae => ae.name === e)) : allPhraseListExercises;
65
70
  const [sortDirection, setSortDirection] = useState("asc");
66
71
  const [sortColumn, setSortColumn] = useState("student");
67
72
  const [membersState, setMembersState] = useState([]);
@@ -695,7 +700,7 @@ function ProgressTable({
695
700
  },
696
701
  children: t("view_topic")
697
702
  })]
698
- }, i)), (currentView === "topic" || currentView === "assignment-topic") && phraseLists.map((phraseList, i) => /*#__PURE__*/_jsxs("th", {
703
+ }, i)), (currentView === "topic" || currentView === "assignment-topic") && tablePhraseLists.map((phraseList, i) => /*#__PURE__*/_jsxs("th", {
699
704
  scope: "col",
700
705
  children: [/*#__PURE__*/_jsx(Typography, {
701
706
  variant: "subtitle1",
@@ -716,7 +721,7 @@ function ProgressTable({
716
721
  },
717
722
  children: t("view_phrase_list")
718
723
  })]
719
- }, i)), (currentView === "phrase-list" || currentView === "assignment-phrase-list") && ["translation", "listening", "pronunciation"].map((exercise, i) => /*#__PURE__*/_jsx("th", {
724
+ }, i)), (currentView === "phrase-list" || currentView === "assignment-phrase-list") && tablePhraseListExercises.map((exercise, i) => /*#__PURE__*/_jsx("th", {
720
725
  scope: "col",
721
726
  children: /*#__PURE__*/_jsx(Typography, {
722
727
  variant: "subtitle1",
@@ -851,7 +856,7 @@ function ProgressTable({
851
856
  selectedTopic: selectedTopic,
852
857
  selectedRoleplay: selectedRoleplay,
853
858
  selectedPhraseList: selectedPhraseList,
854
- phraseLists: phraseLists,
859
+ phraseLists: tablePhraseLists,
855
860
  selectedAssignment: selectedAssignment,
856
861
  fetchMemberCourseCompletions: fetchMemberCourseCompletions,
857
862
  currentView: currentView,
@@ -860,6 +865,7 @@ function ProgressTable({
860
865
  featureFlags: featureFlags,
861
866
  reportType: reportType,
862
867
  tableExercises: tableExercises,
868
+ tablePhraseListExercises: tablePhraseListExercises,
863
869
  tableRoleplays: tableRoleplays,
864
870
  tableRoleplayGames: tableRoleplayGames,
865
871
  tableBots: tableBots,
@@ -33,6 +33,7 @@ export default function ProgressTableRow(props) {
33
33
  featureFlags,
34
34
  reportType,
35
35
  tableExercises = [],
36
+ tablePhraseListExercises = [],
36
37
  tableRoleplays = [],
37
38
  tableBots = [],
38
39
  tableRoleplayGames = [],
@@ -49,7 +50,7 @@ export default function ProgressTableRow(props) {
49
50
  } = member;
50
51
  const isChallengeModeStudent = assignedLabels?.isChallengeModeStudent;
51
52
  const isReadWriteModeStudent = assignedLabels?.isReadWriteModeStudent;
52
- const assignmentDueDateMs = selectedAssignment?.dueDate ? new Date(selectedAssignment.dueDate).getTime() : null;
53
+ const assignmentDueDateMs = reportType === "assignments" && selectedAssignment?.dueDate ? new Date(selectedAssignment.dueDate).getTime() : null;
53
54
  const memberCourseCompletionsQuery = useQuery({
54
55
  queryKey: ["memberCourseProgress", memberId, courseIds],
55
56
  queryFn: async () => {
@@ -201,6 +202,7 @@ export default function ProgressTableRow(props) {
201
202
  index: i
202
203
  }, `topic-cell-${i}`)), (currentView === "topic" || currentView === "assignment-topic") && phraseLists.map((phraseList, i) => {
203
204
  const topicData = getTopicData(data, selectedTopic, selectedCourse, selectedSection);
205
+ const hasMeaning = phraseList?.phrases?.some(p => p.definitions?.length > 0) && !phraseList?.hiddenPhraseGroupGames?.meaning;
204
206
  return /*#__PURE__*/_jsx(PhraseListCellData, {
205
207
  t: t,
206
208
  data: topicData,
@@ -208,9 +210,10 @@ export default function ProgressTableRow(props) {
208
210
  isLoading: isLoading,
209
211
  error: error,
210
212
  index: i,
211
- assignmentDueDateMs: assignmentDueDateMs
213
+ assignmentDueDateMs: assignmentDueDateMs,
214
+ hasMeaning: hasMeaning
212
215
  }, `phrase-list-cell-${i}`);
213
- }), (currentView === "phrase-list" || currentView === "assignment-phrase-list") && ["translation", "listening", "pronunciation"].map((exercise, i) => {
216
+ }), (currentView === "phrase-list" || currentView === "assignment-phrase-list") && tablePhraseListExercises.map((exercise, i) => {
214
217
  const topicData = getTopicData(data, selectedTopic, selectedCourse, selectedSection);
215
218
  return /*#__PURE__*/_jsx(ExerciseCellData, {
216
219
  t: t,
@@ -485,7 +485,8 @@ export function PhraseListCellData({
485
485
  isLoading,
486
486
  error,
487
487
  index,
488
- assignmentDueDateMs = null
488
+ assignmentDueDateMs = null,
489
+ hasMeaning = false
489
490
  }) {
490
491
  if (isLoading) return /*#__PURE__*/_jsx(DataCell, {
491
492
  t: t,
@@ -497,7 +498,7 @@ export function PhraseListCellData({
497
498
  data: "A problem has occurred.",
498
499
  index: index
499
500
  });
500
- const phraseListExercises = ["translation", "listening", "pronunciation"];
501
+ const phraseListExercises = ["translation", "listening", "pronunciation", ...(hasMeaning ? ["meaning"] : [])];
501
502
  const phraseListCompletions = data?.completions?.filter(c => phraseListExercises.includes(c.exercise) && (c.phraseListId === phraseListId || !c.phraseListId && phraseListId?.startsWith('leg-'))) ?? [];
502
503
  const completedCount = phraseListExercises.filter(exercise => phraseListCompletions.some(c => c.exercise === exercise)).length;
503
504
  const total = phraseListExercises.length;
@@ -43,8 +43,7 @@ export default function useProgressUrlParams(opts) {
43
43
  selectedRoleplay,
44
44
  setSelectedRoleplay,
45
45
  selectedPhraseList,
46
- setSelectedPhraseList,
47
- phraseLists
46
+ setSelectedPhraseList
48
47
  } = opts;
49
48
 
50
49
  // Track if we've already initialized from URL to prevent re-initialization
@@ -97,8 +96,8 @@ export default function useProgressUrlParams(opts) {
97
96
  }
98
97
  }
99
98
  const phraseListId = searchParams.phraseListId;
100
- if (phraseListId && !selectedPhraseList && phraseLists && phraseLists.length) {
101
- const phraseList = phraseLists.find(p => p.phraseListId === phraseListId);
99
+ if (phraseListId && !selectedPhraseList) {
100
+ const phraseList = topic?.phraseLists?.find(p => p.phraseListId === phraseListId);
102
101
  if (phraseList) setSelectedPhraseList(phraseList);
103
102
  }
104
103
  }
@@ -1718,6 +1718,7 @@ export default function useExerciseState({
1718
1718
  exerciseCompletion.answerAttempts = newAnswerAttempts && Array.isArray(newAnswerAttempts) ? newAnswerAttempts : answerAttempts;
1719
1719
  exerciseCompletion.unansweredPhrases = unansweredPhrases && Array.isArray(unansweredPhrases) ? unansweredPhrases : questions;
1720
1720
  exerciseCompletion.remainingQuestions = remainingQuestions;
1721
+ if (phraseListId) exerciseCompletion.phraseListId = phraseListId;
1721
1722
  setQuestions(phrases.slice());
1722
1723
  }
1723
1724
  handleComplete(exerciseCompletion);
@@ -135,7 +135,7 @@ export const calcTopicCompletions = (topic, completions = [], isSectionHidden, a
135
135
  let isPhrases;
136
136
  let completedExerciseCount = 0;
137
137
  let totalExercises = 0;
138
- if (topic.phrases && topic.phrases.length > 0) {
138
+ if (topic.phrases && topic.phrases.length > 0 && !topic.phraseLists?.length) {
139
139
  isPhrases = true;
140
140
  }
141
141
  if (isSectionHidden || topic.isTopicHidden) {
@@ -149,6 +149,7 @@ export const calcTopicCompletions = (topic, completions = [], isSectionHidden, a
149
149
  isListeningHidden = true;
150
150
  isTranslationHidden = true;
151
151
  assignmentExercises.forEach(e => {
152
+ if (e.phraseListId) return;
152
153
  if (e.name === "pronunciation") {
153
154
  isPronunciationHidden = false;
154
155
  }
@@ -189,12 +190,14 @@ export const calcTopicCompletions = (topic, completions = [], isSectionHidden, a
189
190
  totalExercises++;
190
191
  }
191
192
  }
192
- const completedPronunciation = Array.isArray(completions) ? completions.filter(c => c.topicId === topic.topicId && c.exercise === "pronunciation").length : 0;
193
- const completedTranslation = Array.isArray(completions) ? completions.filter(c => c.topicId === topic.topicId && c.exercise === "translation").length : 0;
194
- const completedListening = Array.isArray(completions) ? completions.filter(c => c.topicId === topic.topicId && c.exercise === "listening").length : 0;
195
- completedExerciseCount += !isPronunciationHidden && completedPronunciation > 0 ? 1 : 0;
196
- completedExerciseCount += !isTranslationHidden && completedTranslation > 0 ? 1 : 0;
197
- completedExerciseCount += !isListeningHidden && completedListening > 0 ? 1 : 0;
193
+ const completedPronunciation = Array.isArray(completions) ? completions.filter(c => c.topicId === topic.topicId && c.exercise === "pronunciation" && !c.phraseListId).length : 0;
194
+ const completedTranslation = Array.isArray(completions) ? completions.filter(c => c.topicId === topic.topicId && c.exercise === "translation" && !c.phraseListId).length : 0;
195
+ const completedListening = Array.isArray(completions) ? completions.filter(c => c.topicId === topic.topicId && c.exercise === "listening" && !c.phraseListId).length : 0;
196
+ if (isPhrases) {
197
+ completedExerciseCount += !isPronunciationHidden && completedPronunciation > 0 ? 1 : 0;
198
+ completedExerciseCount += !isTranslationHidden && completedTranslation > 0 ? 1 : 0;
199
+ completedExerciseCount += !isListeningHidden && completedListening > 0 ? 1 : 0;
200
+ }
198
201
  let completedRoleplays = 0;
199
202
  let completedGames = 0;
200
203
  let totalNumberOfGames = 0;
@@ -295,6 +298,48 @@ export const calcTopicCompletions = (topic, completions = [], isSectionHidden, a
295
298
  completedCount
296
299
  };
297
300
  });
301
+ if (!assignmentExercises && topic.phraseLists?.length > 0) {
302
+ topic.phraseLists.forEach(phraseList => {
303
+ if (phraseList.isPhraseListHidden) return;
304
+ const hasMeaning = phraseList?.phrases?.some(p => p.definitions?.length > 0) && !phraseList?.hiddenPhraseGroupGames?.meaning;
305
+ const exerciseNames = ["translation", "listening", "pronunciation", ...(hasMeaning ? ["meaning"] : [])];
306
+ exerciseNames.forEach(exerciseName => {
307
+ totalExercises++;
308
+ const isCompleted = Array.isArray(completions) && completions.some(c => c.phraseListId === phraseList.phraseListId && c.exercise === exerciseName && c.topicId === topic.topicId);
309
+ if (isCompleted) completedExerciseCount++;
310
+ });
311
+ });
312
+ }
313
+ if (assignmentExercises) {
314
+ const phraseListExercises = assignmentExercises.filter(e => e.phraseListId);
315
+ const phraseListMap = {};
316
+ phraseListExercises.forEach(e => {
317
+ if (!phraseListMap[e.phraseListId]) phraseListMap[e.phraseListId] = [];
318
+ if (e.name) phraseListMap[e.phraseListId].push(e.name);
319
+ });
320
+ if (topic.phraseLists?.length > 0) {
321
+ Object.keys(phraseListMap).forEach(phraseListId => {
322
+ if (!phraseListMap[phraseListId].includes("meaning")) {
323
+ const pl = topic.phraseLists?.find(p => p.phraseListId === phraseListId);
324
+ const hasMeaning = pl?.phrases?.some(p => p.definitions?.length > 0) && !pl?.hiddenPhraseGroupGames?.meaning;
325
+ if (hasMeaning) phraseListMap[phraseListId].push("meaning");
326
+ }
327
+ });
328
+ }
329
+ Object.entries(phraseListMap).forEach(([phraseListId, exerciseNames]) => {
330
+ if (exerciseNames.length === 0) {
331
+ totalExercises++;
332
+ const isCompleted = Array.isArray(completions) && completions.some(c => c.phraseListId === phraseListId && c.topicId === topic.topicId);
333
+ if (isCompleted) completedExerciseCount++;
334
+ return;
335
+ }
336
+ exerciseNames.forEach(exerciseName => {
337
+ totalExercises++;
338
+ const isCompleted = Array.isArray(completions) && completions.some(c => c.phraseListId === phraseListId && c.exercise === exerciseName && c.topicId === topic.topicId);
339
+ if (isCompleted) completedExerciseCount++;
340
+ });
341
+ });
342
+ }
298
343
  const scoreValues = getScoreValues("topic", topic.topicId, completions);
299
344
  return {
300
345
  ...topic,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nualang/nualang-ui-components",
3
- "version": "0.1.1380",
3
+ "version": "0.1.1383",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "files": [
@@ -101,7 +101,7 @@
101
101
  "@mui/system": "^7.0.2",
102
102
  "@mui/x-date-pickers": "^8.27.2",
103
103
  "@nualang/eslint-config-nualang": "0.2.0-alpha-5",
104
- "@nualang/nualang-api-and-queries": "^1.1.38",
104
+ "@nualang/nualang-api-and-queries": "^1.1.42",
105
105
  "@react-theming/storybook-addon": "^1.1.10",
106
106
  "@storybook/addon-docs": "^10.0.2",
107
107
  "@storybook/addon-links": "^10.0.2",
@@ -134,7 +134,7 @@
134
134
  "react-dom": "^19.2.4",
135
135
  "react-pdf": "^10.2.0",
136
136
  "react-router": "^7.12.0",
137
- "rimraf": "^6.1.0",
137
+ "rimraf": "^6.1.3",
138
138
  "storybook": "^10.0.2",
139
139
  "vitest": "^4.0.18",
140
140
  "zx": "^8.8.5"