Seth0330 commited on
Commit
3327ef3
·
verified ·
1 Parent(s): 9f21447

Update frontend/src/components/admin/ClassStudentManager.jsx

Browse files
frontend/src/components/admin/ClassStudentManager.jsx CHANGED
@@ -16,14 +16,13 @@ const fetchEnrollments = async (classId) => {
16
  };
17
 
18
  /**
19
- * Fetch all memberships so we can pick students from them.
20
- * FastAPI route: /api/admin/memberships
21
- * → Frontend path: /admin/memberships
22
  */
23
- const fetchMemberships = async () => {
24
- const res = await client.get("/admin/memberships");
25
- const payload = res.data || { items: [] };
26
- return payload.items || [];
27
  };
28
 
29
  export default function ClassStudentManager({ classData, onClose }) {
@@ -36,21 +35,34 @@ export default function ClassStudentManager({ classData, onClose }) {
36
  enabled: !!classId,
37
  });
38
 
39
- const { data: memberships = [], isLoading: isLoadingMembers } = useQuery({
40
- queryKey: ["memberships"],
41
- queryFn: fetchMemberships,
42
  });
43
 
44
- const [selectedEmail, setSelectedEmail] = useState("");
45
 
46
- const availableMembers = useMemo(() => {
 
 
 
 
 
 
47
  const enrolledEmails = new Set(
48
  enrollments
49
  .filter((e) => e.status !== "removed")
50
  .map((e) => e.student_email)
51
  );
52
- return memberships.filter((m) => !enrolledEmails.has(m.user_email));
53
- }, [memberships, enrollments]);
 
 
 
 
 
 
 
54
 
