# Schedule Management Guide ## Overview The schedule system in Attune Heart Therapy API allows administrators to create time slots that clients can book for therapy sessions. This guide explains how schedule creation works and how to implement it in your frontend application. ## Understanding the Schedule System ### Schedule Model Structure ```go type Schedule struct { ID uint `json:"id"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` IsAvailable bool `json:"is_available"` MaxBookings int `json:"max_bookings"` BookedCount int `json:"booked_count"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } ``` ### Key Concepts 1. **Time Slots**: Each schedule represents a specific time period when therapy sessions can be booked 2. **Availability**: Schedules can be enabled/disabled using the `is_available` flag 3. **Capacity**: Each schedule can handle multiple bookings (useful for group sessions) 4. **Booking Count**: Tracks how many bookings have been made for each schedule ## Backend Implementation ### 1. Creating Schedules (Admin Only) **Endpoint**: `POST /api/admin/schedules` **Request Structure**: ```json { "start_time": "2024-12-15T10:00:00Z", "end_time": "2024-12-15T11:00:00Z", "max_bookings": 1 } ``` **Validation Rules**: - `start_time` must be in the future - `end_time` must be after `start_time` - `max_bookings` must be at least 1 - No overlapping schedules allowed **Response**: ```json { "message": "Schedule created successfully", "schedule": { "id": 1, "start_time": "2024-12-15T10:00:00Z", "end_time": "2024-12-15T11:00:00Z", "is_available": true, "max_bookings": 1, "booked_count": 0, "created_at": "2024-12-06T10:00:00Z", "updated_at": "2024-12-06T10:00:00Z" } } ``` ### 2. Updating Schedules **Endpoint**: `PUT /api/admin/schedules/:id` **Request Structure** (all fields optional): ```json { "start_time": "2024-12-15T14:00:00Z", "end_time": "2024-12-15T15:00:00Z", "max_bookings": 2, "is_available": false } ``` ### 3. Getting Available Slots (Public) **Endpoint**: `GET /api/schedules?date=2024-12-15` **Response**: ```json { "date": "2024-12-15", "slots": [ { "id": 1, "start_time": "2024-12-15T10:00:00Z", "end_time": "2024-12-15T11:00:00Z", "is_available": true, "max_bookings": 1, "booked_count": 0, "remaining_slots": 1 } ] } ``` ## Frontend Implementation ### 1. Admin Schedule Management Interface #### React Component Example ```jsx import React, { useState, useEffect } from 'react'; import { format, addDays, startOfWeek } from 'date-fns'; const ScheduleManager = () => { const [schedules, setSchedules] = useState([]); const [selectedDate, setSelectedDate] = useState(new Date()); const [isCreating, setIsCreating] = useState(false); const [newSchedule, setNewSchedule] = useState({ start_time: '', end_time: '', max_bookings: 1 }); // API service functions const apiService = { async createSchedule(scheduleData) { const response = await fetch('/api/admin/schedules', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('authToken')}` }, body: JSON.stringify(scheduleData) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to create schedule'); } return response.json(); }, async getAvailableSlots(date) { const dateStr = format(date, 'yyyy-MM-dd'); const response = await fetch(`/api/schedules?date=${dateStr}`); if (!response.ok) { throw new Error('Failed to fetch schedules'); } return response.json(); }, async updateSchedule(scheduleId, updateData) { const response = await fetch(`/api/admin/schedules/${scheduleId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('authToken')}` }, body: JSON.stringify(updateData) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to update schedule'); } return response.json(); } }; // Load schedules for selected date useEffect(() => { loadSchedules(); }, [selectedDate]); const loadSchedules = async () => { try { const data = await apiService.getAvailableSlots(selectedDate); setSchedules(data.slots || []); } catch (error) { console.error('Error loading schedules:', error); setSchedules([]); } }; const handleCreateSchedule = async (e) => { e.preventDefault(); try { // Convert local time to UTC const startTime = new Date(newSchedule.start_time).toISOString(); const endTime = new Date(newSchedule.end_time).toISOString(); await apiService.createSchedule({ start_time: startTime, end_time: endTime, max_bookings: parseInt(newSchedule.max_bookings) }); // Reset form and reload schedules setNewSchedule({ start_time: '', end_time: '', max_bookings: 1 }); setIsCreating(false); await loadSchedules(); alert('Schedule created successfully!'); } catch (error) { alert(`Error creating schedule: ${error.message}`); } }; const handleToggleAvailability = async (scheduleId, currentAvailability) => { try { await apiService.updateSchedule(scheduleId, { is_available: !currentAvailability }); await loadSchedules(); } catch (error) { alert(`Error updating schedule: ${error.message}`); } }; const generateTimeSlots = () => { const slots = []; for (let hour = 9; hour < 18; hour++) { for (let minute = 0; minute < 60; minute += 30) { const time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`; slots.push(time); } } return slots; }; const formatDateTime = (dateTime) => { return format(new Date(dateTime), 'MMM dd, yyyy HH:mm'); }; return (

Schedule Management

