*/ public function getMonthAvailability(int $year, int $month): array { $startOfMonth = Carbon::create($year, $month, 1)->startOfMonth(); $endOfMonth = $startOfMonth->copy()->endOfMonth(); $availability = []; $current = $startOfMonth->copy(); while ($current->lte($endOfMonth)) { $availability[$current->format('Y-m-d')] = $this->getDateStatus($current); $current->addDay(); } return $availability; } /** * Get the availability status for a specific date. */ public function getDateStatus(Carbon $date): string { // Past date if ($date->lt(today())) { return 'past'; } // Check if fully blocked if ($this->isDateFullyBlocked($date)) { return 'blocked'; } // Check working hours $workingHour = WorkingHour::query() ->where('day_of_week', $date->dayOfWeek) ->where('is_active', true) ->first(); if (! $workingHour) { return 'closed'; } // Get available slots $availableSlots = $this->getAvailableSlots($date); if (empty($availableSlots)) { return 'full'; } $totalSlots = count($workingHour->getSlots(60)); if (count($availableSlots) < $totalSlots) { return 'partial'; } return 'available'; } /** * Get available time slots for a specific date. * * @return array */ public function getAvailableSlots(Carbon $date): array { $workingHour = WorkingHour::query() ->where('day_of_week', $date->dayOfWeek) ->where('is_active', true) ->first(); if (! $workingHour) { return []; } // All possible slots $allSlots = $workingHour->getSlots(60); // Booked slots (pending and approved consultations block the slot) $bookedSlots = Consultation::query() ->whereDate('booking_date', $date) ->whereIn('status', [ConsultationStatus::Pending, ConsultationStatus::Approved]) ->pluck('booking_time') ->map(fn ($t) => Carbon::parse($t)->format('H:i')) ->toArray(); // Blocked slots $blockedSlots = $this->getBlockedSlots($date); return array_values(array_diff($allSlots, $bookedSlots, $blockedSlots)); } /** * Get blocked time slots for a specific date. * * @return array */ private function getBlockedSlots(Carbon $date): array { $blockedTimes = BlockedTime::query() ->whereDate('block_date', $date) ->get(); $blockedSlots = []; foreach ($blockedTimes as $blocked) { if ($blocked->isAllDay()) { // Block all slots for the day $workingHour = WorkingHour::query() ->where('day_of_week', $date->dayOfWeek) ->first(); return $workingHour ? $workingHour->getSlots(60) : []; } // Calculate blocked slots from time range $start = Carbon::parse($blocked->start_time); $end = Carbon::parse($blocked->end_time); $current = $start->copy(); while ($current->lt($end)) { $blockedSlots[] = $current->format('H:i'); $current->addMinutes(60); } } return array_unique($blockedSlots); } /** * Check if a date is fully blocked (all-day block). */ private function isDateFullyBlocked(Carbon $date): bool { return BlockedTime::query() ->whereDate('block_date', $date) ->whereNull('start_time') ->exists(); } }