55
  const enrollMutation = useMutation({
56
  // POST /api/admin/classes/{class_id}/enroll
@@ -74,12 +86,15 @@ export default function ClassStudentManager({ classData, onClose }) {
74
  });
75
 
76
  const handleAddStudent = () => {
77
- if (!selectedEmail) return;
 
 
 
78
 
79
- const member = memberships.find((m) => m.user_email === selectedEmail);
80
  enrollMutation.mutate({
81
- student_email: selectedEmail,
82
- student_name: member?.user_name || "",
 
83
  });
84
  };
85
 
@@ -95,7 +110,7 @@ export default function ClassStudentManager({ classData, onClose }) {
95
  Manage Students – {classData?.name}
96
  </h2>
97
  <p className="text-sm text-stone-500">
98
- Add students to this class from your membership list
99
  </p>
100
  </div>
101
  </div>
@@ -115,21 +130,24 @@ export default function ClassStudentManager({ classData, onClose }) {
115
  <div className="flex flex-wrap gap-3 items-center">
116
  <select
117
  className="flex-1 min-w-[220px] rounded-lg border border-stone-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-red-500 focus:border-red-500"
118
- value={selectedEmail}
119
- onChange={(e) => setSelectedEmail(e.target.value)}
120
- disabled={isLoadingMembers || enrollMutation.isPending}
121
  >
122
  <option value="">Select a student to add...</option>
123
- {availableMembers.map((m) => (
124
- <option key={m.id} value={m.user_email}>
125
- {m.user_name || m.user_email} ({m.user_email})
 
 
 
126
  </option>
127
  ))}
128
  </select>
129
  <button
130
  type="button"
131
  onClick={handleAddStudent}
132
- disabled={!selectedEmail || enrollMutation.isPending || isLoadingMembers}
133
  className="inline-flex items-center gap-2 rounded-lg bg-red-600 hover:bg-red-700 text-white text-sm font-medium px-4 py-2 disabled:opacity-50 disabled:cursor-not-allowed"
134
  >
135
  <span>+</span>
@@ -156,9 +174,9 @@ export default function ClassStudentManager({ classData, onClose }) {
156
  >
157
  <div>
158
  <div className="font-medium text-stone-900 text-sm">
159
- {enrollment.student_name || enrollment.student_email}
160
  </div>
161
- {enrollment.student_name && (
162
  <div className="text-xs text-stone-500">
163
  {enrollment.student_email}
164
  </div>
 
16
  };
17
 
18
  /**
19
+ * Fetch all students so we can pick individual students.
20
+ * FastAPI route: /api/admin/students
21
+ * → Frontend path: /admin/students
22
  */
23
+ const fetchStudents = async () => {
24
+ const res = await client.get("/admin/students");
25
+ return Array.isArray(res.data) ? res.data : [];
 
26
  };
27
 
28
  export default function ClassStudentManager({ classData, onClose }) {
 
35
  enabled: !!classId,
36
  });
37
 
38
+ const { data: students = [], isLoading: isLoadingStudents } = useQuery({
39
+ queryKey: ["students"],
40
+ queryFn: fetchStudents,
41
  });
42
 
43
+ const [selectedStudentId, setSelectedStudentId] = useState("");
44
 
45
+ const availableStudents = useMemo(() => {
46
+ const enrolledStudentIds = new Set(
47
+ enrollments
48
+ .filter((e) => e.status !== "removed")
49
+ .map((e) => e.student_id)
50
+ .filter(Boolean) // Remove null/undefined
51
+ );
52
  const enrolledEmails = new Set(
53
  enrollments
54
  .filter((e) => e.status !== "removed")
55
  .map((e) => e.student_email)
56
  );
57
+ // Filter out students that are already enrolled (by student_id or by email matching student name)
58
+ return students.filter((s) => {
59
+ // Check if student ID is enrolled
60
+ if (enrolledStudentIds.has(s.id)) return false;
61
+ // Check if student name matches any enrolled email (for backward compatibility)
62
+ // This is a fallback for old enrollments that might not have student_id
63
+ return !enrolledEmails.has(s.name);
64
+ });
65
+ }, [students, enrollments]);
66
 
67
  const enrollMutation = useMutation({
68
  // POST /api/admin/classes/{class_id}/enroll
 
86
  });
87
 
88
  const handleAddStudent = () => {
89
+ if (!selectedStudentId) return;
90
+
91
+ const student = students.find((s) => s.id === parseInt(selectedStudentId, 10));
92
+ if (!student) return;
93
 
 
94
  enrollMutation.mutate({
95
+ student_id: student.id,
96
+ student_email: student.membership_email || "",
97
+ student_name: student.name,
98
  });
99
  };
100
 
 
110
  Manage Students – {classData?.name}
111
  </h2>
112
  <p className="text-sm text-stone-500">
113
+ Add individual students to this class (students can be from the same or different memberships)
114
  </p>
115
  </div>
116
  </div>
 
130
  <div className="flex flex-wrap gap-3 items-center">
131
  <select
132
  className="flex-1 min-w-[220px] rounded-lg border border-stone-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-red-500 focus:border-red-500"
133
+ value={selectedStudentId}
134
+ onChange={(e) => setSelectedStudentId(e.target.value)}
135
+ disabled={isLoadingStudents || enrollMutation.isPending}
136
  >
137
  <option value="">Select a student to add...</option>
138
+ {availableStudents.map((student) => (
139
+ <option key={student.id} value={student.id}>
140
+ {student.name}
141
+ {student.belt_level ? ` (${student.belt_level})` : ""}
142
+ {student.membership_plan ? ` - ${student.membership_plan}` : ""}
143
+ {student.membership_email ? ` [${student.membership_email}]` : ""}
144
  </option>
145
  ))}
146
  </select>
147
  <button
148
  type="button"
149
  onClick={handleAddStudent}
150
+ disabled={!selectedStudentId || enrollMutation.isPending || isLoadingStudents}
151
  className="inline-flex items-center gap-2 rounded-lg bg-red-600 hover:bg-red-700 text-white text-sm font-medium px-4 py-2 disabled:opacity-50 disabled:cursor-not-allowed"
152
  >
153
  <span>+</span>
 
174
  >
175
  <div>
176
  <div className="font-medium text-stone-900 text-sm">
177
+ {enrollment.student_name || enrollment.student_email || "Unknown Student"}
178
  </div>
179
+ {enrollment.student_email && enrollment.student_name && (
180
  <div className="text-xs text-stone-500">
181
  {enrollment.student_email}
182
  </div>