{/* Date Selector */}
setSelectedDate(new Date(e.target.value))} className="form-control" />
{/* Create Schedule Form */} {isCreating && (

Create New Schedule

setNewSchedule({ ...newSchedule, start_time: e.target.value })} required className="form-control" />
setNewSchedule({ ...newSchedule, end_time: e.target.value })} required className="form-control" />
setNewSchedule({ ...newSchedule, max_bookings: e.target.value })} required className="form-control" />
)} {/* Schedules List */}

Schedules for {format(selectedDate, 'MMMM dd, yyyy')}

{schedules.length === 0 ? (

No schedules found for this date.

) : (
{schedules.map((schedule) => (
{format(new Date(schedule.start_time), 'HH:mm')} - {format(new Date(schedule.end_time), 'HH:mm')}
{schedule.booked_count}/{schedule.max_bookings} booked {schedule.is_available ? 'Available' : 'Unavailable'}
))}
)}
); }; export default ScheduleManager; ``` #### CSS Styles ```css .schedule-manager { padding: 20px; max-width: 1200px; margin: 0 auto; } .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; } .date-selector { margin-bottom: 20px; } .date-selector label { display: block; margin-bottom: 5px; font-weight: bold; } .create-schedule-form { background: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 30px; } .form-group { margin-bottom: 15px; } .form-group label { display: block; margin-bottom: 5px; font-weight: bold; } .form-control { width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; } .form-actions { display: flex; gap: 10px; margin-top: 20px; } .schedules-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; } .schedule-card { border: 1px solid #ddd; border-radius: 8px; padding: 15px; background: white; } .schedule-time { font-size: 18px; font-weight: bold; margin-bottom: 10px; color: #333; } .schedule-details { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .capacity { font-size: 14px; color: #666; } .status { padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; text-transform: uppercase; } .status.available { background: #d4edda; color: #155724; } .status.unavailable { background: #f8d7da; color: #721c24; } .schedule-actions { text-align: right; } .btn { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; text-decoration: none; display: inline-block; } .btn-primary { background: #007bff; color: white; } .btn-success { background: #28a745; color: white; } .btn-warning { background: #ffc107; color: #212529; } .btn-secondary { background: #6c757d; color: white; } .btn-sm { padding: 4px 8px; font-size: 12px; } .no-schedules { text-align: center; color: #666; font-style: italic; padding: 40px; } ``` ### 2. Client Booking Interface #### React Component for Booking ```jsx import React, { useState, useEffect } from 'react'; import { format, addDays, startOfWeek } from 'date-fns'; const BookingInterface = () => { const [selectedDate, setSelectedDate] = useState(new Date()); const [availableSlots, setAvailableSlots] = useState([]); const [selectedSlot, setSelectedSlot] = useState(null); const [bookingNotes, setBookingNotes] = useState(''); const [isLoading, setIsLoading] = useState(false); const apiService = { async getAvailableSlots(date) { const dateStr = format(date, 'yyyy-MM-dd'); const response = await fetch(`/api/schedules?date=${dateStr}`); if (!response.ok) { throw new Error('Failed to fetch available slots'); } return response.json(); }, async createBooking(scheduleId, notes) { const response = await fetch('/api/bookings', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('authToken')}` }, body: JSON.stringify({ schedule_id: scheduleId, duration: 60, notes: notes, amount: 150.00 // $150 per session }) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to create booking'); } return response.json(); } }; useEffect(() => { loadAvailableSlots(); }, [selectedDate]); const loadAvailableSlots = async () => { setIsLoading(true); try { const data = await apiService.getAvailableSlots(selectedDate); setAvailableSlots(data.slots || []); } catch (error) { console.error('Error loading available slots:', error); setAvailableSlots([]); } finally { setIsLoading(false); } }; const handleBooking = async () => { if (!selectedSlot) { alert('Please select a time slot'); return; } try { setIsLoading(true); const result = await apiService.createBooking(selectedSlot.id, bookingNotes); alert('Booking created successfully! You will receive payment instructions shortly.'); // Reset form setSelectedSlot(null); setBookingNotes(''); // Reload available slots await loadAvailableSlots(); } catch (error) { alert(`Error creating booking: ${error.message}`); } finally { setIsLoading(false); } }; const getNextSevenDays = () => { const days = []; for (let i = 0; i < 7; i++) { days.push(addDays(new Date(), i)); } return days; }; return (

Book Your Therapy Session

{/* Date Selection */}

Select Date

{getNextSevenDays().map((date) => ( ))}
{/* Time Slot Selection */}

Available Times for {format(selectedDate, 'MMMM dd, yyyy')}

{isLoading ? (
Loading available times...
) : availableSlots.length === 0 ? (
No available time slots for this date. Please select another date.
) : (
{availableSlots.map((slot) => ( ))}
)}
{/* Booking Notes */} {selectedSlot && (

Booking Details

Selected Time: {format(selectedDate, 'MMMM dd, yyyy')} at{' '} {format(new Date(selectedSlot.start_time), 'HH:mm')} - {format(new Date(selectedSlot.end_time), 'HH:mm')}