@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.
- package/dist/Assignments/AssignmentCard/AssignmentCard.js +46 -6
- package/dist/Assignments/AssignmentCardsList/AssignmentCardsList.js +72 -15
- package/dist/Assignments/CreateAssignmentDialog/CreateAssignmentDialog.js +24 -2
- package/dist/Cards/FeedbackCard/FeedbackCard.js +411 -0
- package/dist/Cards/RecordingCard/RecordingCard.js +161 -120
- package/dist/Cards/ScheduleCard/ScheduleCard.js +99 -44
- package/dist/Dialogs/GenerateDiscussion/GenerateDiscussion.js +171 -0
- package/dist/Dialogs/GroupedFeedbackDialog/GroupedFeedbackDialog.js +273 -0
- package/dist/Dialogs/PDFViewer/rubrics/level1Rubric.pdf +0 -0
- package/dist/Dialogs/PDFViewer/rubrics/level2Rubric.pdf +0 -0
- package/dist/Dialogs/PDFViewer/rubrics/level3Rubric.pdf +0 -0
- package/dist/Dialogs/PDFViewer/rubrics/level4Rubric.pdf +0 -0
- package/dist/Dialogs/RecordingDialog/RecordingDialog.js +61 -43
- package/dist/Forms/CreateMeetingMultiStepForm/CreateMeetingMultiStepForm.js +15 -3
- package/dist/Forms/CreateMeetingMultiStepForm/MeetingForm.js +144 -113
- package/dist/Screens/Classrooms/ViewClassroom/ViewClassroom.js +24 -14
- package/dist/Tables/MeetingPrompstList/MeetingPromptsList.js +78 -72
- package/dist/Tables/Progress/ProgressTable.js +22 -14
- package/dist/Tables/ScheduleListCards/ScheduleListCards.js +3 -97
- package/dist/img/NualaHeadphonesBackground.svg +39 -0
- package/dist/img/aaronAvatar.png +0 -0
- package/dist/img/connorAvatar.png +0 -0
- package/dist/img/jamieAvatar.png +0 -0
- package/dist/img/stephenAvatar.png +0 -0
- 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,
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
256
|
+
mb: 3,
|
|
257
|
+
mt: "8px"
|
|
223
258
|
},
|
|
224
|
-
children: [/*#__PURE__*/
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
293
|
+
sx: {
|
|
294
|
+
size: "large"
|
|
295
|
+
},
|
|
296
|
+
children: /*#__PURE__*/_jsx(Refresh, {
|
|
297
|
+
fontSize: "inherit"
|
|
298
|
+
})
|
|
235
299
|
})
|
|
236
300
|
})]
|
|
237
|
-
}),
|
|
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
|
+
};
|