@nualang/nualang-ui-components 0.1.1295 → 0.1.1297

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.
@@ -116,7 +116,7 @@ const AssignmentCardsList = ({
116
116
  }, idx))]
117
117
  });
118
118
  }
119
- if (assignments.length === 0) {
119
+ if (!assignments || assignments.length === 0) {
120
120
  return /*#__PURE__*/_jsx(Box, {
121
121
  mb: 1,
122
122
  children: /*#__PURE__*/_jsxs(Box, {
@@ -21,7 +21,7 @@ function Course({
21
21
  handleSelectExercise,
22
22
  selectedExercises,
23
23
  useCase,
24
- assignment,
24
+ assignment = {},
25
25
  handleStartExercise,
26
26
  isCreator,
27
27
  viewOnly,
@@ -34,7 +34,12 @@ function Course({
34
34
  isChallengeModeStudent
35
35
  }) {
36
36
  const numOfIds = lastClickedExerciseId ? lastClickedExerciseId.split("|") : [];
37
- const [open, setOpen] = useState(lastClickedExerciseId && (numOfIds.length === 4 && lastClickedExerciseId.endsWith(`|${assignment.assignmentId}`) || numOfIds.length === 5 && lastClickedExerciseId.split("|")[3] === assignment.assignmentId));
37
+ const [open, setOpen] = useState(false);
38
+ useEffect(() => {
39
+ if (lastClickedExerciseId && (numOfIds.length === 4 && lastClickedExerciseId.endsWith(`|${assignment?.assignmentId}`) || numOfIds.length === 5 && lastClickedExerciseId.split("|")[3] === assignment?.assignmentId)) {
40
+ setOpen(true);
41
+ }
42
+ }, [lastClickedExerciseId, assignment.assignmentId]);
38
43
  const [selectedSectionIds, setSelectedSectionIds] = useState([]);
39
44
  const [filteredSections, setFilteredSections] = useState([]);
40
45
  const [courseSectionCompletion, setCourseSectionCompletion] = useState(null);
@@ -21,7 +21,6 @@ import { ProgressBadge, InProgressBadge } from "../../Lists/Exercises/Exercises"
21
21
  import ColorLinearProgress from "../../Misc/ColorLinearProgress/ColorLinearProgress";
22
22
  import { calcCompletions } from "../../utils";
23
23
  import AssignmentBotSelection from "../AssignmentBotSelection/AssignmentBotSelection";
24
- import { Clear } from "@mui/icons-material";
25
24
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
26
25
  function Exercise({
27
26
  name,
@@ -130,18 +129,13 @@ function Exercise({
130
129
  setBotsOpen(!botsOpen);
131
130
  };
132
131
  useEffect(() => {
133
- section.topics.forEach(topic => {
134
- if (topic.hiddenExercises && topic.hiddenExercises.includes("listening")) {
135
- setListeningHidden(true);
136
- }
137
- if (topic.hiddenExercises && topic.hiddenExercises.includes("translation")) {
138
- setTranslationHidden(true);
139
- }
140
- if (topic.hiddenExercises && topic.hiddenExercises.includes("pronunciation")) {
141
- setPronunciationHidden(true);
142
- }
143
- });
144
- }, [section]);
132
+ const currentTopicId = courseSectionTopicId.split("|")[2];
133
+ const currentTopic = section?.topics?.find(t => t.topicId === currentTopicId) || {};
134
+ const hidden = (currentTopic.hiddenExercises || []).map(x => String(x).toLowerCase());
135
+ setListeningHidden(hidden.includes("listening"));
136
+ setTranslationHidden(hidden.includes("translation"));
137
+ setPronunciationHidden(hidden.includes("pronunciation"));
138
+ }, [section, courseSectionTopicId]);
145
139
  const isValidExercise = phrases.length >= 2 || name.toLowerCase() === "roleplays" || name.toLowerCase() === "bots";
146
140
  if (name.toLowerCase() === "listening" && listeningHidden || name.toLowerCase() === "translation" && translationHidden || name.toLowerCase() === "pronunciation" && pronunciationHidden) {
147
141
  return null;
@@ -418,72 +412,66 @@ function Topic({
418
412
  const theme = useTheme();
419
413
  const isLargeScreen = useMediaQuery(theme.breakpoints.up("sm"));
420
414
  const topicRef = useRef(null);
421
- const [lasClickedTopicId, lastClickedAssignmentId] = lastClickedExerciseId?.split("|") || [];
422
- const [isExerciseListOpen, setExerciseListOpen] = useState(lasClickedTopicId === topicId && lastClickedAssignmentId === assignment.assignmentId);
415
+ const numOfIds = lastClickedExerciseId ? lastClickedExerciseId.split("|") : [];
416
+
417
+ // Define a single variable for clarity
418
+ const matchesLastClickedExercise = lastClickedExerciseId && (numOfIds.length === 4 && lastClickedExerciseId.endsWith(`|${assignment.assignmentId}`) && numOfIds[2] === topicId || numOfIds.length === 5 && numOfIds[3] === assignment.assignmentId && numOfIds[2] === topicId);
419
+ const [isExerciseListOpen, setExerciseListOpen] = useState(false);
420
+ useEffect(() => {
421
+ if (matchesLastClickedExercise) {
422
+ setExerciseListOpen(true);
423
+ }
424
+ }, [matchesLastClickedExercise]);
423
425
  const [isWholeTopicSelected, setIsWholeTopicSelected] = useState(false);
424
426
  useEffect(() => {
425
- if (lasClickedTopicId === topicId && topicRef.current && lastClickedAssignmentId === assignment.assignmentId) {
427
+ if (matchesLastClickedExercise && topicRef.current) {
426
428
  topicRef.current.scrollIntoView({
427
429
  behavior: "smooth",
428
430
  block: "start"
429
431
  });
430
432
  }
431
- }, []);
433
+ }, [matchesLastClickedExercise, topicRef]);
434
+ const currentTopic = (section?.topics || []).find(t => t.topicId === topicId) || {};
435
+ const hidden = new Set((currentTopic.hiddenExercises || []).map(x => String(x).toLowerCase()));
436
+ const baseRegular = [{
437
+ name: "translation",
438
+ description: "translation_exercise_desc"
439
+ }, {
440
+ name: "listening",
441
+ description: "listening_exercise_desc"
442
+ }, {
443
+ name: "pronunciation",
444
+ description: "pronunciation_exercise_desc"
445
+ }].filter(e => !hidden.has(e.name));
432
446
  let exercises;
433
447
  switch (useCase) {
434
448
  case "assignment-select":
435
- exercises = [{
436
- name: "translation",
437
- description: `translation_exercise_desc`
438
- }, {
439
- name: "listening",
440
- description: `listening_exercise_desc`
441
- }, {
442
- name: "pronunciation",
443
- description: `pronunciation_exercise_desc`
444
- }, {
449
+ exercises = [...baseRegular, {
445
450
  name: "roleplays",
446
- description: `roleplay_exercise_desc`
451
+ description: "roleplay_exercise_desc"
447
452
  }, {
448
453
  name: "bots",
449
- description: `chatbot_exercise_desc`
454
+ description: "chatbot_exercise_desc"
450
455
  }];
451
456
  break;
452
457
  case "assignment-view":
453
- exercises = [{
454
- name: "translation",
455
- description: `translation_exercise_desc`
456
- }, {
457
- name: "listening",
458
- description: `listening_exercise_desc`
459
- }, {
460
- name: "pronunciation",
461
- description: `pronunciation_exercise_desc`
462
- }, {
458
+ exercises = [...baseRegular, {
463
459
  name: "roleplays",
464
- description: `roleplay_exercise_desc`
460
+ description: "roleplay_exercise_desc"
465
461
  }, {
466
462
  name: "bots",
467
- description: `chatbot_exercise_desc`
463
+ description: "chatbot_exercise_desc"
468
464
  }].filter(exercise => selectedExercises?.some(e => e.name === exercise.name || exercise.name === "roleplays" && e.roleplayId || exercise.name === "bots" && e.botId));
469
465
  break;
470
466
  case "assignment-start":
471
- exercises = [{
472
- name: "translation",
473
- description: `translation_exercise_desc`
474
- }, {
475
- name: "listening",
476
- description: `listening_exercise_desc`
477
- }, {
478
- name: "pronunciation",
479
- description: `pronunciation_exercise_desc`
480
- }, {
467
+ exercises = [...baseRegular, {
481
468
  name: "roleplays",
482
- description: `roleplay_exercise_desc`
469
+ description: "roleplay_exercise_desc"
483
470
  }, {
484
471
  name: "bots",
485
- description: `chatbot_exercise_desc`
472
+ description: "chatbot_exercise_desc"
486
473
  }].filter(exercise => selectedExercises?.some(e => e.name === exercise.name || exercise.name === "roleplays" && e.roleplayId || exercise.name === "bots" && e.botId));
474
+ exercises = exercises.filter(ex => !["translation", "listening", "pronunciation"].includes(ex.name) || !hidden.has(ex.name));
487
475
  break;
488
476
  default:
489
477
  exercises = [];
@@ -504,7 +492,8 @@ function Topic({
504
492
  const expectedRoleplayCount = roleplays?.length * games?.length || 0;
505
493
  const expectedBotCount = bots?.length || 0;
506
494
  const totalExpected = (exercises?.filter(e => e.name !== "roleplays").length || 0) + expectedRoleplayCount + expectedBotCount;
507
- const selectedRegular = selectedExercises?.filter(exercise => !exercise.roleplayId && !exercise.botId && exercise.courseSectionTopicId === `${courseId}|${sectionId}|${topicId}`).length || 0;
495
+ const regularNames = new Set(baseRegular.map(e => e.name));
496
+ const selectedRegular = selectedExercises?.filter(e => e.courseSectionTopicId === courseSectionTopicId && !e.roleplayId && !e.botId && regularNames.has(e.name)).length || 0;
508
497
  const selectedRoleplays = selectedExercises?.filter(exercise => exercise.roleplayId && roleplays?.some(roleplay => roleplay.roleplayId === exercise.roleplayId)).length || 0;
509
498
  const selectedBots = selectedExercises?.filter(exercise => exercise.botId && bots?.some(bot => bot.botId === exercise.botId)).length || 0;
510
499
  const totalSelected = selectedRegular + selectedRoleplays + selectedBots;
@@ -517,10 +506,10 @@ function Topic({
517
506
  handleSelectExercise(removeExercises);
518
507
  } else {
519
508
  let exercisesToAdd = [];
520
- exercises?.filter(e => e.name !== "roleplays" && e.name !== "bots").forEach(exercise => {
509
+ baseRegular.forEach(exercise => {
521
510
  if (!selectedExercises?.some(e => e.courseSectionTopicId === courseSectionTopicId && e.name === exercise.name)) {
522
511
  exercisesToAdd.push({
523
- courseSectionTopicId: courseSectionTopicId,
512
+ courseSectionTopicId,
524
513
  name: exercise.name
525
514
  });
526
515
  }
@@ -557,7 +546,7 @@ function Topic({
557
546
  const selectedRoleplays = selectedExercises?.filter(e => e.courseSectionTopicId === courseSectionTopicId && e.roleplayId).length || 0;
558
547
  const selectedBots = selectedExercises?.filter(e => e.courseSectionTopicId === courseSectionTopicId && e.botId).length || 0;
559
548
  const selectedCount = selectedRegular + selectedRoleplays + selectedBots;
560
- const totalRegular = 3;
549
+ const totalRegular = baseRegular.length;
561
550
  const totalRoleplays = (roleplays?.length || 0) * (games?.length || 0);
562
551
  const totalBots = bots?.length || 0;
563
552
  const totalCount = totalRegular + totalRoleplays + totalBots;
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
- import Avatar, { Piece } from "@nualang/avatars";
2
+ import AvatarMod, { Piece } from "@nualang/avatars";
3
3
  import AvatarSwitcher from "../../Misc/AvatarSwitcher/AvatarSwitcher";
4
4
  import Box from "@mui/material/Box";
5
5
  import Button from "@mui/material/Button";
@@ -24,6 +24,7 @@ import options from "./options";
24
24
  import PropTypes from "prop-types";
25
25
  import { useDropzone } from "react-dropzone";
26
26
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
27
+ const Avatar = AvatarMod.default || AvatarMod;
27
28
  const getColor = (props, theme) => {
28
29
  if (props.isDragAccept) {
29
30
  return theme.palette.success.main;
@@ -120,16 +120,24 @@ function BotSettings({
120
120
  className: classes.group,
121
121
  name: "isChallengeBot",
122
122
  value: isChallengeBot,
123
- onChange: handleChange,
123
+ onChange: e => {
124
+ const value = e.target.value === "true";
125
+ handleChange({
126
+ target: {
127
+ name: "isChallengeBot",
128
+ value
129
+ }
130
+ });
131
+ },
124
132
  onBlur: handleBlur,
125
133
  helperText: touched.isChallengeBot ? errors.isChallengeBot : "",
126
134
  error: touched.isChallengeBot && Boolean(errors.isChallengeBot),
127
135
  children: [/*#__PURE__*/_jsx(FormControlLabel, {
128
- value: true,
136
+ value: "true",
129
137
  control: /*#__PURE__*/_jsx(Radio, {}),
130
138
  label: t("enabled")
131
139
  }), /*#__PURE__*/_jsx(FormControlLabel, {
132
- value: false,
140
+ value: "false",
133
141
  control: /*#__PURE__*/_jsx(Radio, {}),
134
142
  label: t("disabled")
135
143
  })]
@@ -1,9 +1,8 @@
1
1
  import { useEffect, useState, useCallback, useRef } from "react";
2
2
  import { Link as RouterLink, useNavigate, useParams } from "react-router-dom";
3
- import { ListSubheader, List, ListItem, ListItemIcon, ListItemText, ListItemAvatar, ListItemSecondaryAction, IconButton, Button, Collapse, Typography, Box, Tooltip, LinearProgress, Menu, MenuItem, Avatar } from "@mui/material";
4
- import { useTheme, alpha } from "@mui/material/styles";
3
+ import { ListSubheader, List, ListItem, ListItemIcon, ListItemText, ListItemAvatar, ListItemSecondaryAction, IconButton, Button, Collapse, Typography, Box, Tooltip, Menu, MenuItem, Avatar } from "@mui/material";
4
+ import { useTheme } from "@mui/material/styles";
5
5
  import { makeStyles } from "tss-react/mui";
6
- import { withStyles } from "tss-react/mui";
7
6
  import CheckBox from "@mui/icons-material/CheckBox";
8
7
  import CheckBoxOutlineBlank from "@mui/icons-material/CheckBoxOutlineBlank";
9
8
  import AddCircle from "@mui/icons-material/AddCircle";
@@ -26,7 +25,7 @@ import ImageSearchIcon from "@mui/icons-material/ImageSearch";
26
25
  import Skeleton from "@mui/material/Skeleton";
27
26
  import OndemandVideoIcon from "@mui/icons-material/OndemandVideo";
28
27
  import ColorLinearProgress from "../../Misc/ColorLinearProgress/ColorLinearProgress";
29
- import { randomId, calcCompletions, calcPercentageCompletion } from "../../utils/index";
28
+ import { calcCompletions, calcPercentageCompletion } from "../../utils/index";
30
29
  import SelectClassroom from "../../Dialogs/SelectClassroom/SelectClassroom";
31
30
  import { CardInfoSectionItem } from "../../Cards/CardElements";
32
31
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
@@ -142,7 +141,7 @@ function ExerciseList({
142
141
  })
143
142
  }),
144
143
  children: exercises.map((exercise, i) => {
145
- const keyId = `exerc_${randomId()}${i}`;
144
+ const keyId = `exerc_${exercise.name}${i}`;
146
145
  return /*#__PURE__*/_jsx(Exercise, {
147
146
  t: t,
148
147
  ...exercise,
@@ -1152,7 +1151,14 @@ function CourseOutline({
1152
1151
  classroomId,
1153
1152
  ...otherProps
1154
1153
  }) {
1155
- const [sects, setSects] = useState(calcCompletions(sections, completions));
1154
+ const [sects, setSects] = useState([]);
1155
+ useEffect(() => {
1156
+ if (isMember) {
1157
+ setSects(calcCompletions(sections, completions));
1158
+ } else {
1159
+ setSects(sections);
1160
+ }
1161
+ }, [isMember, sections?.length, completions?.length]);
1156
1162
  const moveSection = useCallback((dragIndex, hoverIndex) => {
1157
1163
  const dragSection = sects[dragIndex];
1158
1164
  const newSections = update(sects, {
@@ -1064,19 +1064,10 @@ function Exercises(props) {
1064
1064
  }
1065
1065
  });
1066
1066
  }
1067
- const [roleplaysArray, setRoleplaysArray] = useState([]);
1068
- const roleplaysStringified = JSON.stringify(roleplays);
1067
+ const [roleplaysArray, setRoleplaysArray] = useState(roleplays);
1069
1068
  const onDropRoleplay = () => {
1070
1069
  updateRoleplays(roleplaysArray);
1071
1070
  };
1072
- useEffect(() => {
1073
- if (roleplays && Array.isArray(roleplays) && roleplays.length && roleplays.length > 0) {
1074
- const sortedRoleplays = [...roleplays].sort((a, b) => a.idx - b.idx);
1075
- setRoleplaysArray(sortedRoleplays);
1076
- } else if (roleplays && Array.isArray(roleplays)) {
1077
- setRoleplaysArray(roleplays);
1078
- }
1079
- }, [roleplays, roleplaysStringified]);
1080
1071
  const moveRoleplay = useCallback((dragIndex, hoverIndex) => {
1081
1072
  const dragRoleplay = roleplaysArray[dragIndex];
1082
1073
  const newRoleplays = update(roleplaysArray, {
@@ -1087,24 +1078,15 @@ function Exercises(props) {
1087
1078
  });
1088
1079
  setRoleplaysArray(newRoleplays);
1089
1080
  }, [roleplaysArray]);
1090
- const [botsArray, setBotsArray] = useState([]);
1091
- const botsStringified = JSON.stringify(bots);
1081
+ const [botsArray, setBotsArray] = useState(() => {
1082
+ if (!isChallengeModeStudent && !isCreator && !parentClassroom?.isCreator) {
1083
+ return bots.filter(bot => !bot.isChallengeBot || bot.isChallengeBot === false);
1084
+ }
1085
+ return bots;
1086
+ });
1092
1087
  const onDropBot = () => {
1093
1088
  updateBots(botsArray);
1094
1089
  };
1095
- useEffect(() => {
1096
- if (bots && Array.isArray(bots) && bots.length && bots.length > 0) {
1097
- const sortedBots = [...bots].sort((a, b) => a.idx - b.idx);
1098
- setBotsArray(sortedBots);
1099
- } else if (bots && Array.isArray(bots)) {
1100
- setBotsArray(bots);
1101
- }
1102
- }, [bots, botsStringified]);
1103
- useEffect(() => {
1104
- if (!isChallengeModeStudent && !isCreator && !parentClassroom?.isCreator) {
1105
- setBotsArray(bots.filter(bot => !bot.isChallengeBot || bot.isChallengeBot === false));
1106
- }
1107
- }, [bots, isChallengeModeStudent, isCreator]);
1108
1090
  const moveBot = useCallback((dragIndex, hoverIndex) => {
1109
1091
  const dragBot = botsArray[dragIndex];
1110
1092
  const newBots = update(botsArray, {
@@ -1,9 +1,10 @@
1
1
  import { useState, useEffect } from "react";
2
2
  import PropTypes from "prop-types";
3
- import Avatar from "@nualang/avatars";
4
3
  import sample from "lodash/sample";
5
4
  import options from "../../Dialogs/AvatarDialog/options";
5
+ import AvatarMod from "@nualang/avatars";
6
6
  import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
7
+ const Avatar = AvatarMod.default || AvatarMod;
7
8
  function AvatarSwitcher({
8
9
  style = null
9
10
  }) {
@@ -1,9 +1,8 @@
1
- import { useState, useEffect } from "react";
1
+ import { useState, useMemo } from "react";
2
2
  import "regenerator-runtime/runtime";
3
3
  import useMediaQuery from "@mui/material/useMediaQuery";
4
- import { useTheme, alpha } from "@mui/material/styles";
4
+ import { useTheme } from "@mui/material/styles";
5
5
  import { makeStyles } from "tss-react/mui";
6
- import { withStyles } from "tss-react/mui";
7
6
  import { Link as RouterLink, useParams, useNavigate } from "react-router-dom";
8
7
  import { Typography, Avatar, Box, Menu, MenuItem, Button, Paper, Tooltip, Card, Grid, CardActionArea, CardContent } from "@mui/material";
9
8
  import LiveTvIcon from "@mui/icons-material/LiveTv";
@@ -102,140 +101,6 @@ const useStyles = makeStyles()(theme => ({
102
101
  position: "relative"
103
102
  }
104
103
  }));
105
- const calcCompletions = (topic = {}, completions = [], _bots = [], _roleplays = []) => {
106
- let isRoleplaysHidden;
107
- let isPronunciationHidden;
108
- let isBotsHidden;
109
- let isListeningHidden;
110
- let isTranslationHidden;
111
- let isPhrases;
112
- let completedExerciseCount = 0;
113
- let totalExercises = 0;
114
- if (topic.phrases && topic.phrases.length > 0) {
115
- isPhrases = true;
116
- }
117
- if (topic.isTopicHidden) {
118
- isRoleplaysHidden = true;
119
- isBotsHidden = true;
120
- isPronunciationHidden = true;
121
- isListeningHidden = true;
122
- isTranslationHidden = true;
123
- } else if (topic && topic.hiddenExercises) {
124
- topic.hiddenExercises.forEach(exercise => {
125
- if (exercise === "roleplays") {
126
- isRoleplaysHidden = true;
127
- }
128
- if (exercise === "chatbots") {
129
- isBotsHidden = true;
130
- }
131
- if (exercise === "pronunciation") {
132
- isPronunciationHidden = true;
133
- }
134
- if (exercise === "listening") {
135
- isListeningHidden = true;
136
- }
137
- if (exercise === "translation") {
138
- isTranslationHidden = true;
139
- }
140
- });
141
- }
142
- if (isPhrases) {
143
- if (!isPronunciationHidden) {
144
- totalExercises += 1;
145
- }
146
- if (!isListeningHidden) {
147
- totalExercises += 1;
148
- }
149
- if (!isTranslationHidden) {
150
- totalExercises += 1;
151
- }
152
- }
153
- const completedPronunciation = completions.filter(c => c.topicId === topic.topicId && c.exercise === "pronunciation").length;
154
- const completedTranslation = completions.filter(c => c.topicId === topic.topicId && c.exercise === "translation").length;
155
- const completedListening = completions.filter(c => c.topicId === topic.topicId && c.exercise === "listening").length;
156
- completedExerciseCount += !isPronunciationHidden && completedPronunciation > 0 ? 1 : 0;
157
- completedExerciseCount += !isTranslationHidden && completedTranslation > 0 ? 1 : 0;
158
- completedExerciseCount += !isListeningHidden && completedListening > 0 ? 1 : 0;
159
- let completedRoleplays = 0;
160
- let completedBots = 0;
161
- const roleplays = _roleplays.map(roleplay => {
162
- if (isRoleplaysHidden || roleplay.isRoleplayHidden || topic.isTopicHidden) {
163
- return {
164
- ...roleplay,
165
- completedCount: 0
166
- };
167
- } else {
168
- const completedCount = completions.reduce((acc, completion) => {
169
- if (completion && completion.topicId === topic.topicId && completion.exercise.startsWith("roleplay") && completion.roleplayId === roleplay.roleplayId) {
170
- acc += 1;
171
- }
172
- return acc;
173
- }, 0);
174
- if (completedCount > 0) {
175
- completedRoleplays += 1;
176
- completedExerciseCount += 1;
177
- }
178
- totalExercises += 1;
179
- return {
180
- ...roleplay,
181
- completedCount: completedCount
182
- };
183
- }
184
- });
185
- const bots = _bots.map(bot => {
186
- if (isBotsHidden || bot.isBotHidden || topic.isTopicHidden) {
187
- return {
188
- ...bot,
189
- completedCount: 0
190
- };
191
- } else {
192
- const completedCount = completions.reduce((acc, completion) => {
193
- if (completion && completion.exercise === "bot" && completion.botId === bot.botId) {
194
- acc += 1;
195
- }
196
- return acc;
197
- }, 0);
198
- if (completedCount > 0) {
199
- completedBots += 1;
200
- completedExerciseCount += 1;
201
- }
202
- totalExercises += 1;
203
- return {
204
- ...bot,
205
- completedCount: completedCount
206
- };
207
- }
208
- });
209
- return {
210
- ...topic,
211
- roleplays: roleplays,
212
- bots: bots,
213
- completion: {
214
- completed: completedExerciseCount,
215
- total: totalExercises,
216
- percentage: completedExerciseCount / totalExercises * 100,
217
- pronunciation: {
218
- completedCount: completedPronunciation
219
- },
220
- translation: {
221
- completedCount: completedTranslation
222
- },
223
- listening: {
224
- completedCount: completedListening
225
- },
226
- roleplays: {
227
- completed: completedRoleplays,
228
- total: roleplays.length,
229
- percentage: completedRoleplays / roleplays.length * 100
230
- },
231
- bots: {
232
- completed: completedBots,
233
- total: bots.length,
234
- percentage: completedBots / bots.length * 100
235
- }
236
- }
237
- };
238
- };
239
104
  function OverflowMenu({
240
105
  t,
241
106
  topicId,
@@ -664,14 +529,16 @@ function Topic({
664
529
  };
665
530
  const [isCreatePhraseDialogOpen, setIsCreatePhraseDialogOpen] = useState(false);
666
531
  const [isUploadPhrasesDialogOpen, setIsUploadPhrasesDialogOpen] = useState(false);
667
- let topicCompletion = {
668
- completed: 0,
669
- total: 0,
670
- percentage: 0
671
- };
672
- if (isMember && topic && topic.completion) {
673
- topicCompletion = calcTopicCompletions(topic, topic.completion, false).completion;
674
- }
532
+ const topicCompletion = useMemo(() => {
533
+ if (isMember && topic?.topicId && topic?.completions?.length) {
534
+ return calcTopicCompletions(topic, topic.completions, false).completion;
535
+ }
536
+ return {
537
+ completed: 0,
538
+ total: 0,
539
+ percentage: 0
540
+ };
541
+ }, [isMember, topic?.topicId, topic?.completions?.length]);
675
542
  const {
676
543
  percentage,
677
544
  completed,
@@ -697,7 +564,9 @@ function Topic({
697
564
  } else {
698
565
  initialTabValue = 0;
699
566
  }
700
- if (!topic) {
567
+
568
+ // if (!topic) {
569
+ if (topic && Object.keys(topic).length === 0) {
701
570
  return /*#__PURE__*/_jsxs(_Fragment, {
702
571
  children: [/*#__PURE__*/_jsx("div", {
703
572
  className: classes.cardTop,
@@ -1437,7 +1306,8 @@ export default function ViewTopic({
1437
1306
  children: /*#__PURE__*/_jsx(Topic, {
1438
1307
  t: t,
1439
1308
  isLoading: isLoading,
1440
- courseSettings: courseSettings
1309
+ courseSettings: courseSettings,
1310
+ ...otherProps
1441
1311
  })
1442
1312
  }), !isLoading && topic && Object.keys(topic).length && /*#__PURE__*/_jsx(_Fragment, {
1443
1313
  children: /*#__PURE__*/_jsx(Topic, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nualang/nualang-ui-components",
3
- "version": "0.1.1295",
3
+ "version": "0.1.1297",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "files": [
@@ -21,7 +21,7 @@
21
21
  "@hookform/resolvers": "^3.6.0",
22
22
  "@json2csv/plainjs": "^7.0.1",
23
23
  "@nualang/avatars": "2.0.3",
24
- "@nualang/nualang-api-and-queries": "^1.1.22",
24
+ "@nualang/nualang-api-and-queries": "^1.1.23",
25
25
  "@stripe/react-stripe-js": "^2.1.1",
26
26
  "@stripe/stripe-js": "^1.54.2",
27
27
  "ajv-keywords": "^3.5.2",
@@ -129,11 +129,13 @@
129
129
  "globals": "^16.0.0",
130
130
  "intersection-observer": "^0.12.0",
131
131
  "jsdom": "^27.0.0",
132
- "msw": "^2.10.5",
132
+ "msw": "^2.11.3",
133
+ "msw-storybook-addon": "^2.0.5",
133
134
  "prettier": "^3.2.5",
134
135
  "react": "19.1.1",
135
136
  "react-dom": "19.1.1",
136
137
  "react-router-dom": "^6.30.0",
138
+ "rimraf": "^6.0.1",
137
139
  "storybook": "^9.1.3",
138
140
  "vitest": "^3.1.1",
139
141
  "zx": "^8.5.4"
@@ -169,7 +171,9 @@
169
171
  ]
170
172
  },
171
173
  "msw": {
172
- "workerDirectory": "public"
174
+ "workerDirectory": [
175
+ "public"
176
+ ]
173
177
  },
174
178
  "overrrides": {
175
179
  "@emotion/react": "$@emotion/react",