Seth0330 commited on
Commit
959c902
·
verified ·
1 Parent(s): 48eb982

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

Browse files
frontend/src/components/admin/ClassStudentManager.jsx CHANGED
@@ -2,25 +2,6 @@
2
  import React from "react";
3
  import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
4
  import api from "../../api/client";
5
-
6
- import { Button } from "../ui/button";
7
- import {
8
- Card,
9
- CardHeader,
10
- CardTitle,
11
- CardDescription,
12
- CardContent,
13
- } from "../ui/card";
14
- import {
15
- Table,
16
- TableBody,
17
- TableCell,
18
- TableHead,
19
- TableHeader,
20
- TableRow,
21
- } from "../ui/table";
22
- import { Skeleton } from "../ui/skeleton";
23
- import { Badge } from "../ui/badge";
24
  import { X, UserPlus, Trash2 } from "lucide-react";
25
 
26
  function useMemberships() {
@@ -108,149 +89,167 @@ export default function ClassStudentManager({ classData, onClose }) {
108
  };
109
 
110
  return (
111
- <Card>
112
- <CardHeader className="flex flex-col md:flex-row md:items-center md:justify-between gap-3">
113
  <div>
114
- <CardTitle>Manage Students for {classData.name}</CardTitle>
115
- <CardDescription>
 
 
116
  Enroll existing members into this class. Students will see
117
  their classes when they log in.
118
- </CardDescription>
119
  </div>
120
- <Button variant="ghost" size="icon" onClick={onClose}>
 
 
 
 
121
  <X className="w-5 h-5" />
122
- </Button>
123
- </CardHeader>
124
- <CardContent>
125
- <div className="grid md:grid-cols-2 gap-6">
126
- {/* Left: Membership list */}
127
- <div>
128
- <h3 className="font-semibold mb-2">All Members</h3>
129
- <p className="text-xs text-stone-500 mb-3">
130
- Click &quot;Enroll&quot; to add a member into this class.
131
- </p>
132
 
133
- {membershipsLoading ? (
134
- <div className="space-y-2">
135
- {[1, 2, 3].map((i) => (
136
- <Skeleton key={i} className="h-10 w-full" />
137
- ))}
138
- </div>
139
- ) : memberships.length === 0 ? (
140
- <p className="text-sm text-stone-500">
141
- No memberships found.
142
- </p>
143
- ) : (
144
- <div className="border rounded-lg overflow-hidden">
145
- <Table>
146
- <TableHeader>
147
- <TableRow>
148
- <TableHead>Member</TableHead>
149
- <TableHead>Plan</TableHead>
150
- <TableHead className="text-right">
151
- Action
152
- </TableHead>
153
- </TableRow>
154
- </TableHeader>
155
- <TableBody>
156
- {memberships.map((m) => {
157
- const alreadyEnrolled =
158
- enrolledEmails.has(m.user_email);
159
- return (
160
- <TableRow key={m.id}>
161
- <TableCell>
162
- <div className="flex flex-col">
163
- <span className="font-medium">
164
- {m.user_name || "Unnamed"}
165
- </span>
166
- <span className="text-xs text-stone-500">
167
- {m.user_email}
168
- </span>
169
- </div>
170
- </TableCell>
171
- <TableCell>
172
- <span className="text-xs">
173
- {m.plan_name}
 
 
 
 
 
 
 
 
 
 
 
 
174
  </span>
175
- </TableCell>
176
- <TableCell className="text-right">
177
- <Button
178
- size="sm"
179
- variant={alreadyEnrolled ? "outline" : "default"}
180
- className="gap-1"
181
- disabled={
182
- alreadyEnrolled || enrollMutation.isPending
183
- }
184
- onClick={() => handleEnroll(m)}
185
- >
186
- <UserPlus className="w-4 h-4" />
187
- {alreadyEnrolled ? "Enrolled" : "Enroll"}
188
- </Button>
189
- </TableCell>
190
- </TableRow>
191
- );
192
- })}
193
- </TableBody>
194
- </Table>
195
- </div>
196
- )}
197
- </div>
 
 
 
 
 
 
 
 
 
198
 
199
- {/* Right: Current enrollments */}
200
- <div>
201
- <h3 className="font-semibold mb-2">
202
- Students in this class
203
- </h3>
204
- <p className="text-xs text-stone-500 mb-3">
205
- These students are currently associated with this class.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  </p>
207
-
208
- {enrollmentsLoading ? (
209
- <div className="space-y-2">
210
- {[1, 2, 3].map((i) => (
211
- <Skeleton key={i} className="h-10 w-full" />
212
- ))}
213
- </div>
214
- ) : activeEnrollments.length === 0 ? (
215
- <p className="text-sm text-stone-500">
216
- No students enrolled yet.
217
- </p>
218
- ) : (
219
- <div className="space-y-2">
220
- {activeEnrollments.map((enroll) => (
221
- <div
222
- key={enroll.id}
223
- className="flex items-center justify-between border rounded-lg px-3 py-2"
224
- >
225
- <div className="flex flex-col">
226
- <span className="font-medium">
227
- {enroll.student_name || "Student"}
228
- </span>
229
- <span className="text-xs text-stone-500">
230
- {enroll.student_email}
231
- </span>
232
- </div>
233
- <div className="flex items-center gap-2">
234
- <Badge variant="outline" className="text-xs">
235
- {enroll.status}
236
- </Badge>
237
- <Button
238
- size="icon"
239
- variant="ghost"
240
- className="text-red-600"
241
- onClick={() => handleRemove(enroll.id)}
242
- disabled={removeMutation.isPending}
243
- >
244
- <Trash2 className="w-4 h-4" />
245
- </Button>
246
- </div>
247
  </div>
248
- ))}
249
- </div>
250
- )}
251
- </div>
 
 
 
 
 
 
 
 
252
  </div>
253
- </CardContent>
254
- </Card>
255
  );
256
  }
 
