| | |
| | 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> |
| | ); |
| | } |
| |
|