@nualang/nualang-ui-components 0.1.1305 → 0.1.1307

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.
Files changed (25) hide show
  1. package/dist/Assignments/AssignmentCard/AssignmentCard.js +46 -6
  2. package/dist/Assignments/AssignmentCardsList/AssignmentCardsList.js +72 -15
  3. package/dist/Assignments/CreateAssignmentDialog/CreateAssignmentDialog.js +24 -2
  4. package/dist/Cards/FeedbackCard/FeedbackCard.js +411 -0
  5. package/dist/Cards/RecordingCard/RecordingCard.js +161 -120
  6. package/dist/Cards/ScheduleCard/ScheduleCard.js +99 -44
  7. package/dist/Dialogs/GenerateDiscussion/GenerateDiscussion.js +171 -0
  8. package/dist/Dialogs/GroupedFeedbackDialog/GroupedFeedbackDialog.js +273 -0
  9. package/dist/Dialogs/PDFViewer/rubrics/level1Rubric.pdf +0 -0
  10. package/dist/Dialogs/PDFViewer/rubrics/level2Rubric.pdf +0 -0
  11. package/dist/Dialogs/PDFViewer/rubrics/level3Rubric.pdf +0 -0
  12. package/dist/Dialogs/PDFViewer/rubrics/level4Rubric.pdf +0 -0
  13. package/dist/Dialogs/RecordingDialog/RecordingDialog.js +61 -43
  14. package/dist/Forms/CreateMeetingMultiStepForm/CreateMeetingMultiStepForm.js +15 -3
  15. package/dist/Forms/CreateMeetingMultiStepForm/MeetingForm.js +144 -113
  16. package/dist/Screens/Classrooms/ViewClassroom/ViewClassroom.js +24 -14
  17. package/dist/Tables/MeetingPrompstList/MeetingPromptsList.js +78 -72
  18. package/dist/Tables/Progress/ProgressTable.js +22 -14
  19. package/dist/Tables/ScheduleListCards/ScheduleListCards.js +3 -97
  20. package/dist/img/NualaHeadphonesBackground.svg +39 -0
  21. package/dist/img/aaronAvatar.png +0 -0
  22. package/dist/img/connorAvatar.png +0 -0
  23. package/dist/img/jamieAvatar.png +0 -0
  24. package/dist/img/stephenAvatar.png +0 -0
  25. package/package.json +3 -3
@@ -1,12 +1,11 @@
1
1
  import { useMemo } from "react";
2
2
  import Typography from "@mui/material/Typography";
3
- import { Card, CardContent, Grid, Box, Divider, Button, LinearProgress, IconButton, Tooltip } from "@mui/material";
3
+ import { Card, CardContent, Grid, Box, Divider, Button, IconButton, Tooltip } from "@mui/material";
4
4
  import PropTypes from "prop-types";
5
5
  import AssignmentExerciseSelector from "../AssignmentExerciseSelector/AssignmentExerciseSelector";
6
6
  import { courses as courseQuerys, ReactQuery } from "@nualang/nualang-api-and-queries/Queries";
7
7
  import useConfirm from "../../hooks/useConfirm";
8
- import { Delete } from "@mui/icons-material";
9
- import { Edit } from "@mui/icons-material";
8
+ import { Delete, Edit } from "@mui/icons-material";
10
9
  import dayjs from "dayjs";