2
  import React from "react";
3
  import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
4
  import api from "../../api/client";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  import { X, UserPlus, Trash2 } from "lucide-react";
6
 
7
  function useMemberships() {
 
89
  };
90
 
91
  return (
92
+ <div className="border rounded-xl bg-white shadow-sm">
93
+ <div className="px-4 py-3 border-b flex items-center justify-between">
94
  <div>
95
+ <h2 className="font-semibold text-stone-900">
96
+ Manage Students for {classData.name}
97
+ </h2>
98
+ <p className="text-xs text-stone-500">
99
  Enroll existing members into this class. Students will see
100
  their classes when they log in.
101
+ </p>
102
  </div>
103
+ <button
104
+ type="button"
105
+ onClick={onClose}
106
+ className="rounded-md p-2 text-stone-500 hover:bg-stone-100"
107
+ >
108
  <X className="w-5 h-5" />
109
+ </button>
110
+ </div>
 
 
 
 
 
 
 
 
111
 
112
+ <div className="p-4 grid md:grid-cols-2 gap-6">
113
+ {/* Left: Members */}
114
+ <div>
115
+ <h3 className="font-semibold mb-1 text-sm text-stone-900">
116
+ All Members
117
+ </h3>
118
+ <p className="text-xs text-stone-500 mb-3">
119
+ Click &quot;Enroll&quot; to add a member into this class.
120
+ </p>
121
+
122
+ {membershipsLoading ? (
123
+ <div className="space-y-2">
124
+ {[1, 2, 3].map((i) => (
125
+ <div
126
+ key={i}
127
+ className="h-10 w-full animate-pulse rounded-md bg-stone-100"
128
+ />
129
+ ))}
130
+ </div>
131
+ ) : memberships.length === 0 ? (
132
+ <p className="text-sm text-stone-500">No memberships found.</p>
133
+ ) : (
134
+ <div className="border rounded-lg overflow-hidden">
135
+ <table className="w-full text-xs">
136
+ <thead className="bg-stone-50">
137
+ <tr>
138
+ <th className="px-3 py-2 text-left font-semibold text-stone-800">
139
+ Member
140
+ </th>
141
+ <th className="px-3 py-2 text-left font-semibold text-stone-800">
142
+ Plan
143
+ </th>
144
+ <th className="px-3 py-2 text-right font-semibold text-stone-800">
145
+ Action
146
+ </th>
147
+ </tr>
148
+ </thead>
149
+ <tbody>
150
+ {memberships.map((m) => {
151
+ const alreadyEnrolled =
152
+ enrolledEmails.has(m.user_email);
153
+ return (
154
+ <tr
155
+ key={m.id}
156
+ className="border-t border-stone-100"
157
+ >
158
+ <td className="px-3 py-2 align-top">
159
+ <div className="flex flex-col">
160
+ <span className="font-medium text-stone-900">
161
+ {m.user_name || "Unnamed"}
162
+ </span>
163
+ <span className="text-[11px] text-stone-500">
164
+ {m.user_email}
165
  </span>
166
+ </div>
167
+ </td>
168
+ <td className="px-3 py-2 align-top">
169
+ <span className="text-[11px] text-stone-700">
170
+ {m.plan_name}
171
+ </span>
172
+ </td>
173
+ <td className="px-3 py-2 align-top text-right">
174
+ <button
175
+ type="button"
176
+ onClick={() => handleEnroll(m)}
177
+ disabled={
178
+ alreadyEnrolled || enrollMutation.isPending
179
+ }
180
+ className={`inline-flex items-center gap-1 rounded-md px-2 py-1 text-[11px] font-medium ${
181
+ alreadyEnrolled
182
+ ? "border border-stone-300 text-stone-500 bg-stone-50"
183
+ : "bg-stone-900 text-white hover:bg-stone-800"
184
+ } disabled:opacity-60`}
185
+ >
186
+ <UserPlus className="w-3 h-3" />
187
+ {alreadyEnrolled ? "Enrolled" : "Enroll"}
188
+ </button>
189
+ </td>
190
+ </tr>
191
+ );
192
+ })}
193
+ </tbody>
194
+ </table>
195
+ </div>
196
+ )}
197
+ </div>
198
 
199
+ {/* Right: Enrollments */}
200
+ <div>
201
+ <h3 className="font-semibold mb-1 text-sm text-stone-900">
202
+ Students in this class
203
+ </h3>
204
+ <p className="text-xs text-stone-500 mb-3">
205
+ These students are currently associated with this class.
206
+ </p>
207
+
208
+ {enrollmentsLoading ? (
209
+ <div className="space-y-2">
210
+ {[1, 2, 3].map((i) => (
211
+ <div
212
+ key={i}
213
+ className="h-10 w-full animate-pulse rounded-md bg-stone-100"
214
+ />
215
+ ))}
216
+ </div>
217
+ ) : activeEnrollments.length === 0 ? (
218
+ <p className="text-sm text-stone-500">
219
+ No students enrolled yet.
220
  </p>
221
+ ) : (
222
+ <div className="space-y-2">
223
+ {activeEnrollments.map((enroll) => (
224
+ <div
225
+ key={enroll.id}
226
+ className="flex items-center justify-between rounded-lg border border-stone-200 px-3 py-2"
227
+ >
228
+ <div className="flex flex-col">
229
+ <span className="text-sm font-medium text-stone-900">
230
+ {enroll.student_name || "Student"}
231
+ </span>
232
+ <span className="text-[11px] text-stone-500">
233
+ {enroll.student_email}
234
+ </span>
235
+ <span className="mt-0.5 inline-flex items-center rounded-full bg-stone-100 px-2 py-0.5 text-[10px] font-medium text-stone-700">
236
+ {enroll.status}
237
+ </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  </div>
239
+ <button
240
+ type="button"
241
+ onClick={() => handleRemove(enroll.id)}
242
+ disabled={removeMutation.isPending}
243
+ className="rounded-md p-2 text-red-600 hover:bg-red-50 disabled:opacity-60"
244
+ >
245
+ <Trash2 className="w-4 h-4" />
246
+ </button>
247
+ </div>
248
+ ))}
249
+ </div>
250
+ )}
251
  </div>
252
+ </div>
253
+ </div>
254
  );
255
  }