@nualang/nualang-ui-components 0.1.1326 → 0.1.1329

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.
@@ -73,6 +73,7 @@ export default function CreateAssignmentDialog({
73
73
  exercises: submittedExercises
74
74
  });
75
75
  const [isTitleEdited, setIsTitleEdited] = useState(Boolean(initialData.title));
76
+ const [isCreating, setIsCreating] = useState(false);
76
77
  const classroomQuery = useQueries({
77
78
  queries: (Array.isArray(assignment.classroomId) ? assignment.classroomId : [assignment.classroomId]).map(cId => ({
78
79
  queryKey: classroomQuerys.classroomKeys.item(cId, username),
@@ -262,6 +263,7 @@ export default function CreateAssignmentDialog({
262
263
  }));
263
264
  };
264
265
  const handleCreateAssignment = async () => {
266
+ setIsCreating(true);
265
267
  try {
266
268
  await Promise.all(assignment.classroomId.map(async cId => {
267
269
  const selectedClassroomMembers = uniqueClassroomMembers.filter(member => member.classroomId === cId).map(student => student.memberId).filter(studentId => assignment?.assignedStudents.includes(studentId));
@@ -275,6 +277,8 @@ export default function CreateAssignmentDialog({
275
277
  handleClose();
276
278
  } catch (error) {
277
279
  console.error("Error creating assignments:", error);
280
+ } finally {
281
+ setIsCreating(false);
278
282
  }
279
283
  };
280
284
  const handleRemoveExercise = toRemove => {
@@ -647,7 +651,7 @@ export default function CreateAssignmentDialog({
647
651
  },
648
652
  children: /*#__PURE__*/_jsx(Button, {
649
653
  "data-cy": "assignment-assign-button",
650
- disabled: !assignment.assignedStudents?.length || !assignment.scheduleDate || !assignment.dueDate || !assignment.title || !assignment.classroomId || !assignment.exercises?.length,
654
+ disabled: isCreating || !assignment.assignedStudents?.length || !assignment.scheduleDate || !assignment.dueDate || !assignment.title || !assignment.classroomId || !assignment.exercises?.length,
651
655
  onClick: async () => await handleCreateAssignment(),
652
656
  children: t("assign")
653
657
  })
@@ -4,6 +4,8 @@ import { grey, green } from "@mui/material/colors";
4
4
  import AutoFixHighIcon from "@mui/icons-material/AutoFixHigh";
5
5
  import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
6
6
  import CheckCircleIcon from "@mui/icons-material/CheckCircle";
7
+ import TrendingUpIcon from "@mui/icons-material/TrendingUp";
8
+ import TrendingDownIcon from "@mui/icons-material/TrendingDown";
7
9
  import { Formik } from "formik";
8
10
  import * as Yup from "yup";
9
11
  import RecordingDialog from "../../Dialogs/RecordingDialog/RecordingDialog";
@@ -13,8 +15,39 @@ import level2Rubric from "../../Dialogs/PDFViewer/rubrics/level2Rubric.pdf";
13
15
  import level3Rubric from "../../Dialogs/PDFViewer/rubrics/level3Rubric.pdf";
14
16
  import level4Rubric from "../../Dialogs/PDFViewer/rubrics/level4Rubric.pdf";
15
17
  import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
16
- import NualaCreating from "../../Misc/NualaCreating/NualaCreating";
17
18
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
19
+ const getPerformanceLevel = (student, aiGrade) => {
20
+ if (!aiGrade || Object.keys(aiGrade).length === 0) return null;
21
+ const keyVariants = [student.username, `${student.username}Student`, student.username?.replace(/\s+/g, "")];
22
+ const aiData = keyVariants.map(k => aiGrade?.[k]).find(v => v && typeof v === "object") || null;
23
+ return aiData?.LevelPerformance || null;
24
+ };
25
+ const getPerformanceChipProps = (level, t) => {
26
+ switch (level?.toLowerCase()) {
27
+ case 'above':
28
+ return {
29
+ label: t('above_level'),
30
+ color: 'success',
31
+ icon: /*#__PURE__*/_jsx(TrendingUpIcon, {
32
+ sx: {
33
+ fontSize: 16
34
+ }
35
+ })
36
+ };
37
+ case 'below':
38
+ return {
39
+ label: t('below_level'),
40
+ color: 'warning',
41
+ icon: /*#__PURE__*/_jsx(TrendingDownIcon, {
42
+ sx: {
43
+ fontSize: 16
44
+ }
45
+ })
46
+ };
47
+ default:
48
+ return null;
49
+ }
50
+ };
18
51
  export default function FeedbackCard({
19
52
  t = x => x,
20
53
  discussionData = {},
@@ -39,7 +72,8 @@ export default function FeedbackCard({
39
72
  goToTimestamp = () => {},
40
73
  startTimes = {},
41
74
  canSubmitAll = {},
42
- setCanSubmit = () => {}
75
+ setCanSubmit = () => {},
76
+ setIsGeneratingIndividual = () => {}
43
77
  }) {
44
78
  const [isRecordingDialogOpen, setIsRecordingDialogOpen] = useState(false);
45
79
  const [hasBeenEdited, setHasBeenEdited] = useState(false);
@@ -50,6 +84,7 @@ export default function FeedbackCard({
50
84
  const [openPDF, setOpenPDF] = useState(false);
51
85
  const [pageNumber, setPageNumber] = useState(1);
52
86
  const [scale, setScale] = useState(1);
87
+ const [isGeneratingFeedback, setIsGeneratingFeedback] = useState(false);
53
88
  const handleChangeCheckbox = event => {
54
89
  setCheckedSubmissions({
55
90
  ...checkedSubmissions,
@@ -81,18 +116,21 @@ export default function FeedbackCard({
81
116
  attendedMembers.forEach(student => {
82
117
  const existingStudentFeedback = existingFeedback[student.memberId];
83
118
  feedbackData[student.memberId] = {
84
- feedbackText: existingStudentFeedback?.feedbackText || ""
119
+ feedbackText: existingStudentFeedback?.feedbackText || "",
120
+ levelPerformance: existingStudentFeedback?.levelPerformance || null
85
121
  };
86
122
  });
87
123
  notAttendedMembers.forEach(student => {
88
124
  feedbackData[student.memberId] = {
89
- feedbackText: existingFeedback[student.memberId]?.feedbackText || t("not_present")
125
+ feedbackText: existingFeedback[student.memberId]?.feedbackText || t("not_present"),
126
+ levelPerformance: existingFeedback[student.memberId]?.levelPerformance || null
90
127
  };
91
128
  });
92
129
  } else {
93
130
  selectedMembers.forEach(student => {
94
131
  feedbackData[student.memberId] = {
95
- feedbackText: existingFeedback[student.memberId]?.feedbackText || ""
132
+ feedbackText: existingFeedback[student.memberId]?.feedbackText || "",
133
+ levelPerformance: existingFeedback[student.memberId]?.levelPerformance || null
96
134
  };
97
135
  });
98
136
  }
@@ -132,6 +170,9 @@ export default function FeedbackCard({
132
170
  if (aiData?.Feedback) {
133
171
  formik.setFieldValue(`feedbackData.${student.memberId}.feedbackText`, aiData.Feedback);
134
172
  }
173
+ if (aiData?.LevelPerformance) {
174
+ formik.setFieldValue(`feedbackData.${student.memberId}.levelPerformance`, aiData.LevelPerformance);
175
+ }
135
176
  });
136
177
  setHasBeenEdited(true);
137
178
  setIsSubmitted(false);
@@ -159,7 +200,14 @@ export default function FeedbackCard({
159
200
  }, [submitTrigger]);
160
201
  const handleGenerateAI = async () => {
161
202
  setAiJustGenerated(true);
162
- await onGenerateFeedback(discussionData);
203
+ setIsGeneratingFeedback(true);
204
+ setIsGeneratingIndividual(true);
205
+ try {
206
+ await onGenerateFeedback(discussionData);
207
+ } finally {
208
+ setIsGeneratingFeedback(false);
209
+ setIsGeneratingIndividual(false);
210
+ }
163
211
  };
164
212
  const handleOpenPDF = () => {
165
213
  setOpenPDF(true);
@@ -182,9 +230,18 @@ export default function FeedbackCard({
182
230
  const feedbackDataWithAbsent = {
183
231
  ...values.feedbackData
184
232
  };
233
+ selectedMembers.forEach(student => {
234
+ const existingLevel = feedbackDataWithAbsent[student.memberId]?.levelPerformance;
235
+ const aiLevel = getPerformanceLevel(student, aiGrade);
236
+ feedbackDataWithAbsent[student.memberId] = {
237
+ ...feedbackDataWithAbsent[student.memberId],
238
+ levelPerformance: existingLevel ?? aiLevel ?? null
239
+ };
240
+ });
185
241
  notAttendedMembers.forEach(student => {
186
242
  feedbackDataWithAbsent[student.memberId] = {
187
- feedbackText: t("not_present")
243
+ feedbackText: t("not_present"),
244
+ levelPerformance: feedbackDataWithAbsent[student.memberId]?.levelPerformance ?? null
188
245
  };
189
246
  });
190
247
  await onSubmitFeedback({
@@ -241,23 +298,7 @@ export default function FeedbackCard({
241
298
  overflow: "hidden",
242
299
  backgroundColor: "background.paper"
243
300
  },
244
- children: [aiJustGenerated ? /*#__PURE__*/_jsxs(Grid, {
245
- sx: {
246
- ...(!aiJustGenerated && {
247
- display: "none"
248
- })
249
- },
250
- mx: 2,
251
- size: 12,
252
- children: [/*#__PURE__*/_jsx(Typography, {
253
- variant: "h6",
254
- sx: {
255
- mt: 2,
256
- mb: 2
257
- },
258
- children: t("generating_feedback")
259
- }), /*#__PURE__*/_jsx(NualaCreating, {})]
260
- }) : /*#__PURE__*/_jsxs(_Fragment, {
301
+ children: [/*#__PURE__*/_jsxs(_Fragment, {
261
302
  children: [/*#__PURE__*/_jsxs(Box, {
262
303
  sx: {
263
304
  px: 3,
@@ -322,6 +363,8 @@ export default function FeedbackCard({
322
363
  const isAbsent = hasSubmission && !joinedMembers.includes(student.memberId);
323
364
  const disabled = !hasSubmission || isAbsent;
324
365
  const studentFeedback = values.feedbackData[student.memberId] || {};
366
+ const perfLevel = studentFeedback.levelPerformance || getPerformanceLevel(student, aiGrade);
367
+ const chipProps = getPerformanceChipProps(perfLevel, t);
325
368
  return /*#__PURE__*/_jsx(Box, {
326
369
  sx: {
327
370
  py: 2
@@ -338,7 +381,7 @@ export default function FeedbackCard({
338
381
  children: /*#__PURE__*/_jsxs(Box, {
339
382
  display: "flex",
340
383
  alignItems: "center",
341
- gap: 2,
384
+ gap: 1.5,
342
385
  children: [/*#__PURE__*/_jsx(Avatar, {
343
386
  alt: student.username,
344
387
  src: student.userImage,
@@ -347,12 +390,65 @@ export default function FeedbackCard({
347
390
  height: 48,
348
391
  filter: isAbsent ? "grayscale(100%)" : "none"
349
392
  }
350
- }), /*#__PURE__*/_jsx(Typography, {
351
- fontWeight: 600,
352
- sx: {
353
- color: isAbsent ? grey[500] : "text.primary"
354
- },
355
- children: student.username
393
+ }), /*#__PURE__*/_jsxs(Box, {
394
+ display: "flex",
395
+ flexDirection: "column",
396
+ gap: 0.5,
397
+ children: [/*#__PURE__*/_jsx(Typography, {
398
+ fontWeight: 600,
399
+ sx: {
400
+ color: isAbsent ? grey[500] : "text.primary"
401
+ },
402
+ children: student.username
403
+ }), chipProps && hasSubmission && !isAbsent && /*#__PURE__*/_jsx(Tooltip, {
404
+ title: /*#__PURE__*/_jsxs(Box, {
405
+ sx: {
406
+ p: 0.5
407
+ },
408
+ children: [/*#__PURE__*/_jsx(Typography, {
409
+ variant: "body2",
410
+ fontWeight: "bold",
411
+ sx: {
412
+ mb: 0.5
413
+ },
414
+ children: t('ai_performance_assessment')
415
+ }), /*#__PURE__*/_jsx(Typography, {
416
+ variant: "caption",
417
+ sx: {
418
+ display: 'block',
419
+ mb: 0.5
420
+ },
421
+ children: t('performance_chip_tooltip')
422
+ }), /*#__PURE__*/_jsx(Typography, {
423
+ variant: "caption",
424
+ sx: {
425
+ fontStyle: 'italic',
426
+ opacity: 0.9
427
+ },
428
+ children: t('performance_chip_note')
429
+ })]
430
+ }),
431
+ placement: "top",
432
+ children: /*#__PURE__*/_jsx(Chip, {
433
+ label: chipProps.label,
434
+ color: chipProps.color,
435
+ size: "small",
436
+ icon: chipProps.icon,
437
+ sx: {
438
+ width: 'fit-content',
439
+ fontWeight: 600,
440
+ fontSize: '0.7rem',
441
+ height: '22px',
442
+ '& .MuiChip-label': {
443
+ paddingLeft: '8px',
444
+ paddingRight: '8px'
445
+ },
446
+ '&:hover': {
447
+ opacity: 0.9
448
+ }
449
+ }
450
+ })
451
+ })]
356
452
  })]
357
453
  })
358
454
  }), /*#__PURE__*/_jsx(Grid, {
@@ -420,16 +516,16 @@ export default function FeedbackCard({
420
516
  },
421
517
  children: [/*#__PURE__*/_jsx(Button, {
422
518
  variant: "outlined",
423
- disabled: !hasSubmission || aiJustGenerated,
519
+ disabled: !hasSubmission || isGeneratingFeedback,
424
520
  onClick: handleOpenRecordingDialog,
425
521
  children: t("view_recording")
426
522
  }), /*#__PURE__*/_jsx(Button, {
427
523
  variant: "contained",
428
524
  color: "secondary",
429
- disabled: !hasSubmission || aiJustGenerated,
525
+ disabled: !hasSubmission || isGeneratingFeedback,
430
526
  onClick: handleGenerateAI,
431
527
  endIcon: /*#__PURE__*/_jsx(AutoFixHighIcon, {}),
432
- children: t("generate_feedback")
528
+ children: isGeneratingFeedback ? t("generating") : t("generate_feedback")
433
529
  }), /*#__PURE__*/_jsx(Button, {
434
530
  type: "submit",
435
531
  variant: "contained",
@@ -261,7 +261,8 @@ function GenerateBotDialog({
261
261
  required: true,
262
262
  onChange: handleChange,
263
263
  multiline: true,
264
- rows: 3
264
+ rows: 3,
265
+ autoFocus: true
265
266
  // maxLength={200}
266
267
  })
267
268
  }), /*#__PURE__*/_jsx(Grid, {