'Maintenance', 'repair' => 'Repair', 'inspection' => 'Inspection', 'estimate' => 'Estimate', 'pickup' => 'Pickup', 'delivery' => 'Delivery' ]; public $durationOptions = [ 30 => '30 minutes', 60 => '1 hour', 90 => '1.5 hours', 120 => '2 hours', 150 => '2.5 hours', 180 => '3 hours', 240 => '4 hours', ]; protected $rules = [ 'customer_id' => 'required|exists:customers,id', 'vehicle_id' => 'required|exists:vehicles,id', 'scheduled_date' => 'required|date|after_or_equal:today', 'scheduled_time' => 'required', 'estimated_duration_minutes' => 'required|integer|min:15|max:480', 'appointment_type' => 'required|in:maintenance,repair,inspection,estimate,pickup,delivery', 'service_requested' => 'required|string|max:500', 'customer_notes' => 'nullable|string|max:1000', 'internal_notes' => 'nullable|string|max:1000', ]; protected $messages = [ 'customer_id.required' => 'Please select a customer.', 'vehicle_id.required' => 'Please select a vehicle.', 'scheduled_date.required' => 'Please select an appointment date.', 'scheduled_date.after_or_equal' => 'Appointment date cannot be in the past.', 'scheduled_time.required' => 'Please select an appointment time.', 'service_requested.required' => 'Please describe the service requested.', ]; public function mount() { $this->loadInitialData(); $this->scheduled_date = Carbon::tomorrow()->format('Y-m-d'); } public function loadInitialData() { $this->customers = Customer::orderBy('first_name')->orderBy('last_name')->get(); $this->technicians = Technician::where('status', 'active')->orderBy('first_name')->orderBy('last_name')->get(); } public function updatedCustomerId() { if ($this->customer_id) { $this->vehicles = Vehicle::where('customer_id', $this->customer_id)->get(); $this->vehicle_id = ''; } else { $this->vehicles = []; $this->vehicle_id = ''; } } public function updatedScheduledDate() { if ($this->scheduled_date) { $this->loadAvailableTimeSlots(); } } public function updatedAssignedTechnicianId() { if ($this->scheduled_date) { $this->loadAvailableTimeSlots(); } } public function loadAvailableTimeSlots() { $date = Carbon::parse($this->scheduled_date); $technicianId = $this->assigned_technician_id; // Generate time slots from 8 AM to 5 PM $slots = []; $startTime = $date->copy()->setTime(8, 0); $endTime = $date->copy()->setTime(17, 0); while ($startTime->lt($endTime)) { $timeSlot = $startTime->format('H:i'); // Check if this time slot is available for the technician $isAvailable = true; if ($technicianId) { $startDateTime = Carbon::parse($this->scheduled_date . ' ' . $timeSlot); $endDateTime = $startDateTime->copy()->addMinutes($this->estimated_duration_minutes); $conflictingAppointments = Appointment::where('assigned_technician_id', $technicianId) ->where(function($query) use ($startDateTime, $endDateTime) { $query->where(function($q) use ($startDateTime, $endDateTime) { // Check if new appointment overlaps with existing ones $q->where(function($subQ) use ($startDateTime, $endDateTime) { // New appointment starts during existing appointment $subQ->where('scheduled_datetime', '<=', $startDateTime) ->whereRaw('DATE_ADD(scheduled_datetime, INTERVAL estimated_duration_minutes MINUTE) > ?', [$startDateTime]); })->orWhere(function($subQ) use ($startDateTime, $endDateTime) { // New appointment ends during existing appointment $subQ->where('scheduled_datetime', '<', $endDateTime) ->where('scheduled_datetime', '>=', $startDateTime); }); }); }) ->exists(); $isAvailable = !$conflictingAppointments; } if ($isAvailable) { $slots[] = [ 'value' => $timeSlot, 'label' => $startTime->format('g:i A'), ]; } $startTime->addMinutes(30); } $this->availableTimeSlots = $slots; } public function save() { $this->validate(); try { // Check for conflicts one more time $scheduledDateTime = Carbon::parse($this->scheduled_date . ' ' . $this->scheduled_time); $endDateTime = $scheduledDateTime->copy()->addMinutes($this->estimated_duration_minutes); if ($this->assigned_technician_id) { $conflicts = Appointment::where('assigned_technician_id', $this->assigned_technician_id) ->where(function($query) use ($scheduledDateTime, $endDateTime) { $query->where(function($q) use ($scheduledDateTime, $endDateTime) { // Check if new appointment overlaps with existing ones $q->where(function($subQ) use ($scheduledDateTime, $endDateTime) { // New appointment starts during existing appointment $subQ->where('scheduled_datetime', '<=', $scheduledDateTime) ->whereRaw('DATE_ADD(scheduled_datetime, INTERVAL estimated_duration_minutes MINUTE) > ?', [$scheduledDateTime]); })->orWhere(function($subQ) use ($scheduledDateTime, $endDateTime) { // New appointment ends during existing appointment $subQ->where('scheduled_datetime', '<', $endDateTime) ->where('scheduled_datetime', '>=', $scheduledDateTime); })->orWhere(function($subQ) use ($scheduledDateTime, $endDateTime) { // New appointment completely contains existing appointment $subQ->where('scheduled_datetime', '>=', $scheduledDateTime) ->whereRaw('DATE_ADD(scheduled_datetime, INTERVAL estimated_duration_minutes MINUTE) <= ?', [$endDateTime]); }); }); }) ->exists(); if ($conflicts) { $this->addError('scheduled_time', 'This time slot conflicts with another appointment for the selected technician.'); return; } } // Combine date and time into scheduled_datetime $scheduledDateTime = Carbon::parse($this->scheduled_date . ' ' . $this->scheduled_time); $appointment = Appointment::create([ 'customer_id' => $this->customer_id, 'vehicle_id' => $this->vehicle_id, 'assigned_technician_id' => $this->assigned_technician_id ?: null, 'scheduled_datetime' => $scheduledDateTime, 'estimated_duration_minutes' => $this->estimated_duration_minutes, 'appointment_type' => $this->appointment_type, 'service_requested' => $this->service_requested, 'customer_notes' => $this->customer_notes, 'internal_notes' => $this->internal_notes, 'status' => 'scheduled', 'created_by' => auth()->id(), ]); session()->flash('message', 'Appointment scheduled successfully!'); return redirect()->route('appointments.index'); } catch (\Exception $e) { $this->addError('general', 'Error creating appointment: ' . $e->getMessage()); } } public function render() { return view('livewire.appointments.create')->layout('components.layouts.app', [ 'title' => 'Schedule Appointment' ]); } }