| |
| import React, { useState, useEffect } from "react"; |
| import { Plus, Trash2, Loader2, Save, X } from "lucide-react"; |
|
|
| const DAYS = [ |
| "Monday", |
| "Tuesday", |
| "Wednesday", |
| "Thursday", |
| "Friday", |
| "Saturday", |
| "Sunday", |
| ]; |
|
|
| export default function ClassForm({ classData, onSave, onCancel, isLoading }) { |
| |
| const [formData, setFormData] = useState({ |
| name: "", |
| description: "", |
| coach_email: "", |
| schedule: [{ day: "Monday", start_time: "", end_time: "" }], |
| is_active: true, |
| }); |
|
|
| useEffect(() => { |
| if (!classData) return; |
| setFormData({ |
| name: classData.name || "", |
| description: classData.description || "", |
| coach_email: classData.coach_email || "", |
| schedule: |
| Array.isArray(classData.schedule) && classData.schedule.length > 0 |
| ? classData.schedule.map((slot) => ({ |
| day: slot.day || "Monday", |
| start_time: slot.start_time || "", |
| end_time: slot.end_time || "", |
| })) |
| : [{ day: "Monday", start_time: "", end_time: "" }], |
| is_active: |
| classData.is_active !== undefined ? classData.is_active : true, |
| }); |
| }, [classData]); |
|
|
| const addScheduleSlot = () => { |
| setFormData((prev) => ({ |
| ...prev, |
| schedule: [ |
| ...prev.schedule, |
| { day: "Monday", start_time: "", end_time: "" }, |
| ], |
| })); |
| }; |
|
|
| const removeScheduleSlot = (index) => { |
| setFormData((prev) => ({ |
| ...prev, |
| schedule: prev.schedule.filter((_, i) => i !== index), |
| })); |
| }; |
|
|
| const updateScheduleSlot = (index, field, value) => { |
| setFormData((prev) => { |
| const schedule = [...prev.schedule]; |
| schedule[index] = { ...schedule[index], [field]: value }; |
| return { ...prev, schedule }; |
| }); |
| }; |
|
|
| const handleSubmit = (e) => { |
| e.preventDefault(); |
|
|
| |
| const transformedSchedule = formData.schedule.map((slot) => ({ |
| day: slot.day, |
| start_time: slot.start_time, |
| end_time: slot.end_time, |
| time: |
| slot.start_time && slot.end_time |
| ? `${slot.start_time}-${slot.end_time}` |
| : "", |
| })); |
|
|
| const payload = { |
| name: formData.name, |
| description: formData.description, |
| coach_email: formData.coach_email || null, |
| schedule: transformedSchedule, |
| is_active: formData.is_active, |
| }; |
|
|
| onSave(payload); |
| }; |
|
|
| return ( |
| <form onSubmit={handleSubmit} className="space-y-6"> |
| {/* Class Name */} |
| <div className="space-y-1"> |
| <label className="block text-sm font-medium text-stone-800"> |
| Class Name |
| </label> |
| <input |
| type="text" |
| className="w-full rounded-md border border-stone-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-red-600" |
| value={formData.name} |
| onChange={(e) => |
| setFormData((prev) => ({ ...prev, name: e.target.value })) |
| } |
| placeholder="e.g., Beginner Karate" |
| required |
| /> |
| </div> |
| |
| {/* Description */} |
| <div className="space-y-1"> |
| <label className="block text-sm font-medium text-stone-800"> |
| Description |
| </label> |
| <textarea |
| className="w-full rounded-md border border-stone-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-red-600" |
| rows={2} |
| value={formData.description} |
| onChange={(e) => |
| setFormData((prev) => ({ |
| ...prev, |
| description: e.target.value, |
| })) |
| } |
| placeholder="Class description..." |
| /> |
| </div> |
| |
| {/* Coach Email */} |
| <div className="space-y-1"> |
| <label className="block text-sm font-medium text-stone-800"> |
| Coach Email (Optional) |
| </label> |
| <input |
| type="email" |
| className="w-full rounded-md border border-stone-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-red-600" |
| value={formData.coach_email} |
| onChange={(e) => |
| setFormData((prev) => ({ ...prev, coach_email: e.target.value })) |
| } |
| placeholder="coach@dojo.com" |
| /> |
| <p className="text-xs text-stone-500 mt-1"> |
| Assign a coach to this class. Coach can login with this email to manage attendance. |
| </p> |
| </div> |
| |
| {/* Schedule */} |
| <div> |
| <div className="flex items-center justify-between mb-3"> |
| <span className="text-sm font-medium text-stone-800"> |
| Class Schedule |
| </span> |
| <button |
| type="button" |
| onClick={addScheduleSlot} |
| className="inline-flex items-center gap-2 rounded-md border border-stone-300 px-2.5 py-1 text-xs font-medium text-stone-800 hover:bg-stone-50" |
| > |
| <Plus className="w-4 h-4" /> |
| Add Time Slot |
| </button> |
| </div> |
| |
| <div className="space-y-3"> |
| {formData.schedule.map((slot, index) => ( |
| <div |
| key={index} |
| className="rounded-lg border border-stone-200 bg-stone-50 px-3 py-3" |
| > |
| <div className="flex flex-col gap-3 md:flex-row md:items-center"> |
| {/* Day */} |
| <div className="w-full md:w-40"> |
| <label className="block text-xs font-medium text-stone-700 mb-1"> |
| Day |
| </label> |
| <select |
| className="w-full rounded-md border border-stone-300 px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-red-600" |
| value={slot.day} |
| onChange={(e) => |
| updateScheduleSlot(index, "day", e.target.value) |
| } |
| > |
| {DAYS.map((day) => ( |
| <option key={day} value={day}> |
| {day} |
| </option> |
| ))} |
| </select> |
| </div> |
| |
| {/* From / To */} |
| <div className="flex flex-1 flex-col gap-3 md:flex-row"> |
| <div className="flex-1"> |
| <label className="block text-xs font-medium text-stone-700 mb-1"> |
| From |
| </label> |
| <input |
| type="time" |
| className="w-full rounded-md border border-stone-300 px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-red-600" |
| value={slot.start_time} |
| onChange={(e) => |
| updateScheduleSlot(index, "start_time", e.target.value) |
| } |
| /> |
| </div> |
| <div className="flex-1"> |
| <label className="block text-xs font-medium text-stone-700 mb-1"> |
| To |
| </label> |
| <input |
| type="time" |
| className="w-full rounded-md border border-stone-300 px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-red-600" |
| value={slot.end_time} |
| onChange={(e) => |
| updateScheduleSlot(index, "end_time", e.target.value) |
| } |
| /> |
| </div> |
| </div> |
| |
| {/* Remove */} |
| {formData.schedule.length > 1 && ( |
| <button |
| type="button" |
| onClick={() => removeScheduleSlot(index)} |
| className="self-start rounded-md p-2 text-red-600 hover:bg-red-50" |
| > |
| <Trash2 className="w-4 h-4" /> |
| </button> |
| )} |
| </div> |
| </div> |
| ))} |
| </div> |
| </div> |
| |
| {/* Actions */} |
| <div className="flex gap-3 pt-2"> |
| <button |
| type="submit" |
| disabled={isLoading} |
| className="inline-flex items-center gap-2 rounded-md bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700 disabled:opacity-60" |
| > |
| {isLoading ? ( |
| <Loader2 className="w-4 h-4 animate-spin" /> |
| ) : ( |
| <Save className="w-4 h-4" /> |
| )} |
| {classData ? "Update Class" : "Create Class"} |
| </button> |
| |
| <button |
| type="button" |
| onClick={onCancel} |
| disabled={isLoading} |
| className="inline-flex items-center gap-2 rounded-md border border-stone-300 px-4 py-2 text-sm font-medium text-stone-800 hover:bg-stone-50 disabled:opacity-60" |
| > |
| <X className="w-4 h-4" /> |
| Cancel |
| </button> |
| </div> |
| </form> |
| ); |
| } |
|
|