'tersedia', 'kategori' => 'outdoor' ]; /** * Get all reservations for this table. */ public function reservasi() { return $this->hasMany(Reservasi::class, 'meja_id'); } /** * Get active reservations for this table. */ public function activeReservations(): HasMany { return $this->reservasi() ->where('status', '!=', 'cancelled') ->where('date', '>=', Carbon::today()) ->orderBy('date') ->orderBy('start_time'); } /** * Check if table has any available slots for a specific date */ public function hasAvailableSlotsForDate($date): bool { $reservations = $this->getReservationsForDate($date); $operatingHours = $this->getOperatingHours(); // If no reservations, the entire day is available if ($reservations->isEmpty()) { return true; } $reservedSlots = $reservations->map(function ($reservation) { return [ 'start' => Carbon::parse($reservation->start_time), 'end' => Carbon::parse($reservation->end_time) ]; })->sortBy('start'); // Check for any gaps between reservations $previousEnd = Carbon::parse($operatingHours['start']); foreach ($reservedSlots as $slot) { if ($previousEnd->lt($slot['start'])) { return true; // Found a gap } $previousEnd = max($previousEnd, $slot['end']); } // Check if there's time after the last reservation $lastSlotEnd = $reservedSlots->last()['end']; $closingTime = Carbon::parse($operatingHours['end']); return $lastSlotEnd->lt($closingTime); } /** * Get the next available date with open slots */ public function getNextAvailableDate($startDate = null): ?string { $date = $startDate ? Carbon::parse($startDate) : Carbon::today(); $maxDays = 30; // Limit search to next 30 days $daysChecked = 0; while ($daysChecked < $maxDays) { if ($this->hasAvailableSlotsForDate($date->format('Y-m-d'))) { return $date->format('Y-m-d'); } $date->addDay(); $daysChecked++; } return null; } /** * Get operating hours */ public function getOperatingHours(): array { return [ 'start' => '10:00', 'end' => '22:00' ]; } /** * Get available time slots for a specific date */ public function getAvailableTimeSlots($date): array { $reservations = $this->getReservationsForDate($date); $operatingHours = $this->getOperatingHours(); $slots = []; $startTime = Carbon::parse($operatingHours['start']); $endTime = Carbon::parse($operatingHours['end']); $interval = 30; // 30 minutes interval // If date is today, start from current time rounded up to next 30 minutes if ($date === date('Y-m-d')) { $now = Carbon::now(); $startTime = $now->copy()->addMinutes($interval - ($now->minute % $interval))->startOfMinute(); if ($startTime->lt($now)) { $startTime->addMinutes($interval); } } while ($startTime < $endTime) { $timeSlot = $startTime->format('H:i'); $isAvailable = true; // Check if this time slot conflicts with any existing reservations foreach ($reservations as $reservation) { $reservationStart = Carbon::parse($reservation->start_time); $reservationEnd = Carbon::parse($reservation->end_time); if ($startTime->between($reservationStart, $reservationEnd, true)) { $isAvailable = false; break; } } if ($isAvailable) { $slots[] = $timeSlot; } $startTime->addMinutes($interval); } return $slots; } /** * Get reservations for a specific date */ public function getReservationsForDate($date) { return $this->reservasi() ->whereDate('date', $date) ->where('status', '!=', 'cancelled') ->get(['start_time', 'end_time']); } /** * Check if table is available for a specific time slot. */ public function isAvailableForTimeSlot($date, $startTime, $endTime): bool { return Reservasi::isTimeSlotAvailable($this->id, $date, $startTime, $endTime); } /** * Get the status badge class */ public function getStatusBadgeClass(): string { return match($this->status) { 'tersedia' => 'bg-green-100 text-green-800', 'dipesan' => 'bg-yellow-100 text-yellow-800', 'terisi' => 'bg-red-100 text-red-800', default => 'bg-gray-100 text-gray-800', }; } /** * Get formatted category name */ public function getFormattedCategory(): string { return match($this->kategori) { 'outdoor' => 'Outdoor', 'vip-outdoor' => 'VIP Outdoor', 'vip-indoor' => 'VIP Indoor', default => ucfirst($this->kategori), }; } /** * Check if the table is available */ public function getIsAvailableAttribute(): bool { return $this->status === 'tersedia'; } /** * Format nomor meja for display */ public function getNomorMejaFormattedAttribute(): string { return 'M' . str_pad($this->nomor_meja, 3, '0', STR_PAD_LEFT); } // Accessor untuk memastikan kapasitas selalu integer public function getKapasitasAttribute($value) { return (int) $value; } // Scope untuk meja yang tersedia public function scopeAvailable($query) { return $query->where('status', 'tersedia'); } }