11
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
11
  export default function AssignmentCard({
@@ -41,6 +40,43 @@ export default function AssignmentCard({
41
40
  await deleteAssignment(classroomId, assignmentId);
42
41
  }
43
42
  };
43
+
44
+ // 🧠 Helper to format due or available text (adapted from MeetingPromptsList)
45
+ const formatAssignmentDueText = (availableDate, dueDate, t) => {
46
+ const now = dayjs();
47
+ const available = dayjs(availableDate);
48
+ const due = dayjs(dueDate);
49
+ const formattedDue = due.format("D MMM YYYY • h:mm A");
50
+ const formattedAvailable = available.format("D MMM YYYY • h:mm A");
51
+
52
+ // Case 1: Not yet available
53
+ if (available.isAfter(now)) {
54
+ const diffDays = available.startOf("day").diff(now.startOf("day"), "day");
55
+ let availableText = "";
56
+ if (diffDays === 0) {
57
+ availableText = t("available_today");
58
+ } else if (diffDays === 1) {
59
+ availableText = t("available_tomorrow");
60
+ } else {
61
+ availableText = `${t("available_in")} ${diffDays} ${t("days")}`;
62
+ }
63
+ return `${availableText} - ${formattedAvailable}`;
64
+ }
65
+
66
+ // Case 2: Available, now check due
67
+ const diffDays = due.startOf("day").diff(now.startOf("day"), "day");
68
+ let dueText = "";
69
+ if (diffDays < 0) {
70
+ dueText = t("past_due");
71
+ } else if (diffDays === 0) {
72
+ dueText = t("due_today");
73
+ } else if (diffDays === 1) {
74
+ dueText = t("due_tomorrow");
75
+ } else {
76
+ dueText = `${t("due_in")} ${diffDays} ${t("days")}`;
77
+ }
78
+ return `${dueText} - ${formattedDue}`;
79
+ };
44
80
  const {
45
81
  uniqueCourses
46
82
  } = useMemo(() => {
@@ -107,7 +143,10 @@ export default function AssignmentCard({
107
143
  alignItems: "center",
108
144
  children: [/*#__PURE__*/_jsx(Typography, {
109
145
  variant: "body1",
110
- component: "div",
146
+ fontWeight: 600,
147
+ sx: {
148
+ color: "text.primary"
149
+ },
111
150
  children: assignment.title
112
151
  }), isNew && /*#__PURE__*/_jsx(Box, {
113
152
  ml: 2,
@@ -126,7 +165,7 @@ export default function AssignmentCard({
126
165
  children: /*#__PURE__*/_jsx(Typography, {
127
166
  variant: "button",
128
167
  color: "text.secondary",
129
- children: dayjs(assignment.scheduleDate).isAfter(dayjs()) ? `${t("available_from")} ${dayjs(assignment.scheduleDate).format("DD MMM YYYY • h:mm A")}` : `${t("due")} ${dayjs(assignment.dueDate).format("DD MMM YYYY • h:mm A")}`
168
+ children: formatAssignmentDueText(assignment.scheduleDate, assignment.dueDate, t)
130
169
  })
131
170
  }), isCreator && /*#__PURE__*/_jsxs(Grid, {
132
171
  children: [/*#__PURE__*/_jsx(Tooltip, {
@@ -151,7 +190,8 @@ export default function AssignmentCard({
151
190
  mt: 2,
152
191
  children: [/*#__PURE__*/_jsx(Divider, {
153
192
  sx: {
154
- marginBottom: 1.5
193
+ marginBottom: "12px",
194
+ marginTop: "12px"
155
195
  }
156
196
  }), /*#__PURE__*/_jsxs(Grid, {
157
197
  container: true,
@@ -1,6 +1,6 @@
1
1
  import React, { useState, useEffect } from "react";
2
2
  import PropTypes from "prop-types";
3
- import { Typography, Box, IconButton, Tooltip, Button, Skeleton } from "@mui/material";
3
+ import { Typography, Box, IconButton, Tooltip, Button, Skeleton, Select, MenuItem, FormControl, InputLabel } from "@mui/material";
4
4
  import AssignmentCard from "../AssignmentCard/AssignmentCard";
5
5
  import Refresh from "@mui/icons-material/Refresh";
6
6
  import TeacherCreate from "../../img/teacher-create-2.svg";
@@ -31,6 +31,8 @@ const AssignmentCardsList = ({
31
31
  isChallengeModeStudent
32
32
  }) => {
33
33
  const [lastClickedExerciseId, setLastClickedExerciseId] = useState(null);
34
+ const [filter, setFilter] = useState("all"); // Filter state
35
+
34
36
  useEffect(() => {
35
37
  const stored = localStorage.getItem("lastClickedExercise");
36
38
  if (stored) {
@@ -44,6 +46,38 @@ const AssignmentCardsList = ({
44
46
  }
45
47
  }
46
48
  }, []);
49
+ const now = new Date();
50
+ const filteredAssignments = assignments.filter(assignment => {
51
+ if (!isCreator) {
52
+ if (Array.isArray(assignment.assignedStudents) && !assignment.assignedStudents.includes(memberId)) {
53
+ return false;
54
+ }
55
+ if (assignment.scheduleDate && new Date(assignment.scheduleDate) > now) {
56
+ return false;
57
+ }
58
+ }
59
+ return true;
60
+ }).filter(assignment => {
61
+ if (filter === "all") return true;
62
+ if (filter === "scheduled") {
63
+ return assignment.scheduleDate && new Date(assignment.scheduleDate) > now;
64
+ }
65
+ if (filter === "pastDue") {
66
+ return assignment.dueDate && new Date(assignment.dueDate) < now;
67
+ }
68
+ if (filter === "dueNext") {
69
+ return assignment.dueDate && new Date(assignment.dueDate) >= now;
70
+ }
71
+ return true;
72
+ }).sort((a, b) => {
73
+ if (filter === "dueNext") {
74
+ return new Date(a.dueDate) - new Date(b.dueDate);
75
+ }
76
+ if (filter === "scheduled") {
77
+ return new Date(a.scheduleDate) - new Date(b.scheduleDate);
78
+ }
79
+ return new Date(b.createdAt) - new Date(a.createdAt);
80
+ });
47
81
  if (isLoadingAssignments) {
48
82
  return /*#__PURE__*/_jsxs(Box, {
49
83
  mb: 1,
@@ -216,14 +250,39 @@ const AssignmentCardsList = ({
216
250
  mb: 1,
217
251
  children: [/*#__PURE__*/_jsxs(Box, {
218
252
  display: "flex",
219
- justifyContent: "space-between",
253
+ justifyContent: isCreator ? "space-between" : "flex-end",
220
254
  alignItems: "center",
221
255
  sx: {
222
- mb: 2
256
+ mb: 3,
257
+ mt: "8px"
223
258
  },
224
- children: [/*#__PURE__*/_jsx(Typography, {
225
- variant: "h5",
226
- children: t("assignments")
259
+ children: [isCreator && /*#__PURE__*/_jsxs(FormControl, {
260
+ size: "large",
261
+ sx: {
262
+ minWidth: 140,
263
+ marginTop: 1
264
+ },
265
+ children: [/*#__PURE__*/_jsx(InputLabel, {
266
+ children: t("view")
267
+ }), /*#__PURE__*/_jsxs(Select, {
268
+ value: filter,
269
+ onChange: e => setFilter(e.target.value),
270
+ label: t("view"),
271
+ autoWidth: true,
272
+ children: [/*#__PURE__*/_jsx(MenuItem, {
273
+ value: "all",
274
+ children: t("all")
275
+ }), /*#__PURE__*/_jsx(MenuItem, {
276
+ value: "dueNext",
277
+ children: t("due_next")
278
+ }), /*#__PURE__*/_jsx(MenuItem, {
279
+ value: "pastDue",
280
+ children: t("past_due")
281
+ }), /*#__PURE__*/_jsx(MenuItem, {
282
+ value: "scheduled",
283
+ children: t("scheduled")
284
+ })]
285
+ })]
227
286
  }), /*#__PURE__*/_jsx(Tooltip, {
228
287
  placement: "left",
229
288
  title: t("refresh_assignments"),
@@ -231,17 +290,15 @@ const AssignmentCardsList = ({
231
290
  onClick: refreshAssignments,
232
291
  "aria-label": "refresh",
233
292
  size: "large",
234
- children: /*#__PURE__*/_jsx(Refresh, {})
293
+ sx: {
294
+ size: "large"
295
+ },
296
+ children: /*#__PURE__*/_jsx(Refresh, {
297
+ fontSize: "inherit"
298
+ })
235
299
  })
236
300
  })]
237
- }), assignments.filter(assignment => {
238
- if (isCreator) return true;
239
- if (Array.isArray(assignment.assignedStudents) && !assignment.assignedStudents.includes(memberId)) {
240
- return false;
241
- }
242
- if (!assignment.scheduleDate) return true;
243
- return new Date(assignment.scheduleDate) <= new Date();
244
- }).sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)).map((assignment, index) => /*#__PURE__*/_jsx(AssignmentCard, {
301
+ }), filteredAssignments.map((assignment, index) => /*#__PURE__*/_jsx(AssignmentCard, {
245
302
  index: index,
246
303
  lastAssignmentFetch: lastAssignmentFetch,
247
304
  assignment: assignment,
@@ -565,7 +565,18 @@ export default function CreateAssignmentDialog({
565
565
  sx: {
566
566
  width: "45%"
567
567
  },
568
- format: "DD MMM YYYY HH:mm"
568
+ format: "DD MMM YYYY HH:mm",
569
+ slotProps: {
570
+ popper: {
571
+ placement: "left-start",
572
+ modifiers: [{
573
+ name: "preventOverflow",
574
+ options: {
575
+ boundary: "viewport"
576
+ }
577
+ }]
578
+ }
579
+ }
569
580
  })
570
581
  }), /*#__PURE__*/_jsx(Typography, {
571
582
  variant: "h4",
@@ -585,7 +596,18 @@ export default function CreateAssignmentDialog({
585
596
  sx: {
586
597
  width: "45%"
587
598
  },
588
- format: "DD MMM YYYY HH:mm"
599
+ format: "DD MMM YYYY HH:mm",
600
+ slotProps: {
601
+ popper: {
602
+ placement: "left-start",
603
+ modifiers: [{
604
+ name: "preventOverflow",
605
+ options: {
606
+ boundary: "viewport"
607
+ }
608
+ }]
609
+ }
610
+ }
589
611
  })
590
612
  })]
591
613
  })]
@@ -0,0 +1,411 @@
1
+ import { useState, useEffect, useRef } from "react";
2
+ import { Card, CardContent, CardActions, Typography, Avatar, Box, Button, TextField, Chip, Grid, IconButton, Tooltip } from "@mui/material";
3
+ import { grey, green } from "@mui/material/colors";
4
+ import AutoFixHighIcon from "@mui/icons-material/AutoFixHigh";
5
+ import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
6
+ import CheckCircleIcon from "@mui/icons-material/CheckCircle";
7
+ import { Formik } from "formik";
8
+ import * as Yup from "yup";
9
+ import RecordingDialog from "../../Dialogs/RecordingDialog/RecordingDialog";
10
+ import PDFViewer from "../../Dialogs/PDFViewer/PDFViewer";
11
+ import level1Rubric from '../../Dialogs/PDFViewer/rubrics/level1Rubric.pdf';
12
+ import level2Rubric from '../../Dialogs/PDFViewer/rubrics/level2Rubric.pdf';
13
+ import level3Rubric from '../../Dialogs/PDFViewer/rubrics/level3Rubric.pdf';
14
+ import level4Rubric from '../../Dialogs/PDFViewer/rubrics/level4Rubric.pdf';
15
+ import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
16
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
17
+ export default function FeedbackCard({
18
+ t = x => x,
19
+ discussionData = {},
20
+ userData = [],
21
+ hasSubmission = false,
22
+ aiGrade = {},
23
+ onGenerateFeedback = () => {},
24
+ onSubmitFeedback = () => {},
25
+ setSelectedRecording = () => {},
26
+ s3Url = "",
27
+ conversation = {},
28
+ existingFeedback = {}
29
+ }) {
30
+ const [isRecordingDialogOpen, setIsRecordingDialogOpen] = useState(false);
31
+ const [hasBeenEdited, setHasBeenEdited] = useState(false);
32
+ const [isSubmitted, setIsSubmitted] = useState(false);
33
+ const [aiJustGenerated, setAiJustGenerated] = useState(false);
34
+ const formikRef = useRef(null);
35
+ const initialValuesRef = useRef(null);
36
+ const [openPDF, setOpenPDF] = useState(false);
37
+ const [pageNumber, setPageNumber] = useState(1);
38
+ const [scale, setScale] = useState(1);
39
+ const rubricMap = {
40
+ 1: level1Rubric,
41
+ 2: level2Rubric,
42
+ 3: level3Rubric,
43
+ 4: level4Rubric
44
+ };
45
+ const pdfUrl = rubricMap[discussionData?.level] || level1Rubric;
46
+ const selectedMembers = userData.filter(u => [discussionData?.meetingMembers || discussionData?.userIds].flat().includes(u?.memberId));
47
+ const joinedMembers = discussionData?.joinedMembers || [];
48
+ let attendedMembers = [];
49
+ let notAttendedMembers = [];
50
+ if (hasSubmission) {
51
+ attendedMembers = selectedMembers.filter(m => joinedMembers.includes(m?.memberId));
52
+ notAttendedMembers = selectedMembers.filter(m => !joinedMembers.includes(m?.memberId));
53
+ }
54
+ const hasFeedbackSubmitted = hasSubmission && attendedMembers.every(member => {
55
+ const feedback = existingFeedback[member.memberId];
56
+ return feedback && feedback.feedbackText && feedback.feedbackText.trim() !== "";
57
+ });
58
+ const getInitialValues = () => {
59
+ const feedbackData = {};
60
+ if (hasSubmission) {
61
+ attendedMembers.forEach(student => {
62
+ const existingStudentFeedback = existingFeedback[student.memberId];
63
+ feedbackData[student.memberId] = {
64
+ feedbackText: existingStudentFeedback?.feedbackText || ""
65
+ };
66
+ });
67
+ notAttendedMembers.forEach(student => {
68
+ feedbackData[student.memberId] = {
69
+ feedbackText: existingFeedback[student.memberId]?.feedbackText || t("not_present")
70
+ };
71
+ });
72
+ } else {
73
+ selectedMembers.forEach(student => {
74
+ feedbackData[student.memberId] = {
75
+ feedbackText: existingFeedback[student.memberId]?.feedbackText || ""
76
+ };
77
+ });
78
+ }
79
+ return {
80
+ feedbackData
81
+ };
82
+ };
83
+ const attendeesInThisDiscussion = userData.filter(user => [discussionData?.meetingMembers || discussionData?.userIds].flat().includes(user?.memberId));
84
+ const handleOpenRecordingDialog = () => {
85
+ if (hasSubmission) {
86
+ setSelectedRecording({
87
+ meetingId: discussionData?.meetingId,
88
+ extMeetingId: discussionData?.extMeetingId,
89
+ creator: discussionData?.creator || discussionData?.userId,
90
+ meetingCreator: discussionData?.meetingCreator
91
+ });
92
+ setIsRecordingDialogOpen(true);
93
+ }
94
+ };
95
+ useEffect(() => {
96
+ const initialVals = getInitialValues();
97
+ const previous = initialValuesRef.current;
98
+ const hasChanged = JSON.stringify(previous) !== JSON.stringify(initialVals);
99
+ if (hasChanged) {
100
+ initialValuesRef.current = initialVals;
101
+ setIsSubmitted(false);
102
+ }
103
+ }, [existingFeedback, hasSubmission, selectedMembers, attendedMembers, notAttendedMembers, t]);
104
+ useEffect(() => {
105
+ const formik = formikRef.current;
106
+ if (!formik) return;
107
+ if (!hasSubmission || !aiGrade || Object.keys(aiGrade).length === 0) return;
108
+ if (!aiJustGenerated) return;
109
+ userData.forEach(student => {
110
+ const keyVariants = [student.username, `${student.username}Student`, student.username?.replace(/\s+/g, "")];
111
+ const aiData = keyVariants.map(k => aiGrade?.[k]).find(v => v && typeof v === "object") || null;
112
+ if (aiData?.Feedback) {
113
+ formik.setFieldValue(`feedbackData.${student.memberId}.feedbackText`, aiData.Feedback);
114
+ }
115
+ });
116
+ setHasBeenEdited(true);
117
+ setIsSubmitted(false);
118
+ setAiJustGenerated(false);
119
+ }, [aiGrade, hasSubmission, userData, aiJustGenerated]);
120
+ const handleGenerateAI = async () => {
121
+ setAiJustGenerated(true);
122
+ await onGenerateFeedback(discussionData);
123
+ };
124
+ const handleOpenPDF = () => {
125
+ setOpenPDF(true);
126
+ };
127
+ const handleClosePDF = () => {
128
+ setOpenPDF(false);
129
+ };
130
+ return /*#__PURE__*/_jsxs(_Fragment, {
131
+ children: [/*#__PURE__*/_jsx(Formik, {
132
+ innerRef: formikRef,
133
+ initialValues: getInitialValues(),
134
+ validationSchema: getFeedbackValidationSchema(attendedMembers.map(m => m.memberId)),
135
+ onSubmit: async (values, {
136
+ setSubmitting
137
+ }) => {
138
+ setSubmitting(true);
139
+ try {
140
+ const allStudents = selectedMembers.map(m => m.memberId);
141
+ const feedbackDataWithAbsent = {
142
+ ...values.feedbackData
143
+ };
144
+ notAttendedMembers.forEach(student => {
145
+ feedbackDataWithAbsent[student.memberId] = {
146
+ feedbackText: t("not_present")
147
+ };
148
+ });
149
+ await onSubmitFeedback({
150
+ feedbackData: feedbackDataWithAbsent,
151
+ selectedAttendee: allStudents,
152
+ discussionData
153
+ });
154
+ initialValuesRef.current = JSON.parse(JSON.stringify(values));
155
+ setHasBeenEdited(false);
156
+ setIsSubmitted(true);
157
+ } catch (error) {
158
+ console.error("Error submitting feedback:", error);
159
+ } finally {
160
+ setSubmitting(false);
161
+ }
162
+ },
163
+ enableReinitialize: false,
164
+ children: ({
165
+ values,
166
+ handleSubmit,
167
+ setFieldValue,
168
+ isSubmitting
169
+ }) => {
170
+ const isFormValid = () => {
171
+ if (!hasSubmission) return false;
172
+ return attendedMembers.every(member => {
173
+ const feedback = values.feedbackData[member.memberId];
174
+ return feedback?.feedbackText && feedback.feedbackText.trim() !== "";
175
+ });
176
+ };
177
+ const hasChanges = () => {
178
+ if (!initialValuesRef.current) return false;
179
+ return attendedMembers.some(member => {
180
+ const current = values.feedbackData[member.memberId]?.feedbackText || "";
181
+ const initial = initialValuesRef.current.feedbackData[member.memberId]?.feedbackText || "";
182
+ return current !== initial;
183
+ });
184
+ };
185
+ const canSubmit = hasSubmission && !isSubmitting && isFormValid() && (hasChanges() || hasBeenEdited) && !isSubmitted;
186
+ return /*#__PURE__*/_jsx("form", {
187
+ onSubmit: handleSubmit,
188
+ children: /*#__PURE__*/_jsxs(Card, {
189
+ sx: {
190
+ mb: 3,
191
+ borderRadius: 3,
192
+ boxShadow: 3,
193
+ overflow: "hidden"
194
+ },
195
+ children: [/*#__PURE__*/_jsx(Box, {
196
+ sx: {
197
+ px: 3,
198
+ pt: 2,
199
+ pb: 1,
200
+ fontWeight: 600,
201
+ color: "text.secondary"
202
+ },
203
+ children: /*#__PURE__*/_jsxs(Grid, {
204
+ container: true,
205
+ alignItems: "center",
206
+ spacing: 3,
207
+ children: [/*#__PURE__*/_jsx(Grid, {
208
+ size: {
209
+ xs: 12,
210
+ md: 2.5
211
+ },
212
+ children: /*#__PURE__*/_jsx(Typography, {
213
+ variant: "subtitle2",
214
+ children: t("student")
215
+ })
216
+ }), /*#__PURE__*/_jsx(Grid, {
217
+ size: {
218
+ xs: 12,
219
+ md: 9.5
220
+ },
221
+ children: /*#__PURE__*/_jsxs(Box, {
222
+ sx: {
223
+ display: "flex",
224
+ alignItems: "center"
225
+ },
226
+ children: [/*#__PURE__*/_jsx(Typography, {
227
+ variant: "subtitle2",
228
+ children: t("feedback")
229
+ }), /*#__PURE__*/_jsx(Tooltip, {
230
+ title: t("rubric_info_desc"),
231
+ children: /*#__PURE__*/_jsx(IconButton, {
232
+ onClick: handleOpenPDF,
233
+ color: "primary",
234
+ "aria-label": t("rubric_info"),
235
+ size: "small",
236
+ children: /*#__PURE__*/_jsx(HelpOutlineIcon, {})
237
+ })
238
+ })]
239
+ })
240
+ })]
241
+ })
242
+ }), /*#__PURE__*/_jsx(CardContent, {
243
+ sx: {
244
+ px: 3,
245
+ pt: 1
246
+ },
247
+ children: [...selectedMembers].sort((a, b) => {
248
+ const aJoined = joinedMembers.includes(a.memberId);
249
+ const bJoined = joinedMembers.includes(b.memberId);
250
+ return aJoined === bJoined ? 0 : aJoined ? -1 : 1;
251
+ }).map(student => {
252
+ const isAbsent = hasSubmission && !joinedMembers.includes(student.memberId);
253
+ const disabled = !hasSubmission || isAbsent;
254
+ const studentFeedback = values.feedbackData[student.memberId] || {};
255
+ return /*#__PURE__*/_jsx(Box, {
256
+ sx: {
257
+ py: 2
258
+ },
259
+ children: /*#__PURE__*/_jsxs(Grid, {
260
+ container: true,
261
+ alignItems: "flex-start",
262
+ spacing: 3,
263
+ children: [/*#__PURE__*/_jsx(Grid, {
264
+ size: {
265
+ xs: 12,
266
+ md: 2.5
267
+ },
268
+ children: /*#__PURE__*/_jsxs(Box, {
269
+ display: "flex",
270
+ alignItems: "center",
271
+ gap: 2,
272
+ children: [/*#__PURE__*/_jsx(Avatar, {
273
+ alt: student.username,
274
+ src: student.userImage,
275
+ sx: {
276
+ width: 48,
277
+ height: 48,
278
+ filter: isAbsent ? "grayscale(100%)" : "none"
279
+ }
280
+ }), /*#__PURE__*/_jsx(Typography, {
281
+ fontWeight: 600,
282
+ sx: {
283
+ color: isAbsent ? grey[500] : "text.primary"
284
+ },
285
+ children: student.username
286
+ })]
287
+ })
288
+ }), /*#__PURE__*/_jsx(Grid, {
289
+ size: {
290
+ xs: 12,
291
+ md: 9.5
292
+ },
293
+ children: /*#__PURE__*/_jsx(TextField, {
294
+ variant: "outlined",
295
+ fullWidth: true,
296
+ multiline: true,
297
+ disabled: disabled,
298
+ value: studentFeedback.feedbackText || "",
299
+ onChange: e => {
300
+ setFieldValue(`feedbackData.${student.memberId}.feedbackText`, e.target.value);
301
+ setHasBeenEdited(true);
302
+ setIsSubmitted(false);
303
+ },
304
+ placeholder: t("Write your feedback here")
305
+ })
306
+ })]
307
+ })
308
+ }, student.memberId);
309
+ })
310
+ }), /*#__PURE__*/_jsxs(CardActions, {
311
+ sx: {
312
+ px: 3,
313
+ py: 2,
314
+ justifyContent: "space-between",
315
+ alignItems: "center",
316
+ flexWrap: "wrap",
317
+ gap: 1.5
318
+ },
319
+ children: [/*#__PURE__*/_jsxs(Box, {
320
+ sx: {
321
+ display: "flex",
322
+ gap: 1.5
323
+ },
324
+ children: [!hasSubmission && /*#__PURE__*/_jsx(Chip, {
325
+ label: t("group_not_submitted"),
326
+ color: "warning",
327
+ variant: "filled",
328
+ icon: /*#__PURE__*/_jsx(ErrorOutlineIcon, {}),
329
+ sx: {
330
+ fontWeight: 500,
331
+ borderRadius: "20px"
332
+ }
333
+ }), hasFeedbackSubmitted && /*#__PURE__*/_jsx(Chip, {
334
+ label: t("feedback_submitted"),
335
+ color: "success",
336
+ variant: "filled",
337
+ icon: /*#__PURE__*/_jsx(CheckCircleIcon, {}),
338
+ sx: {
339
+ fontWeight: 500,
340
+ borderRadius: "20px",
341
+ bgcolor: green[600]
342
+ }
343
+ })]
344
+ }), /*#__PURE__*/_jsxs(Box, {
345
+ sx: {
346
+ display: "flex",
347
+ gap: 1.5,
348
+ ml: "auto"
349
+ },
350
+ children: [/*#__PURE__*/_jsx(Button, {
351
+ variant: "outlined",
352
+ disabled: !hasSubmission,
353
+ onClick: handleOpenRecordingDialog,
354
+ children: t("view_recording")
355
+ }), /*#__PURE__*/_jsx(Button, {
356
+ variant: "contained",
357
+ color: "secondary",
358
+ disabled: !hasSubmission,
359
+ onClick: handleGenerateAI,
360
+ endIcon: /*#__PURE__*/_jsx(AutoFixHighIcon, {}),
361
+ children: t("generate_feedback")
362
+ }), /*#__PURE__*/_jsx(Button, {
363
+ type: "submit",
364
+ variant: "contained",
365
+ disabled: !canSubmit,
366
+ children: isSubmitting ? t("submitting") : t("submit_feedback")
367
+ })]
368
+ })]
369
+ })]
370
+ })
371
+ });
372
+ }
373
+ }), /*#__PURE__*/_jsx(RecordingDialog, {
374
+ t: t,
375
+ open: isRecordingDialogOpen,
376
+ handleClose: () => setIsRecordingDialogOpen(false),
377
+ meetingID: discussionData?.meetingID || discussionData?.meetingId,
378
+ meetingPrompt: discussionData?.meetingPrompt,
379
+ meetingTopic: discussionData?.meetingTopic,
380
+ isTeacher: true,
381
+ attendeesData: attendeesInThisDiscussion,
382
+ conversation: conversation,
383
+ s3Url: s3Url,
384
+ playerRef: {
385
+ current: null
386
+ },
387
+ aiGrade: aiGrade,
388
+ hasBadLanguage: discussionData?.hasBadLanguage || false
389
+ }), /*#__PURE__*/_jsx(PDFViewer, {
390
+ open: openPDF,
391
+ handleClose: handleClosePDF,
392
+ pdfUrl: pdfUrl,
393
+ pageNumber: pageNumber,
394
+ setPageNumber: setPageNumber,
395
+ scale: scale,
396
+ setScale: setScale,
397
+ t: t
398
+ })]
399
+ });
400
+ }
401
+ const getFeedbackValidationSchema = attendedMemberIds => {
402
+ return Yup.object().shape({
403
+ feedbackData: Yup.object().test("all-attended-filled", "All attended students must have feedback", function (value) {
404
+ if (!attendedMemberIds || attendedMemberIds.length === 0) return true;
405
+ return attendedMemberIds.every(memberId => {
406
+ const feedback = value?.[memberId];
407
+ return feedback?.feedbackText && feedback.feedbackText.trim() !== "";
408
+ });
409
+ })
410
+ });
411
+ };