Commit b2a36e89 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Show orphan schedules on facility grid + cancel support

The grid now also displays training_schedules that have no matching
space_reservation (orphan schedules). These appear on the grid so users
can see and cancel them. The cancelReservation method now accepts both
reservationId and scheduleId to handle both cases.

Also added cancel button column to the FacilityShow reservations tab.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 18ced3e7
...@@ -511,16 +511,25 @@ public function saveRecurring(): void ...@@ -511,16 +511,25 @@ public function saveRecurring(): void
// ─── Cancel Operations ─────────────────────────────────────── // ─── Cancel Operations ───────────────────────────────────────
public function cancelReservation(int $reservationId): void public function cancelReservation(?int $reservationId = null, ?int $scheduleId = null): void
{ {
$reservation = SpaceReservation::find($reservationId); if ($reservationId) {
if (!$reservation) return; $reservation = SpaceReservation::find($reservationId);
if ($reservation) {
$reservation->update([
'status' => 'cancelled',
'cancelled_by' => auth()->id(),
'cancelled_at' => now(),
]);
}
}
$reservation->update([ if ($scheduleId) {
'status' => 'cancelled', $schedule = TrainingSchedule::find($scheduleId);
'cancelled_by' => auth()->id(), if ($schedule) {
'cancelled_at' => now(), $schedule->update(['is_active' => false, 'effective_until' => now()->toDateString()]);
]); }
}
$this->loadGridForSlot(); $this->loadGridForSlot();
$this->refreshAvailability(); $this->refreshAvailability();
...@@ -762,10 +771,13 @@ private function loadGridForSlot(): void ...@@ -762,10 +771,13 @@ private function loadGridForSlot(): void
->with('reservable') ->with('reservable')
->get(); ->get();
$reservedScheduleIds = [];
foreach ($reservations as $res) { foreach ($reservations as $res) {
$trainerName = null; $trainerName = null;
if ($res->reservable && $res->reservable instanceof TrainingSchedule) { if ($res->reservable && $res->reservable instanceof TrainingSchedule) {
$trainerName = $res->reservable->trainer?->name_ar ?? $res->reservable->trainer?->name; $trainerName = $res->reservable->trainer?->name_ar ?? $res->reservable->trainer?->name;
$reservedScheduleIds[] = $res->reservable_id;
} }
$this->assignments[] = [ $this->assignments[] = [
...@@ -780,6 +792,32 @@ private function loadGridForSlot(): void ...@@ -780,6 +792,32 @@ private function loadGridForSlot(): void
'time' => substr($res->start_time, 0, 5) . ' - ' . substr($res->end_time, 0, 5), 'time' => substr($res->start_time, 0, 5) . ' - ' . substr($res->end_time, 0, 5),
]; ];
} }
// Also show active training_schedules that match this day/time but have no reservation
$dayOfWeek = Carbon::parse($this->selectedDay)->dayOfWeek;
$orphanSchedules = TrainingSchedule::where('facility_id', $facility->id)
->where('day_of_week', $dayOfWeek)
->where('is_active', true)
->where('start_time', '<', $this->selectedSlotEnd)
->where('end_time', '>', $this->selectedSlotStart)
->when(!empty($reservedScheduleIds), fn ($q) => $q->whereNotIn('id', $reservedScheduleIds))
->with(['group', 'trainer'])
->get();
foreach ($orphanSchedules as $schedule) {
$this->assignments[] = [
'type' => 'existing',
'reservation_id' => null,
'schedule_id' => $schedule->id,
'title' => $schedule->group?->name_ar ?? __('جدول بدون مجموعة'),
'segment_ids' => $schedule->space_reservation_template ?? [],
'is_recurring' => true,
'reservable_type' => TrainingSchedule::class,
'reservable_id' => $schedule->id,
'trainer_name' => $schedule->trainer?->name_ar ?? $schedule->trainer?->name,
'time' => substr($schedule->start_time, 0, 5) . ' - ' . substr($schedule->end_time, 0, 5),
];
}
} }
private function generateTimeSlots(?Facility $facility): array private function generateTimeSlots(?Facility $facility): array
...@@ -813,7 +851,7 @@ private function getExistingReservations(?Facility $facility): array ...@@ -813,7 +851,7 @@ private function getExistingReservations(?Facility $facility): array
{ {
if (!$facility || !$this->selectedDay) return []; if (!$facility || !$this->selectedDay) return [];
return SpaceReservation::where('facility_id', $facility->id) $reservations = SpaceReservation::where('facility_id', $facility->id)
->where('reservation_date', $this->selectedDay) ->where('reservation_date', $this->selectedDay)
->where('status', 'confirmed') ->where('status', 'confirmed')
->orderBy('start_time') ->orderBy('start_time')
...@@ -825,8 +863,40 @@ private function getExistingReservations(?Facility $facility): array ...@@ -825,8 +863,40 @@ private function getExistingReservations(?Facility $facility): array
'end' => substr($r->end_time, 0, 5), 'end' => substr($r->end_time, 0, 5),
'segment_ids' => $r->segment_ids ?? [], 'segment_ids' => $r->segment_ids ?? [],
'is_recurring' => $r->is_recurring, 'is_recurring' => $r->is_recurring,
'source' => 'reservation',
]) ])
->toArray(); ->toArray();
// Also include active schedules for this day that have no reservation
$dayOfWeek = Carbon::parse($this->selectedDay)->dayOfWeek;
$reservedScheduleIds = SpaceReservation::where('facility_id', $facility->id)
->where('reservation_date', $this->selectedDay)
->where('status', 'confirmed')
->where('reservable_type', TrainingSchedule::class)
->pluck('reservable_id')
->toArray();
$orphanSchedules = TrainingSchedule::where('facility_id', $facility->id)
->where('day_of_week', $dayOfWeek)
->where('is_active', true)
->when(!empty($reservedScheduleIds), fn ($q) => $q->whereNotIn('id', $reservedScheduleIds))
->with('group')
->orderBy('start_time')
->get();
foreach ($orphanSchedules as $schedule) {
$reservations[] = [
'id' => $schedule->id,
'title' => $schedule->group?->name_ar ?? __('جدول'),
'start' => substr($schedule->start_time, 0, 5),
'end' => substr($schedule->end_time, 0, 5),
'segment_ids' => $schedule->space_reservation_template ?? [],
'is_recurring' => true,
'source' => 'schedule',
];
}
return $reservations;
} }
private function resolveLayout(?Facility $facility): ?SpaceLayout private function resolveLayout(?Facility $facility): ?SpaceLayout
......
...@@ -418,10 +418,10 @@ class="relative flex flex-col p-3 transition-all duration-100 cursor-pointer ...@@ -418,10 +418,10 @@ class="relative flex flex-col p-3 transition-all duration-100 cursor-pointer
@endif @endif
</div> </div>
<div class="flex gap-1 justify-center mt-1"> <div class="flex gap-1 justify-center mt-1">
<button wire:click="cancelReservation({{ $segAssignment['reservation_id'] }})" <button wire:click="cancelReservation({{ $segAssignment['reservation_id'] ?? 'null' }}, {{ $segAssignment['schedule_id'] ?? 'null' }})"
wire:confirm="{{ __('إلغاء هذا الحجز؟') }}" wire:confirm="{{ __('إلغاء هذا الحجز؟') }}"
class="text-[10px] text-red-600 hover:text-red-800 hover:underline">{{ __('إلغاء') }}</button> class="text-[10px] text-red-600 hover:text-red-800 hover:underline">{{ __('إلغاء') }}</button>
@if($segAssignment['is_recurring'] ?? false) @if(($segAssignment['is_recurring'] ?? false) && ($segAssignment['reservation_id'] ?? null))
<button wire:click="cancelSeries({{ $segAssignment['reservation_id'] }})" <button wire:click="cancelSeries({{ $segAssignment['reservation_id'] }})"
wire:confirm="{{ __('إلغاء كل حجوزات هذه السلسلة المستقبلية؟') }}" wire:confirm="{{ __('إلغاء كل حجوزات هذه السلسلة المستقبلية؟') }}"
class="text-[10px] text-red-500 hover:text-red-700 hover:underline">{{ __('السلسلة') }}</button> class="text-[10px] text-red-500 hover:text-red-700 hover:underline">{{ __('السلسلة') }}</button>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment