sackey e839d40a99
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run
Initial commit
2025-07-30 17:15:50 +00:00

230 lines
9.2 KiB
PHP

<?php
namespace App\Livewire\Appointments;
use Livewire\Component;
use App\Models\Appointment;
use App\Models\Customer;
use App\Models\Vehicle;
use App\Models\Technician;
use Carbon\Carbon;
class Create extends Component
{
// Form fields
public $customer_id = '';
public $vehicle_id = '';
public $assigned_technician_id = '';
public $scheduled_date = '';
public $scheduled_time = '';
public $estimated_duration_minutes = 60;
public $appointment_type = 'maintenance';
public $service_requested = '';
public $customer_notes = '';
public $internal_notes = '';
// Options
public $customers = [];
public $vehicles = [];
public $technicians = [];
public $availableTimeSlots = [];
public $appointmentTypes = [
'maintenance' => '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'
]);
}
}