Seth0330 commited on
Commit
11361a8
·
verified ·
1 Parent(s): c8ade5a

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

Browse files
frontend/src/components/admin/ClassForm.jsx CHANGED
@@ -1,183 +1,226 @@
1
  // frontend/src/components/admin/ClassForm.jsx
2
  import React, { useState } from "react";
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  const DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
5
 
6
- export default function ClassForm({
7
- classData,
8
- onSave,
9
- onCancel,
10
- isLoading,
11
- }) {
12
- const [name, setName] = useState(classData?.name || "");
13
- const [description, setDescription] = useState(classData?.description || "");
14
- const [classTime, setClassTime] = useState(classData?.class_time || "");
15
- const [daysOfWeek, setDaysOfWeek] = useState(
16
- classData?.days_of_week && classData.days_of_week.length
17
- ? classData.days_of_week
18
- : ["Monday"]
19
- );
20
- const [classesPerWeek, setClassesPerWeek] = useState(
21
- classData?.classes_per_week ?? 1
22
- );
23
- const [maxStudents, setMaxStudents] = useState(
24
- classData?.max_students ?? 20
25
- );
26
- const [error, setError] = useState("");
27
 
28
- function toggleDay(day) {
29
- setDaysOfWeek((prev) =>
30
- prev.includes(day) ? prev.filter((d) => d !== day) : [...prev, day]
31
- );
32
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
- function handleSubmit(e) {
 
 
 
 
 
 
 
 
35
  e.preventDefault();
36
- setError("");
37
 
38
- if (!name.trim()) {
39
- setError("Please enter a class name.");
40
- return;
41
- }
42
- if (!classTime) {
43
- setError("Please choose a class time.");
44
- return;
45
- }
46
- if (!daysOfWeek.length) {
47
- setError("Please select at least one day.");
48
  return;
49
  }
50
 
51
  const payload = {
52
- name: name.trim(),
53
- description: description.trim() || null,
54
- class_time: classTime, // HTML time input -> "HH:MM"
55
- days_of_week: daysOfWeek,
56
- classes_per_week: Number(classesPerWeek) || 1,
57
- max_students: Number(maxStudents) || 1,
58
- // no membership/plan fields anymore
 
59
  };
60
 
61
- if (onSave) onSave(payload);
62
- }
63
 
64
  return (
65
- <form onSubmit={handleSubmit} className="space-y-5">
66
- {/* Class name + time */}
67
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
68
- <div>
69
- <label className="block text-sm font-medium text-stone-700 mb-1">
70
- Class Name
71
- </label>
72
- <input
73
- type="text"
74
- className="w-full rounded-lg border border-stone-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-red-500/70 focus:border-red-500"
75
- placeholder="e.g., Beginner Karate"
76
- value={name}
77
- onChange={(e) => setName(e.target.value)}
78
- />
79
- </div>
80
- <div>
81
- <label className="block text-sm font-medium text-stone-700 mb-1">
82
- Class Time
83
- </label>
84
- <input
85
- type="time"
86
- className="w-full rounded-lg border border-stone-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-red-500/70 focus:border-red-500"
87
- value={classTime}
88
- onChange={(e) => setClassTime(e.target.value)}
89
- />
90
- </div>
91
  </div>
92
 
93
  {/* Description */}
94
  <div>
95
- <label className="block text-sm font-medium text-stone-700 mb-1">
96
- Description
97
- </label>
98
- <textarea
99
- className="w-full rounded-lg border border-stone-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-red-500/70 focus:border-red-500 min-h-[80px]"
 
100
  placeholder="Class description..."
101
- value={description}
102
- onChange={(e) => setDescription(e.target.value)}
103
  />
104
  </div>
105
 
106
- {/* Days of week */}
107
  <div>
108
- <label className="block text-sm font-medium text-stone-700 mb-2">
109
- Days of Week
110
- </label>
111
- <div className="flex flex-wrap gap-2">
112
- {DAYS.map((day) => {
113
- const active = daysOfWeek.includes(day);
114
- return (
115
- <button
116
- key={day}
117
- type="button"
118
- onClick={() => toggleDay(day)}
119
- className={`px-3 py-1.5 rounded-full text-xs font-medium border transition ${
120
- active
121
- ? "bg-red-600 text-white border-red-600"
122
- : "bg-white text-stone-700 border-stone-300 hover:bg-stone-50"
123
- }`}
124
- >
125
- {day}
126
- </button>
127
- );
128
- })}
129
  </div>
130
- </div>
131
 
132
- {/* Numbers (no plan anymore) */}
133
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
134
- <div>
135
- <label className="block text-sm font-medium text-stone-700 mb-1">
136
- Classes Per Week
137
- </label>
138
- <input
139
- type="number"
140
- min={1}
141
- className="w-full rounded-lg border border-stone-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-red-500/70 focus:border-red-500"
142
- value={classesPerWeek}
143
- onChange={(e) => setClassesPerWeek(e.target.value)}
144
- />
145
- </div>
146
- <div>
147
- <label className="block text-sm font-medium text-stone-700 mb-1">
148
- Max Students
149
- </label>
150
- <input
151
- type="number"
152
- min={1}
153
- className="w-full rounded-lg border border-stone-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-red-500/70 focus:border-red-500"
154
- value={maxStudents}
155
- onChange={(e) => setMaxStudents(e.target.value)}
156
- />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  </div>
158
  </div>
159
 
160
- {/* Error + buttons */}
161
- {error && (
162
- <p className="text-sm text-red-600 bg-red-50 border border-red-100 rounded-md px-3 py-2">
163
- {error}
164
- </p>
165
- )}
166
- <div className="flex justify-start gap-3 pt-1">
167
- <button
168
  type="submit"
169
  disabled={isLoading}
170
- className="inline-flex items-center justify-center px-4 py-2 rounded-lg bg-red-600 text-white text-sm font-medium hover:bg-red-700 disabled:opacity-60"
171
  >
172
- {classData ? "Save changes" : "Create Class"}
173
- </button>
174
- <button
 
 
 
 
 
175
  type="button"
 
176
  onClick={onCancel}
177
- className="inline-flex items-center justify-center px-4 py-2 rounded-lg border border-stone-300 text-sm font-medium text-stone-700 hover:bg-stone-50"
178
  >
 
179
  Cancel
180
- </button>
181
  </div>
182
  </form>
183
  );
 
1
  // frontend/src/components/admin/ClassForm.jsx
2
  import React, { useState } from "react";
3
+ import { Button } from "@/components/ui/button";
4
+ import { Input } from "@/components/ui/input";
5
+ import { Textarea } from "@/components/ui/textarea";
6
+ import { Label } from "@/components/ui/label";
7
+ import { Card } from "@/components/ui/card";
8
+ import {
9
+ Select,
10
+ SelectContent,
11
+ SelectItem,
12
+ SelectTrigger,
13
+ SelectValue
14
+ } from "@/components/ui/select";
15
+ import { Loader2, Save, X, Plus, Trash2 } from "lucide-react";
16
 
17
  const DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
18
 
19
+ export default function ClassForm({ classData, onSave, onCancel, isLoading }) {
20
+ // Normalize incoming schedule so it works with:
21
+ // - old shape: { day, time }
22
+ // - new shape: { day, from_time, to_time }
23
+ const initialSchedule =
24
+ classData?.schedule && classData.schedule.length > 0
25
+ ? classData.schedule.map((slot) => ({
26
+ day: slot.day || "Monday",
27
+ from_time:
28
+ slot.from_time ??
29
+ slot.fromTime ??
30
+ (slot.time ? slot.time.split("-")[0]?.trim() : "") ??
31
+ "",
32
+ to_time:
33
+ slot.to_time ??
34
+ slot.toTime ??
35
+ (slot.time ? slot.time.split("-")[1]?.trim() : "") ??
36
+ ""
37
+ }))
38
+ : [{ day: "Monday", from_time: "", to_time: "" }];
 
39
 
40
+ const [formData, setFormData] = useState({
41
+ name: classData?.name || "",
42
+ description: classData?.description || "",
43
+ schedule: initialSchedule,
44
+ is_active: classData?.is_active !== undefined ? classData.is_active : true
45
+ });
46
+
47
+ const addScheduleSlot = () => {
48
+ setFormData((prev) => ({
49
+ ...prev,
50
+ schedule: [...prev.schedule, { day: "Monday", from_time: "", to_time: "" }]
51
+ }));
52
+ };
53
+
54
+ const removeScheduleSlot = (index) => {
55
+ setFormData((prev) => ({
56
+ ...prev,
57
+ schedule: prev.schedule.filter((_, i) => i !== index)
58
+ }));
59
+ };
60
 
61
+ const updateScheduleSlot = (index, field, value) => {
62
+ setFormData((prev) => {
63
+ const schedule = [...prev.schedule];
64
+ schedule[index] = { ...schedule[index], [field]: value };
65
+ return { ...prev, schedule };
66
+ });
67
+ };
68
+
69
+ const handleSubmit = (e) => {
70
  e.preventDefault();
 
71
 
72
+ const invalid = formData.schedule.some(
73
+ (s) => !s.day || !s.from_time || !s.to_time
74
+ );
75
+ if (invalid) {
76
+ alert("Please fill in both From and To time for each schedule slot.");
 
 
 
 
 
77
  return;
78
  }
79
 
80
  const payload = {
81
+ name: formData.name,
82
+ description: formData.description,
83
+ is_active: formData.is_active,
84
+ schedule: formData.schedule.map((slot) => ({
85
+ day: slot.day,
86
+ from_time: slot.from_time,
87
+ to_time: slot.to_time
88
+ }))
89
  };
90
 
91
+ onSave(payload);
92
+ };
93
 
94
  return (
95
+ <form onSubmit={handleSubmit} className="space-y-6">
96
+ {/* Class name */}
97
+ <div>
98
+ <Label>Class Name</Label>
99
+ <Input
100
+ value={formData.name}
101
+ onChange={(e) => setFormData({ ...formData, name: e.target.value })}
102
+ placeholder="e.g., Beginner Karate"
103
+ required
104
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  </div>
106
 
107
  {/* Description */}
108
  <div>
109
+ <Label>Description</Label>
110
+ <Textarea
111
+ value={formData.description}
112
+ onChange={(e) =>
113
+ setFormData({ ...formData, description: e.target.value })
114
+ }
115
  placeholder="Class description..."
116
+ rows={2}
 
117
  />
118
  </div>
119
 
120
+ {/* Schedule */}
121
  <div>
122
+ <div className="flex justify-between items-center mb-3">
123
+ <Label className="text-base">Class Schedule</Label>
124
+ <Button
125
+ type="button"
126
+ variant="outline"
127
+ size="sm"
128
+ onClick={addScheduleSlot}
129
+ className="gap-2"
130
+ >
131
+ <Plus className="w-4 h-4" />
132
+ Add Time Slot
133
+ </Button>
 
 
 
 
 
 
 
 
 
134
  </div>
 
135
 
136
+ <div className="space-y-3">
137
+ {formData.schedule.map((slot, index) => (
138
+ <Card key={index} className="p-3 bg-stone-50">
139
+ <div className="flex flex-col md:flex-row md:items-center gap-3">
140
+ {/* Day */}
141
+ <Select
142
+ value={slot.day}
143
+ onValueChange={(value) =>
144
+ updateScheduleSlot(index, "day", value)
145
+ }
146
+ >
147
+ <SelectTrigger className="w-full md:w-40">
148
+ <SelectValue placeholder="Day" />
149
+ </SelectTrigger>
150
+ <SelectContent>
151
+ {DAYS.map((day) => (
152
+ <SelectItem key={day} value={day}>
153
+ {day}
154
+ </SelectItem>
155
+ ))}
156
+ </SelectContent>
157
+ </Select>
158
+
159
+ {/* From time */}
160
+ <div className="flex-1">
161
+ <Label className="text-xs text-stone-500">From</Label>
162
+ <Input
163
+ type="time"
164
+ value={slot.from_time}
165
+ onChange={(e) =>
166
+ updateScheduleSlot(index, "from_time", e.target.value)
167
+ }
168
+ />
169
+ </div>
170
+
171
+ {/* To time */}
172
+ <div className="flex-1">
173
+ <Label className="text-xs text-stone-500">To</Label>
174
+ <Input
175
+ type="time"
176
+ value={slot.to_time}
177
+ onChange={(e) =>
178
+ updateScheduleSlot(index, "to_time", e.target.value)
179
+ }
180
+ />
181
+ </div>
182
+
183
+ {/* Remove row */}
184
+ {formData.schedule.length > 1 && (
185
+ <Button
186
+ type="button"
187
+ variant="ghost"
188
+ size="icon"
189
+ onClick={() => removeScheduleSlot(index)}
190
+ className="text-red-600 self-end md:self-center"
191
+ >
192
+ <Trash2 className="w-4 h-4" />
193
+ </Button>
194
+ )}
195
+ </div>
196
+ </Card>
197
+ ))}
198
  </div>
199
  </div>
200
 
201
+ {/* Actions */}
202
+ <div className="flex gap-3 pt-2">
203
+ <Button
 
 
 
 
 
204
  type="submit"
205
  disabled={isLoading}
206
+ className="gap-2 bg-red-600 hover:bg-red-700"
207
  >
208
+ {isLoading ? (
209
+ <Loader2 className="w-4 h-4 animate-spin" />
210
+ ) : (
211
+ <Save className="w-4 h-4" />
212
+ )}
213
+ {classData ? "Update Class" : "Create Class"}
214
+ </Button>
215
+ <Button
216
  type="button"
217
+ variant="outline"
218
  onClick={onCancel}
219
+ disabled={isLoading}
220
  >
221
+ <X className="w-4 h-4 mr-2" />
222
  Cancel
223
+ </Button>
224
  </div>
225
  </form>
226
  );