Commit 1b16cf85 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Fix schedule builder grid — support grid/lanes/zones layout types

The grid was blank because lanes and zones segments have null row_index/col_index.
The old code assumed all layouts were grid-type and collapsed to 0×0.

- Grid layouts: keep the spreadsheet-style row/col selector view
- Lanes layouts: render as horizontal strips (label + cell side by side)
- Zones/custom layouts: responsive card grid (no row/col concept)

Layout type is now read from Facility::activeLayout (new HasOne relationship)
with a null-safe fallback to row_index detection. Segment cell HTML extracted
to _segment-cell.blade.php partial to avoid triplication.
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
parent abd15a9b
...@@ -88,6 +88,11 @@ public function layouts(): HasMany ...@@ -88,6 +88,11 @@ public function layouts(): HasMany
return $this->hasMany(SpaceLayout::class); return $this->hasMany(SpaceLayout::class);
} }
public function activeLayout(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(SpaceLayout::class)->where('is_active', true)->orderBy('sort_order');
}
public function reservations(): HasMany public function reservations(): HasMany
{ {
return $this->hasMany(SpaceReservation::class); return $this->hasMany(SpaceReservation::class);
......
...@@ -64,7 +64,7 @@ public function render() ...@@ -64,7 +64,7 @@ public function render()
{ {
$facilities = Facility::active()->with('branch')->orderBy('name_ar')->get(); $facilities = Facility::active()->with('branch')->orderBy('name_ar')->get();
$facility = $this->facility_id ? Facility::find($this->facility_id) : null; $facility = $this->facility_id ? Facility::with('activeLayout')->find($this->facility_id) : null;
$timeSlots = $this->generateTimeSlots($facility); $timeSlots = $this->generateTimeSlots($facility);
$existingReservations = $this->getExistingReservations($facility); $existingReservations = $this->getExistingReservations($facility);
......
{{--
Shared segment cell for all layout types (grid, lanes, zones/custom).
Expects: $seg (array), $isLastCol (bool), $isLastRow (bool)
Requires $assignments to be in scope (passed from parent view).
--}}
@php
$segAssignment = collect($assignments)->first(fn($a) => in_array($seg['id'], $a['segment_ids'] ?? []));
$isOccupied = $segAssignment !== null;
$isExisting = $isOccupied && ($segAssignment['type'] ?? '') === 'existing';
$isNewGroup = $isOccupied && ($segAssignment['type'] ?? '') === 'group';
$isNewTrainer = $isOccupied && ($segAssignment['type'] ?? '') === 'trainer';
@endphp
<div
@dragover.prevent="highlightSegment({{ $seg['id'] }})"
@dragleave="unhighlightSegment({{ $seg['id'] }})"
@drop.prevent="dropOnSegment({{ $seg['id'] }})"
@click="toggleSegmentSelection({{ $seg['id'] }})"
:class="{
'facility-cell--selected': selectedSegments.includes({{ $seg['id'] }}),
'facility-cell--drop-target': highlightedSegment === {{ $seg['id'] }},
}"
class="relative flex flex-col p-3 h-full transition-all duration-100 cursor-pointer
{{ !$isLastCol ? 'border-e' : '' }}
{{ !$isLastRow ? 'border-b' : '' }}
border-gray-200
{{ !$seg['available'] ? 'facility-cell--unavailable cursor-not-allowed opacity-50' : '' }}
{{ $isExisting ? 'bg-blue-500/10' : '' }}
{{ $isNewGroup ? 'bg-emerald-500/10' : '' }}
{{ $isNewTrainer ? 'bg-purple-500/10' : '' }}
{{ !$isOccupied && $seg['available'] ? 'hover:bg-gray-100/80' : '' }}
{{ $isLastCol && $isLastRow ? 'rounded-ee-lg' : '' }}"
>
<span class="text-[10px] font-mono text-gray-400 absolute top-1.5 end-2">{{ $seg['code'] }}</span>
@if($isExisting)
<div class="flex-1 flex flex-col justify-center items-center text-center">
<p class="text-sm font-semibold text-blue-900 truncate max-w-full">{{ $segAssignment['title'] ?? '' }}</p>
@if($segAssignment['trainer_name'] ?? null)
<p class="text-[10px] text-blue-700 mt-0.5 truncate max-w-full">
<svg class="w-3 h-3 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
{{ $segAssignment['trainer_name'] }}
</p>
@endif
@if($segAssignment['is_recurring'] ?? false)
<span class="inline-flex items-center gap-1 mt-1 text-[10px] text-purple-700">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
{{ __('متكرر') }}
</span>
@endif
</div>
<div class="flex gap-1 justify-center mt-1">
<button wire:click="cancelReservation({{ $segAssignment['reservation_id'] }})"
wire:confirm="{{ __('إلغاء هذا الحجز؟') }}"
class="text-[10px] text-red-600 hover:text-red-800 hover:underline">{{ __('إلغاء') }}</button>
@if($segAssignment['is_recurring'] ?? false)
<button wire:click="cancelSeries({{ $segAssignment['reservation_id'] }})"
wire:confirm="{{ __('إلغاء كل حجوزات هذه السلسلة المستقبلية؟') }}"
class="text-[10px] text-red-500 hover:text-red-700 hover:underline">{{ __('السلسلة') }}</button>
@endif
</div>
@elseif($isNewGroup)
<div class="flex-1 flex flex-col justify-center items-center text-center">
<p class="text-sm font-semibold text-emerald-900 truncate max-w-full">{{ $segAssignment['group_name'] ?? '' }}</p>
@if($segAssignment['implied_trainer'] ?? null)
<p class="text-[10px] text-emerald-700 mt-0.5 truncate max-w-full">{{ $segAssignment['implied_trainer'] }}</p>
@endif
<span class="text-[9px] text-emerald-500 mt-1">{{ __('جديد') }}</span>
</div>
@php $assignIdx = collect($assignments)->search(fn($a) => ($a['type'] ?? '') === 'group' && ($a['group_id'] ?? null) === ($segAssignment['group_id'] ?? -1)); @endphp
@if($assignIdx !== false)
<button wire:click="removeAssignment({{ $assignIdx }})" class="text-[10px] text-red-500 hover:text-red-700 hover:underline mt-1 text-center">{{ __('إزالة') }}</button>
@endif
@elseif($isNewTrainer)
<div class="flex-1 flex flex-col justify-center items-center text-center">
<p class="text-sm font-semibold text-purple-900 truncate max-w-full">{{ $segAssignment['trainer_name'] ?? '' }}</p>
<span class="text-[9px] text-purple-500 mt-1">{{ __('مدرب') }}</span>
</div>
@php $assignIdx = collect($assignments)->search(fn($a) => ($a['type'] ?? '') === 'trainer' && ($a['trainer_id'] ?? null) === ($segAssignment['trainer_id'] ?? -1)); @endphp
@if($assignIdx !== false)
<button wire:click="removeAssignment({{ $assignIdx }})" class="text-[10px] text-red-500 hover:text-red-700 hover:underline mt-1 text-center">{{ __('إزالة') }}</button>
@endif
@elseif(!$seg['available'])
<div class="flex-1 flex items-center justify-center">
<span class="text-xs text-gray-400">{{ __('غير متاح') }}</span>
</div>
@else
<div class="flex-1 flex items-center justify-center">
<span class="text-xs text-gray-400" x-show="!selectedSegments.includes({{ $seg['id'] }})">{{ __('اسحب هنا') }}</span>
<span class="text-xs text-blue-600 font-medium" x-show="selectedSegments.includes({{ $seg['id'] }})">✓ {{ __('محدد') }}</span>
</div>
@endif
</div>
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