Seth0330 commited on
Commit
770297a
·
verified ·
1 Parent(s): 0dc70ba

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

Browse files
frontend/src/components/admin/ClassForm.jsx CHANGED
@@ -11,18 +11,23 @@ export default function ClassForm({
11
  isLoading,
12
  }) {
13
  const [name, setName] = useState(classData?.name || "");
14
- const [classTime, setClassTime] = useState(classData?.class_time || "");
15
  const [description, setDescription] = useState(classData?.description || "");
16
- const [daysOfWeek, setDaysOfWeek] = useState(classData?.days_of_week || []);
 
 
 
 
 
17
  const [classesPerWeek, setClassesPerWeek] = useState(
18
- classData?.classes_per_week || 2
19
  );
20
  const [maxStudents, setMaxStudents] = useState(
21
- classData?.max_students || 20
22
  );
23
  const [membershipPlanId, setMembershipPlanId] = useState(
24
- classData?.membership_plan_id || ""
25
  );
 
26
 
27
  function toggleDay(day) {
28
  setDaysOfWeek((prev) =>
@@ -32,44 +37,57 @@ export default function ClassForm({
32
 
33
  function handleSubmit(e) {
34
  e.preventDefault();
35
- if (!name.trim()) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  const payload = {
38
  name: name.trim(),
39
  description: description.trim() || null,
40
- class_time: classTime || null,
41
  days_of_week: daysOfWeek,
42
- classes_per_week: Number(classesPerWeek) || 0,
43
- max_students: Number(maxStudents) || 0,
44
  membership_plan_id: membershipPlanId ? Number(membershipPlanId) : null,
45
  };
46
 
47
- onSave && onSave(payload);
48
  }
49
 
50
  return (
51
  <form onSubmit={handleSubmit} className="space-y-5">
52
- {/* Class name & time */}
53
  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
54
  <div>
55
- <label className="block text-sm font-medium text-stone-800 mb-1">
56
  Class Name
57
  </label>
58
  <input
59
  type="text"
60
- className="w-full rounded-lg border border-stone-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-red-500"
61
  placeholder="e.g., Beginner Karate"
62
  value={name}
63
  onChange={(e) => setName(e.target.value)}
64
  />
65
  </div>
66
  <div>
67
- <label className="block text-sm font-medium text-stone-800 mb-1">
68
  Class Time
69
  </label>
70
  <input
71
  type="time"
72
- className="w-full rounded-lg border border-stone-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-red-500"
73
  value={classTime}
74
  onChange={(e) => setClassTime(e.target.value)}
75
  />
@@ -78,11 +96,11 @@ export default function ClassForm({
78
 
79
  {/* Description */}
80
  <div>
81
- <label className="block text-sm font-medium text-stone-800 mb-1">
82
  Description
83
  </label>
84
  <textarea
85
- className="w-full rounded-lg border border-stone-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-red-500 min-h-[80px]"
86
  placeholder="Class description..."
87
  value={description}
88
  onChange={(e) => setDescription(e.target.value)}
@@ -91,7 +109,7 @@ export default function ClassForm({
91
 
92
  {/* Days of week */}
93
  <div>
94
- <label className="block text-sm font-medium text-stone-800 mb-2">
95
  Days of Week
96
  </label>
97
  <div className="flex flex-wrap gap-2">
@@ -102,10 +120,10 @@ export default function ClassForm({
102
  key={day}
103
  type="button"
104
  onClick={() => toggleDay(day)}
105
- className={`px-3 py-1.5 rounded-full text-xs font-medium border ${
106
  active
107
  ? "bg-red-600 text-white border-red-600"
108
- : "bg-white text-stone-700 border-stone-200 hover:bg-stone-50"
109
  }`}
110
  >
111
  {day}
@@ -115,64 +133,70 @@ export default function ClassForm({
115
  </div>
116
  </div>
117
 
118
- {/* Numbers & plan */}
119
  <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
120
  <div>
121
- <label className="block text-sm font-medium text-stone-800 mb-1">
122
  Classes Per Week
123
  </label>
124
  <input
125
  type="number"
126
- min="0"
127
- className="w-full rounded-lg border border-stone-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-red-500"
128
  value={classesPerWeek}
129
  onChange={(e) => setClassesPerWeek(e.target.value)}
130
  />
131
  </div>
132
  <div>
133
- <label className="block text-sm font-medium text-stone-800 mb-1">
134
  Max Students
135
  </label>
136
  <input
137
  type="number"
138
- min="0"
139
- className="w-full rounded-lg border border-stone-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-red-500"
140
  value={maxStudents}
141
  onChange={(e) => setMaxStudents(e.target.value)}
142
  />
143
  </div>
144
  <div>
145
- <label className="block text-sm font-medium text-stone-800 mb-1">
146
  Membership Plan
147
  </label>
148
  <select
149
- className="w-full rounded-lg border border-stone-200 px-3 py-2 text-sm bg-white focus:outline-none focus:ring-1 focus:ring-red-500"
150
- value={membershipPlanId || ""}
151
  onChange={(e) => setMembershipPlanId(e.target.value)}
152
  >
153
  <option value="">Select plan</option>
154
  {plans.map((p) => (
155
  <option key={p.id} value={p.id}>
156
- {p.name}
157
  </option>
158
  ))}
159
  </select>
160
  </div>
161
  </div>
162
 
163
- {/* Actions */}
164
- <div className="flex justify-start gap-3 pt-2">
 
 
 
 
 
 
165
  <button
166
  type="submit"
167
  disabled={isLoading}
168
- className="inline-flex items-center justify-center rounded-lg bg-red-600 hover:bg-red-700 text-white text-sm font-medium px-4 py-2 disabled:opacity-60"
169
  >
170
- {isLoading ? "Saving..." : classData ? "Update Class" : "Create Class"}
171
  </button>
172
  <button
173
  type="button"
174
  onClick={onCancel}
175
- className="inline-flex items-center justify-center rounded-lg border border-stone-200 text-sm font-medium px-4 py-2 text-stone-700 hover:bg-stone-50"
176
  >
177
  Cancel
178
  </button>
 
11
  isLoading,
12
  }) {
13
  const [name, setName] = useState(classData?.name || "");
 
14
  const [description, setDescription] = useState(classData?.description || "");
15
+ const [classTime, setClassTime] = useState(classData?.class_time || "");
16
+ const [daysOfWeek, setDaysOfWeek] = useState(
17
+ classData?.days_of_week && classData.days_of_week.length
18
+ ? classData.days_of_week
19
+ : ["Monday"]
20
+ );
21
  const [classesPerWeek, setClassesPerWeek] = useState(
22
+ classData?.classes_per_week ?? 1
23
  );
24
  const [maxStudents, setMaxStudents] = useState(
25
+ classData?.max_students ?? 20
26
  );
27
  const [membershipPlanId, setMembershipPlanId] = useState(
28
+ classData?.membership_plan_id ?? ""
29
  );
30
+ const [error, setError] = useState("");
31
 
32
  function toggleDay(day) {
33
  setDaysOfWeek((prev) =>
 
37
 
38
  function handleSubmit(e) {
39
  e.preventDefault();
40
+ setError("");
41
+
42
+ if (!name.trim()) {
43
+ setError("Please enter a class name.");
44
+ return;
45
+ }
46
+ if (!classTime) {
47
+ setError("Please choose a class time.");
48
+ return;
49
+ }
50
+ if (!daysOfWeek.length) {
51
+ setError("Please select at least one day.");
52
+ return;
53
+ }
54
 
55
  const payload = {
56
  name: name.trim(),
57
  description: description.trim() || null,
58
+ class_time: classTime, // HTML time input -> "HH:MM", backend parses to time
59
  days_of_week: daysOfWeek,
60
+ classes_per_week: Number(classesPerWeek) || 1,
61
+ max_students: Number(maxStudents) || 1,
62
  membership_plan_id: membershipPlanId ? Number(membershipPlanId) : null,
63
  };
64
 
65
+ if (onSave) onSave(payload);
66
  }
67
 
68
  return (
69
  <form onSubmit={handleSubmit} className="space-y-5">
70
+ {/* Class name + time */}
71
  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
72
  <div>
73
+ <label className="block text-sm font-medium text-stone-700 mb-1">
74
  Class Name
75
  </label>
76
  <input
77
  type="text"
78
+ 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"
79
  placeholder="e.g., Beginner Karate"
80
  value={name}
81
  onChange={(e) => setName(e.target.value)}
82
  />
83
  </div>
84
  <div>
85
+ <label className="block text-sm font-medium text-stone-700 mb-1">
86
  Class Time
87
  </label>
88
  <input
89
  type="time"
90
+ 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"
91
  value={classTime}
92
  onChange={(e) => setClassTime(e.target.value)}
93
  />
 
96
 
97
  {/* Description */}
98
  <div>
99
+ <label className="block text-sm font-medium text-stone-700 mb-1">
100
  Description
101
  </label>
102
  <textarea
103
+ 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]"
104
  placeholder="Class description..."
105
  value={description}
106
  onChange={(e) => setDescription(e.target.value)}
 
109
 
110
  {/* Days of week */}
111
  <div>
112
+ <label className="block text-sm font-medium text-stone-700 mb-2">
113
  Days of Week
114
  </label>
115
  <div className="flex flex-wrap gap-2">
 
120
  key={day}
121
  type="button"
122
  onClick={() => toggleDay(day)}
123
+ className={`px-3 py-1.5 rounded-full text-xs font-medium border transition ${
124
  active
125
  ? "bg-red-600 text-white border-red-600"
126
+ : "bg-white text-stone-700 border-stone-300 hover:bg-stone-50"
127
  }`}
128
  >
129
  {day}
 
133
  </div>
134
  </div>
135
 
136
+ {/* Numbers + plan */}
137
  <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
138
  <div>
139
+ <label className="block text-sm font-medium text-stone-700 mb-1">
140
  Classes Per Week
141
  </label>
142
  <input
143
  type="number"
144
+ min={1}
145
+ 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"
146
  value={classesPerWeek}
147
  onChange={(e) => setClassesPerWeek(e.target.value)}
148
  />
149
  </div>
150
  <div>
151
+ <label className="block text-sm font-medium text-stone-700 mb-1">
152
  Max Students
153
  </label>
154
  <input
155
  type="number"
156
+ min={1}
157
+ 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"
158
  value={maxStudents}
159
  onChange={(e) => setMaxStudents(e.target.value)}
160
  />
161
  </div>
162
  <div>
163
+ <label className="block text-sm font-medium text-stone-700 mb-1">
164
  Membership Plan
165
  </label>
166
  <select
167
+ className="w-full rounded-lg border border-stone-300 px-3 py-2 text-sm bg-white focus:outline-none focus:ring-2 focus:ring-red-500/70 focus:border-red-500"
168
+ value={membershipPlanId}
169
  onChange={(e) => setMembershipPlanId(e.target.value)}
170
  >
171
  <option value="">Select plan</option>
172
  {plans.map((p) => (
173
  <option key={p.id} value={p.id}>
174
+ {p.name} ({p.billing_period})
175
  </option>
176
  ))}
177
  </select>
178
  </div>
179
  </div>
180
 
181
+ {/* Error + buttons */}
182
+ {error && (
183
+ <p className="text-sm text-red-600 bg-red-50 border border-red-100 rounded-md px-3 py-2">
184
+ {error}
185
+ </p>
186
+ )}
187
+
188
+ <div className="flex justify-start gap-3 pt-1">
189
  <button
190
  type="submit"
191
  disabled={isLoading}
192
+ 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"
193
  >
194
+ {classData ? "Save changes" : "Create Class"}
195
  </button>
196
  <button
197
  type="button"
198
  onClick={onCancel}
199
+ 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"
200
  >
201
  Cancel
202
  </button>