Commit 40d21535 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Add visual Space Assignment system for groups to facility segments

Features:
- SpaceLayoutManager: full CRUD for facility layouts (grid/lanes/zones/custom)
  with visual segment preview and toggle availability
- SpaceAssignmentWizard: 5-step wizard for assigning groups to facility segments
  Step 1: Search and select training group
  Step 2: Choose which schedule slot to assign
  Step 3: Pick the facility (if not already linked)
  Step 4: Visual grid/lane/zone selector with real-time collision detection
  Step 5: Success confirmation
- Visual grid renders as clickable cells showing available/selected/occupied/disabled states
- Real-time collision checking against existing confirmed reservations
- Saves space_reservation_template on TrainingSchedule for auto-reservation on session creation
- Fix: ReservationService.autoReserveForSession() now uses correct field names
  (space_reservation_template instead of segments, segment_ids instead of segments key)
- Added "التخطيط" action link in facility list table
- Added "تعيين المساحات" to sidebar navigation
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 4c209476
......@@ -137,7 +137,8 @@ public function cancelForSession(Model $session, User $actor): void
*/
public function autoReserveForSession(\App\Domain\Training\Models\TrainingSession $session, \App\Domain\Training\Models\TrainingSchedule $schedule): void
{
if (!$schedule->facility_id || !$schedule->segments) {
$segmentIds = $schedule->space_reservation_template ?? [];
if (!$schedule->facility_id || empty($segmentIds)) {
return;
}
......@@ -148,7 +149,7 @@ public function autoReserveForSession(\App\Domain\Training\Models\TrainingSessio
'reservation_date' => $session->session_date->toDateString(),
'start_time' => $session->start_time,
'end_time' => $session->end_time,
'segments' => $schedule->segments,
'segment_ids' => $segmentIds,
'status' => 'confirmed',
'notes' => "حجز تلقائي للحصة #{$session->session_number}",
], \App\Models\User::find($session->trainer_id) ?? \App\Models\User::first(), $session);
......
<?php
namespace App\Livewire\Facilities;
use App\Domain\Facility\Models\Facility;
use App\Domain\Facility\Models\SpaceLayout;
use App\Domain\Facility\Models\SpaceReservation;
use App\Domain\Facility\Models\SpaceSegment;
use App\Domain\Facility\Services\SpaceCollisionService;
use App\Domain\Facility\Services\SpaceLayoutService;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingSchedule;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('تعيين المجموعات على الملاعب')]
class SpaceAssignmentWizard extends Component
{
public int $step = 1;
// Step 1: Select Group
public string $groupSearch = '';
public ?int $selectedGroupId = null;
public ?array $selectedGroup = null;
// Step 2: Select Schedule
public ?int $selectedScheduleId = null;
public array $schedules = [];
// Step 3: Select Facility
public ?int $selectedFacilityId = null;
public array $facilities = [];
// Step 4: Visual Grid Selection
public ?int $resolvedLayoutId = null;
public array $segments = [];
public array $selectedSegmentIds = [];
public array $occupiedSegments = [];
public array $conflicts = [];
public array $warnings = [];
// Layout info
public string $layoutType = 'grid';
public int $gridRows = 0;
public int $gridCols = 0;
public function mount(): void
{
$this->authorize('facilities.manage_layouts');
$this->facilities = Facility::where('status', 'active')
->orderBy('name_ar')
->get(['id', 'name', 'name_ar', 'type'])
->toArray();
}
public function updatedGroupSearch(): void
{
// Search is handled in render()
}
public function selectGroup(int $id): void
{
$group = TrainingGroup::with(['program', 'schedules.trainer'])->findOrFail($id);
$this->selectedGroupId = $id;
$this->selectedGroup = [
'id' => $group->id,
'name_ar' => $group->name_ar,
'name' => $group->name,
'program_name' => $group->program?->name_ar ?? '-',
'current_count' => $group->current_count,
'max_capacity' => $group->max_capacity,
];
$this->schedules = $group->schedules
->map(fn ($s) => [
'id' => $s->id,
'day_of_week' => $s->day_of_week,
'day_name' => $s->day_name,
'start_time' => $s->start_time,
'end_time' => $s->end_time,
'trainer_name' => $s->trainer?->name_ar ?? '-',
'facility_id' => $s->facility_id,
'has_space_template' => !empty($s->space_reservation_template),
])
->toArray();
$this->step = 2;
}
public function selectSchedule(int $id): void
{
$this->selectedScheduleId = $id;
$schedule = TrainingSchedule::findOrFail($id);
if ($schedule->facility_id) {
$this->selectedFacilityId = $schedule->facility_id;
$this->selectedSegmentIds = $schedule->space_reservation_template ?? [];
$this->step = 4;
$this->resolveLayout();
} else {
$this->step = 3;
}
}
public function selectFacility(int $id): void
{
$this->selectedFacilityId = $id;
$this->step = 4;
$this->resolveLayout();
}
public function resolveLayout(): void
{
if (!$this->selectedFacilityId || !$this->selectedScheduleId) {
return;
}
$schedule = TrainingSchedule::findOrFail($this->selectedScheduleId);
$layoutService = app(SpaceLayoutService::class);
$today = now()->toDateString();
$layout = $layoutService->resolveLayout(
$this->selectedFacilityId,
$today,
$schedule->start_time
);
if (!$layout) {
$layout = SpaceLayout::where('facility_id', $this->selectedFacilityId)
->where('is_active', true)
->where('is_recurring', true)
->where('effective_day_of_week', $schedule->day_of_week)
->first();
}
if (!$layout) {
$layout = SpaceLayout::where('facility_id', $this->selectedFacilityId)
->where('is_active', true)
->first();
}
if (!$layout) {
$this->segments = [];
$this->resolvedLayoutId = null;
return;
}
$this->resolvedLayoutId = $layout->id;
$this->layoutType = $layout->layout_type->value;
$config = $layout->layout_config;
$this->gridRows = $config['rows'] ?? 0;
$this->gridCols = $config['columns'] ?? 0;
$this->segments = $layout->segments()
->orderBy('sort_order')
->get(['id', 'code', 'name', 'name_ar', 'row_index', 'col_index', 'lane_number', 'is_available', 'capacity'])
->toArray();
$this->loadOccupiedSegments($schedule);
}
private function loadOccupiedSegments(TrainingSchedule $schedule): void
{
$nextDate = $this->getNextOccurrenceDate($schedule->day_of_week);
$reservations = SpaceReservation::where('facility_id', $this->selectedFacilityId)
->where('reservation_date', $nextDate)
->where('status', 'confirmed')
->where('start_time', '<', $schedule->end_time)
->where('end_time', '>', $schedule->start_time)
->get();
$this->occupiedSegments = [];
foreach ($reservations as $res) {
foreach ($res->segment_ids ?? [] as $segId) {
$this->occupiedSegments[$segId] = [
'reservation_id' => $res->id,
'title' => $res->title ?? $res->notes ?? __('محجوز'),
];
}
}
}
private function getNextOccurrenceDate(int $dayOfWeek): string
{
$today = now();
$currentDay = (int) $today->format('w');
$diff = $dayOfWeek - $currentDay;
if ($diff <= 0) {
$diff += 7;
}
return $today->addDays($diff)->toDateString();
}
public function toggleSegment(int $segmentId): void
{
if (isset($this->occupiedSegments[$segmentId])) {
return;
}
$segment = collect($this->segments)->firstWhere('id', $segmentId);
if ($segment && !$segment['is_available']) {
return;
}
if (in_array($segmentId, $this->selectedSegmentIds)) {
$this->selectedSegmentIds = array_values(array_diff($this->selectedSegmentIds, [$segmentId]));
} else {
$this->selectedSegmentIds[] = $segmentId;
}
$this->checkConflicts();
}
public function selectAll(): void
{
$this->selectedSegmentIds = collect($this->segments)
->filter(fn ($s) => $s['is_available'] && !isset($this->occupiedSegments[$s['id']]))
->pluck('id')
->toArray();
$this->checkConflicts();
}
public function clearSelection(): void
{
$this->selectedSegmentIds = [];
$this->conflicts = [];
$this->warnings = [];
}
private function checkConflicts(): void
{
if (empty($this->selectedSegmentIds) || !$this->selectedScheduleId) {
$this->conflicts = [];
$this->warnings = [];
return;
}
$schedule = TrainingSchedule::findOrFail($this->selectedScheduleId);
$nextDate = $this->getNextOccurrenceDate($schedule->day_of_week);
$collisionService = app(SpaceCollisionService::class);
$this->conflicts = $collisionService->check(
$this->selectedFacilityId,
$nextDate,
$schedule->start_time,
$schedule->end_time,
$this->selectedSegmentIds
);
$this->warnings = $collisionService->getTentativeWarnings(
$this->selectedFacilityId,
$nextDate,
$schedule->start_time,
$schedule->end_time,
$this->selectedSegmentIds
);
}
public function saveAssignment(): void
{
if (empty($this->selectedSegmentIds)) {
session()->flash('error', __('يرجى اختيار مساحة واحدة على الأقل'));
return;
}
if (!empty($this->conflicts)) {
session()->flash('error', __('يوجد تعارض مع حجوزات مؤكدة. يرجى إزالة التعارض أولاً'));
return;
}
$schedule = TrainingSchedule::findOrFail($this->selectedScheduleId);
$schedule->update([
'facility_id' => $this->selectedFacilityId,
'space_reservation_template' => $this->selectedSegmentIds,
]);
session()->flash('success', __('تم تعيين المساحة للمجموعة بنجاح! سيتم حجز المساحة تلقائياً عند إنشاء الحصص.'));
$this->step = 5;
}
public function goToStep(int $step): void
{
if ($step < $this->step) {
$this->step = $step;
}
}
public function startOver(): void
{
$this->reset();
$this->mount();
$this->step = 1;
}
public function render()
{
$searchResults = collect();
if (strlen($this->groupSearch) >= 2) {
$searchResults = TrainingGroup::with('program')
->where(function ($q) {
$q->where('name_ar', 'ilike', "%{$this->groupSearch}%")
->orWhere('name', 'ilike', "%{$this->groupSearch}%")
->orWhere('code', 'ilike', "%{$this->groupSearch}%");
})
->where('status', 'active')
->limit(10)
->get();
}
return view('livewire.facilities.space-assignment-wizard', [
'searchResults' => $searchResults,
]);
}
}
<?php
namespace App\Livewire\Facilities;
use App\Domain\Facility\Models\Facility;
use App\Domain\Facility\Models\SpaceLayout;
use App\Domain\Facility\Services\SpaceLayoutService;
use App\Domain\Shared\Exceptions\DomainException;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('إدارة تخطيط الملاعب')]
class SpaceLayoutManager extends Component
{
public Facility $facility;
public array $layouts = [];
// Form state
public bool $showForm = false;
public ?int $editingLayoutId = null;
public string $name = '';
public string $name_ar = '';
public string $layout_type = 'grid';
public bool $is_recurring = true;
public ?int $effective_day_of_week = 6;
public ?string $effective_date = null;
public string $start_time = '08:00';
public string $end_time = '22:00';
// Grid config
public int $grid_rows = 2;
public int $grid_columns = 3;
// Lanes config
public int $lane_count = 4;
// Zones config
public array $zone_definitions = [];
public function mount(Facility $facility): void
{
$this->authorize('facilities.manage_layouts');
$this->facility = $facility;
$this->loadLayouts();
}
public function loadLayouts(): void
{
$this->layouts = $this->facility->layouts()
->with('segments')
->orderBy('is_recurring', 'desc')
->orderBy('effective_day_of_week')
->orderBy('start_time')
->get()
->toArray();
}
public function openCreateForm(): void
{
$this->resetForm();
$this->showForm = true;
}
public function editLayout(int $id): void
{
$layout = SpaceLayout::with('segments')->findOrFail($id);
$this->editingLayoutId = $id;
$this->name = $layout->name;
$this->name_ar = $layout->name_ar;
$this->layout_type = $layout->layout_type->value;
$this->is_recurring = $layout->is_recurring;
$this->effective_day_of_week = $layout->effective_day_of_week;
$this->effective_date = $layout->effective_date?->format('Y-m-d');
$this->start_time = $layout->start_time;
$this->end_time = $layout->end_time;
$config = $layout->layout_config;
match ($this->layout_type) {
'grid' => (function () use ($config) {
$this->grid_rows = $config['rows'] ?? 2;
$this->grid_columns = $config['columns'] ?? 3;
})(),
'lanes' => $this->lane_count = $config['lane_count'] ?? 4,
'zones', 'custom' => $this->zone_definitions = $config['definitions'] ?? [],
default => null,
};
$this->showForm = true;
}
public function save(SpaceLayoutService $service): void
{
$this->validate([
'name' => 'required|string|max:255',
'name_ar' => 'required|string|max:255',
'layout_type' => 'required|in:grid,lanes,zones,custom',
'start_time' => 'required|date_format:H:i',
'end_time' => 'required|date_format:H:i|after:start_time',
]);
$layoutConfig = match ($this->layout_type) {
'grid' => ['rows' => $this->grid_rows, 'columns' => $this->grid_columns],
'lanes' => ['lane_count' => $this->lane_count],
'zones', 'custom' => ['definitions' => $this->zone_definitions],
default => [],
};
$data = [
'academy_id' => $this->facility->academy_id,
'facility_id' => $this->facility->id,
'name' => $this->name,
'name_ar' => $this->name_ar,
'layout_type' => $this->layout_type,
'layout_config' => $layoutConfig,
'is_recurring' => $this->is_recurring,
'effective_day_of_week' => $this->is_recurring ? $this->effective_day_of_week : null,
'effective_date' => !$this->is_recurring ? $this->effective_date : null,
'start_time' => $this->start_time,
'end_time' => $this->end_time,
];
try {
if ($this->editingLayoutId) {
$layout = SpaceLayout::findOrFail($this->editingLayoutId);
$service->update($layout, $data);
session()->flash('success', __('تم تحديث التخطيط بنجاح'));
} else {
$service->create($data, auth()->user());
session()->flash('success', __('تم إنشاء التخطيط بنجاح'));
}
$this->showForm = false;
$this->resetForm();
$this->loadLayouts();
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
public function deleteLayout(int $id): void
{
try {
$layout = SpaceLayout::findOrFail($id);
app(SpaceLayoutService::class)->delete($layout);
session()->flash('success', __('تم حذف التخطيط'));
$this->loadLayouts();
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
public function toggleSegmentAvailability(int $segmentId): void
{
$segment = \App\Domain\Facility\Models\SpaceSegment::findOrFail($segmentId);
$segment->update(['is_available' => !$segment->is_available]);
$this->loadLayouts();
}
public function addZone(): void
{
$index = count($this->zone_definitions) + 1;
$this->zone_definitions[] = [
'code' => 'Z' . $index,
'name' => 'Zone ' . $index,
'name_ar' => 'منطقة ' . $index,
'capacity' => null,
];
}
public function removeZone(int $index): void
{
unset($this->zone_definitions[$index]);
$this->zone_definitions = array_values($this->zone_definitions);
}
private function resetForm(): void
{
$this->editingLayoutId = null;
$this->name = '';
$this->name_ar = '';
$this->layout_type = 'grid';
$this->is_recurring = true;
$this->effective_day_of_week = 6;
$this->effective_date = null;
$this->start_time = '08:00';
$this->end_time = '22:00';
$this->grid_rows = 2;
$this->grid_columns = 3;
$this->lane_count = 4;
$this->zone_definitions = [];
}
public function render()
{
return view('livewire.facilities.space-layout-manager');
}
}
......@@ -47,6 +47,7 @@
['section' => 'المنشآت', 'items' => [
['label' => 'المنشآت', 'route' => 'facilities.list', 'icon' => 'building-office', 'permission' => 'facilities.list'],
['label' => 'تعيين المساحات', 'route' => 'facilities.space-assignment', 'icon' => 'grid', 'permission' => 'facilities.manage_layouts'],
]],
['section' => 'الإشعارات', 'items' => [
......@@ -102,6 +103,7 @@
'cog-6-tooth' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>',
'reception' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>',
'swatch' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.098 19.902a3.75 3.75 0 005.304 0l6.401-6.402M6.75 21A3.75 3.75 0 013 17.25V4.125C3 3.504 3.504 3 4.125 3h5.25c.621 0 1.125.504 1.125 1.125v4.072M6.75 21a3.75 3.75 0 003.75-3.75V8.197M6.75 21h13.125c.621 0 1.125-.504 1.125-1.125v-5.25c0-.621-.504-1.125-1.125-1.125h-4.072M10.5 8.197l2.88-2.88c.438-.439 1.15-.439 1.59 0l3.712 3.713c.44.44.44 1.152 0 1.59l-2.879 2.88M6.75 17.25h.008v.008H6.75v-.008z"/>',
'grid' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z"/>',
];
$permissionService = app(\App\Domain\Identity\Services\PermissionService::class);
......
......@@ -104,6 +104,10 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin
</td>
<td class="px-4 py-3 text-end">
<div class="flex items-center justify-end gap-2">
@permission('facilities.manage_layouts')
<a href="{{ route('facilities.layouts', $facility) }}" wire:navigate
class="text-indigo-600 hover:text-indigo-800 text-sm font-medium">{{ __('التخطيط') }}</a>
@endpermission
@permission('facilities.update')
<a href="{{ route('facilities.edit', $facility) }}" wire:navigate
class="text-blue-600 hover:text-blue-800 text-sm font-medium">{{ __('تعديل') }}</a>
......
<div x-data="{ confirmSave: false }">
<!-- Header with stepper -->
<div class="mb-8">
<h1 class="text-2xl font-bold text-gray-900">{{ __('تعيين المجموعات على الملاعب') }}</h1>
<p class="mt-1 text-sm text-gray-500">{{ __('اختر المجموعة، حدد الجدول، ثم انقر على القطع المطلوبة من الملعب') }}</p>
<!-- Progress Steps -->
<div class="mt-6 flex items-center gap-2 overflow-x-auto pb-2">
@foreach([
1 => 'اختيار المجموعة',
2 => 'اختيار الجدول',
3 => 'اختيار المنشأة',
4 => 'تحديد المساحة',
5 => 'تم التعيين',
] as $num => $label)
<div class="flex items-center gap-2 shrink-0">
<button
wire:click="goToStep({{ $num }})"
@class([
'w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold transition-all',
'bg-blue-600 text-white shadow-md' => $step === $num,
'bg-emerald-500 text-white' => $step > $num,
'bg-gray-200 text-gray-500' => $step < $num,
])
@disabled($step < $num)>
@if($step > $num)
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
@else
{{ $num }}
@endif
</button>
<span @class([
'text-xs font-medium whitespace-nowrap',
'text-blue-700' => $step === $num,
'text-emerald-600' => $step > $num,
'text-gray-400' => $step < $num,
])>{{ __($label) }}</span>
@if($num < 5)
<div @class([
'w-8 h-0.5 rounded',
'bg-emerald-400' => $step > $num,
'bg-gray-200' => $step <= $num,
])></div>
@endif
</div>
@endforeach
</div>
</div>
@if(session('success'))
<div class="mb-4 p-4 bg-emerald-50 border border-emerald-200 text-emerald-700 rounded-xl text-sm flex items-center gap-2">
<svg class="w-5 h-5 shrink-0" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>
{{ session('success') }}
</div>
@endif
@if(session('error'))
<div class="mb-4 p-4 bg-red-50 border border-red-200 text-red-700 rounded-xl text-sm flex items-center gap-2">
<svg class="w-5 h-5 shrink-0" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>
{{ session('error') }}
</div>
@endif
<!-- STEP 1: Select Group -->
@if($step === 1)
<div class="bg-white rounded-2xl border border-gray-200 p-6 shadow-sm">
<div class="max-w-xl mx-auto">
<div class="text-center mb-6">
<div class="w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center mx-auto mb-3">
<svg class="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
</div>
<h2 class="text-lg font-bold text-gray-900">{{ __('اختر المجموعة التدريبية') }}</h2>
<p class="text-sm text-gray-500 mt-1">{{ __('ابحث بالاسم أو الكود') }}</p>
</div>
<div class="relative">
<input type="text" wire:model.live.debounce.300ms="groupSearch"
class="w-full px-4 py-3 pe-10 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
placeholder="{{ __('ابحث عن المجموعة...') }}">
<svg class="absolute top-3.5 end-3 w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
</div>
@if($searchResults->count() > 0)
<div class="mt-3 space-y-2 max-h-80 overflow-y-auto">
@foreach($searchResults as $group)
<button wire:click="selectGroup({{ $group->id }})" type="button"
class="w-full flex items-center gap-4 p-4 rounded-xl border border-gray-200 hover:border-blue-300 hover:bg-blue-50/50 transition-all text-start group">
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 flex items-center justify-center shrink-0">
<span class="text-white font-bold text-sm">{{ mb_substr($group->name_ar, 0, 1) }}</span>
</div>
<div class="flex-1 min-w-0">
<p class="font-semibold text-gray-900 truncate group-hover:text-blue-700 transition-colors">{{ $group->name_ar }}</p>
<p class="text-xs text-gray-500">{{ $group->program?->name_ar ?? '-' }} — {{ $group->code }}</p>
</div>
<div class="text-end shrink-0">
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium {{ $group->status === 'active' ? 'bg-emerald-100 text-emerald-700' : 'bg-gray-100 text-gray-600' }}">
{{ $group->current_count }}/{{ $group->max_capacity }}
</span>
</div>
<svg class="w-5 h-5 text-gray-300 group-hover:text-blue-500 transition-colors shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
</button>
@endforeach
</div>
@elseif(strlen($groupSearch) >= 2)
<div class="mt-4 text-center py-8 text-gray-500 text-sm">
{{ __('لا توجد مجموعات مطابقة') }}
</div>
@endif
</div>
</div>
@endif
<!-- STEP 2: Select Schedule -->
@if($step === 2)
<div class="bg-white rounded-2xl border border-gray-200 p-6 shadow-sm">
<!-- Selected group summary -->
@if($selectedGroup)
<div class="mb-6 p-4 bg-blue-50 rounded-xl border border-blue-100 flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-blue-600 flex items-center justify-center shrink-0">
<span class="text-white font-bold">{{ mb_substr($selectedGroup['name_ar'], 0, 1) }}</span>
</div>
<div>
<p class="font-semibold text-blue-900">{{ $selectedGroup['name_ar'] }}</p>
<p class="text-xs text-blue-700">{{ $selectedGroup['program_name'] }}</p>
</div>
</div>
@endif
<h2 class="text-lg font-bold text-gray-900 mb-4">{{ __('اختر الموعد المراد تعيين مساحة له') }}</h2>
@if(empty($schedules))
<div class="text-center py-12 text-gray-500">
<svg class="mx-auto h-12 w-12 text-gray-300 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
<p class="font-medium">{{ __('لا توجد مواعيد لهذه المجموعة') }}</p>
<p class="text-sm mt-1">{{ __('يجب إنشاء جدول تدريبي أولاً') }}</p>
</div>
@else
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
@foreach($schedules as $schedule)
<button wire:click="selectSchedule({{ $schedule['id'] }})" type="button"
class="p-4 rounded-xl border-2 transition-all text-start group
{{ $schedule['has_space_template'] ? 'border-emerald-200 bg-emerald-50/50 hover:border-emerald-400' : 'border-gray-200 hover:border-blue-300 hover:bg-blue-50/30' }}">
<div class="flex items-center justify-between mb-2">
<span class="text-lg font-bold text-gray-900">{{ $schedule['day_name'] }}</span>
@if($schedule['has_space_template'])
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs bg-emerald-100 text-emerald-700">
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
{{ __('معيّن') }}
</span>
@endif
</div>
<div class="space-y-1 text-sm text-gray-600">
<p dir="ltr" class="text-start font-mono">{{ $schedule['start_time'] }} - {{ $schedule['end_time'] }}</p>
<p>{{ __('المدرب') }}: {{ $schedule['trainer_name'] }}</p>
</div>
</button>
@endforeach
</div>
@endif
</div>
@endif
<!-- STEP 3: Select Facility -->
@if($step === 3)
<div class="bg-white rounded-2xl border border-gray-200 p-6 shadow-sm">
<h2 class="text-lg font-bold text-gray-900 mb-4">{{ __('اختر المنشأة') }}</h2>
@if(empty($facilities))
<div class="text-center py-12 text-gray-500">
<p>{{ __('لا توجد منشآت نشطة') }}</p>
</div>
@else
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
@foreach($facilities as $facility)
<button wire:click="selectFacility({{ $facility['id'] }})" type="button"
class="p-5 rounded-xl border-2 border-gray-200 hover:border-blue-300 hover:bg-blue-50/30 transition-all text-start group">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-emerald-400 to-teal-600 flex items-center justify-center mb-3">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/></svg>
</div>
<p class="font-semibold text-gray-900 group-hover:text-blue-700 transition-colors">{{ $facility['name_ar'] }}</p>
<p class="text-xs text-gray-500 mt-1">{{ $facility['type'] }}</p>
</button>
@endforeach
</div>
@endif
</div>
@endif
<!-- STEP 4: Visual Grid Selection (THE STAR) -->
@if($step === 4)
<div class="space-y-4">
<!-- Context bar -->
<div class="bg-white rounded-xl border border-gray-200 p-4 flex flex-wrap items-center gap-4 text-sm">
@if($selectedGroup)
<div class="flex items-center gap-2">
<span class="w-2 h-2 rounded-full bg-blue-500"></span>
<span class="font-medium text-gray-700">{{ $selectedGroup['name_ar'] }}</span>
</div>
@endif
@php
$currentSchedule = collect($schedules)->firstWhere('id', $selectedScheduleId);
@endphp
@if($currentSchedule)
<div class="flex items-center gap-2 text-gray-500">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
<span>{{ $currentSchedule['day_name'] }} {{ $currentSchedule['start_time'] }}-{{ $currentSchedule['end_time'] }}</span>
</div>
@endif
<div class="flex items-center gap-2 ms-auto">
<span class="text-xs font-medium text-gray-500">{{ __('محدد:') }}</span>
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-blue-100 text-blue-700 text-xs font-bold">{{ count($selectedSegmentIds) }}</span>
</div>
</div>
@if(empty($segments))
<div class="bg-white rounded-2xl border border-gray-200 p-12 text-center">
<svg class="mx-auto h-16 w-16 text-gray-300 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"/></svg>
<p class="font-medium text-gray-700 mb-2">{{ __('لا يوجد تخطيط لهذه المنشأة') }}</p>
<p class="text-sm text-gray-500">{{ __('يجب إنشاء تخطيط (شبكة/حارات/مناطق) أولاً من إعدادات المنشأة') }}</p>
</div>
@else
<div class="grid grid-cols-1 lg:grid-cols-4 gap-4">
<!-- Grid visualization -->
<div class="lg:col-span-3 bg-white rounded-2xl border border-gray-200 p-6 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-gray-900">{{ __('انقر لتحديد القطع') }}</h3>
<div class="flex gap-2">
<button wire:click="selectAll" type="button" class="text-xs px-3 py-1.5 rounded-lg bg-blue-50 text-blue-700 hover:bg-blue-100 font-medium transition-colors">{{ __('تحديد الكل') }}</button>
<button wire:click="clearSelection" type="button" class="text-xs px-3 py-1.5 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 font-medium transition-colors">{{ __('مسح') }}</button>
</div>
</div>
<!-- Grid Layout -->
@if($layoutType === 'grid')
<div class="grid gap-3 select-none"
style="grid-template-columns: repeat({{ $gridCols }}, 1fr); grid-template-rows: repeat({{ $gridRows }}, 1fr);">
@foreach($segments as $segment)
@php
$isSelected = in_array($segment['id'], $selectedSegmentIds);
$isOccupied = isset($occupiedSegments[$segment['id']]);
$isUnavailable = !$segment['is_available'];
@endphp
<button wire:click="toggleSegment({{ $segment['id'] }})" type="button"
@class([
'relative aspect-square rounded-xl border-2 flex flex-col items-center justify-center transition-all duration-200 p-2',
'border-blue-500 bg-blue-500 text-white shadow-lg shadow-blue-200 scale-[1.02]' => $isSelected,
'border-red-300 bg-red-50 text-red-400 cursor-not-allowed opacity-75' => $isOccupied,
'border-gray-300 bg-gray-100 text-gray-400 cursor-not-allowed' => $isUnavailable && !$isOccupied,
'border-gray-200 bg-white text-gray-700 hover:border-blue-300 hover:bg-blue-50 hover:shadow-md cursor-pointer' => !$isSelected && !$isOccupied && !$isUnavailable,
])
@disabled($isOccupied || $isUnavailable)
title="{{ $isOccupied ? ($occupiedSegments[$segment['id']]['title'] ?? __('محجوز')) : $segment['name_ar'] }}">
@if($isSelected)
<svg class="absolute top-1.5 end-1.5 w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
@endif
@if($isOccupied)
<svg class="w-5 h-5 mb-1" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"/></svg>
@endif
<span class="text-xs font-bold">{{ $segment['code'] }}</span>
<span class="text-[10px] mt-0.5 opacity-75">{{ $segment['name_ar'] }}</span>
@if($segment['capacity'])
<span class="text-[9px] mt-0.5 opacity-60">{{ $segment['capacity'] }} {{ __('فرد') }}</span>
@endif
</button>
@endforeach
</div>
<!-- Lanes Layout -->
@elseif($layoutType === 'lanes')
<div class="space-y-3 select-none">
@foreach($segments as $segment)
@php
$isSelected = in_array($segment['id'], $selectedSegmentIds);
$isOccupied = isset($occupiedSegments[$segment['id']]);
$isUnavailable = !$segment['is_available'];
@endphp
<button wire:click="toggleSegment({{ $segment['id'] }})" type="button"
@class([
'w-full flex items-center gap-4 p-4 rounded-xl border-2 transition-all duration-200',
'border-blue-500 bg-blue-50 shadow-md' => $isSelected,
'border-red-300 bg-red-50 cursor-not-allowed opacity-75' => $isOccupied,
'border-gray-300 bg-gray-100 cursor-not-allowed' => $isUnavailable && !$isOccupied,
'border-gray-200 hover:border-blue-300 hover:bg-blue-50/50 cursor-pointer' => !$isSelected && !$isOccupied && !$isUnavailable,
])
@disabled($isOccupied || $isUnavailable)>
<div @class([
'w-10 h-10 rounded-lg flex items-center justify-center font-bold text-sm',
'bg-blue-600 text-white' => $isSelected,
'bg-red-200 text-red-600' => $isOccupied,
'bg-gray-200 text-gray-500' => !$isSelected && !$isOccupied,
])>{{ $segment['lane_number'] ?? $segment['code'] }}</div>
<div class="flex-1 text-start">
<p class="font-medium text-gray-900">{{ $segment['name_ar'] }}</p>
</div>
@if($isSelected)
<svg class="w-6 h-6 text-blue-600" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>
@endif
</button>
@endforeach
</div>
<!-- Zones/Custom Layout -->
@else
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3 select-none">
@foreach($segments as $segment)
@php
$isSelected = in_array($segment['id'], $selectedSegmentIds);
$isOccupied = isset($occupiedSegments[$segment['id']]);
$isUnavailable = !$segment['is_available'];
@endphp
<button wire:click="toggleSegment({{ $segment['id'] }})" type="button"
@class([
'p-4 rounded-xl border-2 transition-all duration-200 text-center',
'border-blue-500 bg-blue-50 shadow-md' => $isSelected,
'border-red-300 bg-red-50 cursor-not-allowed opacity-75' => $isOccupied,
'border-gray-300 bg-gray-100 cursor-not-allowed' => $isUnavailable && !$isOccupied,
'border-gray-200 hover:border-blue-300 hover:bg-blue-50/50 cursor-pointer' => !$isSelected && !$isOccupied && !$isUnavailable,
])
@disabled($isOccupied || $isUnavailable)>
<p class="font-bold text-sm">{{ $segment['code'] }}</p>
<p class="text-xs mt-1 text-gray-600">{{ $segment['name_ar'] }}</p>
</button>
@endforeach
</div>
@endif
<!-- Legend -->
<div class="mt-6 flex flex-wrap gap-4 pt-4 border-t border-gray-100">
<div class="flex items-center gap-2 text-xs text-gray-600">
<div class="w-4 h-4 rounded border-2 border-gray-200 bg-white"></div>
{{ __('متاح') }}
</div>
<div class="flex items-center gap-2 text-xs text-gray-600">
<div class="w-4 h-4 rounded border-2 border-blue-500 bg-blue-500"></div>
{{ __('محدد') }}
</div>
<div class="flex items-center gap-2 text-xs text-gray-600">
<div class="w-4 h-4 rounded border-2 border-red-300 bg-red-50"></div>
{{ __('محجوز') }}
</div>
<div class="flex items-center gap-2 text-xs text-gray-600">
<div class="w-4 h-4 rounded border-2 border-gray-300 bg-gray-100"></div>
{{ __('غير متاح') }}
</div>
</div>
</div>
<!-- Side Panel: Selection summary + conflicts -->
<div class="space-y-4">
<!-- Selection Summary -->
<div class="bg-white rounded-xl border border-gray-200 p-4">
<h4 class="font-semibold text-gray-900 mb-3">{{ __('ملخص التحديد') }}</h4>
@if(empty($selectedSegmentIds))
<p class="text-sm text-gray-500">{{ __('لم يتم تحديد أي قطعة بعد') }}</p>
@else
<div class="space-y-2">
@foreach($segments as $segment)
@if(in_array($segment['id'], $selectedSegmentIds))
<div class="flex items-center gap-2 text-sm">
<div class="w-2 h-2 rounded-full bg-blue-500"></div>
<span class="font-medium">{{ $segment['code'] }}</span>
<span class="text-gray-500">{{ $segment['name_ar'] }}</span>
</div>
@endif
@endforeach
</div>
<div class="mt-3 pt-3 border-t border-gray-100 text-xs text-gray-500">
{{ count($selectedSegmentIds) }} {{ __('قطعة محددة') }}
@php $totalCapacity = collect($segments)->whereIn('id', $selectedSegmentIds)->sum('capacity'); @endphp
@if($totalCapacity)
— {{ __('سعة') }}: {{ $totalCapacity }}
@endif
</div>
@endif
</div>
<!-- Conflicts -->
@if(!empty($conflicts))
<div class="bg-red-50 rounded-xl border border-red-200 p-4">
<h4 class="font-semibold text-red-800 mb-2 flex items-center gap-2">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>
{{ __('تعارضات') }}
</h4>
@foreach($conflicts as $conflict)
<div class="text-sm text-red-700 mb-1">
<span class="font-medium">{{ $conflict['title'] ?? __('حجز') }}</span>
<span class="text-xs">({{ $conflict['time'] }})</span>
</div>
@endforeach
</div>
@endif
@if(!empty($warnings))
<div class="bg-amber-50 rounded-xl border border-amber-200 p-4">
<h4 class="font-semibold text-amber-800 mb-2">{{ __('حجوزات مبدئية') }}</h4>
@foreach($warnings as $warning)
<div class="text-sm text-amber-700 mb-1">
{{ $warning['title'] ?? __('حجز مبدئي') }}
</div>
@endforeach
</div>
@endif
<!-- Save Button -->
<button
@click="confirmSave = true"
type="button"
@class([
'w-full py-3.5 rounded-xl font-bold text-sm transition-all',
'bg-blue-600 text-white hover:bg-blue-700 shadow-lg shadow-blue-200' => !empty($selectedSegmentIds) && empty($conflicts),
'bg-gray-200 text-gray-400 cursor-not-allowed' => empty($selectedSegmentIds) || !empty($conflicts),
])
@disabled(empty($selectedSegmentIds) || !empty($conflicts))>
{{ __('حفظ التعيين') }}
</button>
</div>
</div>
@endif
</div>
<!-- Confirmation Modal -->
<div x-show="confirmSave" x-transition class="fixed inset-0 z-50 flex items-center justify-center p-4" style="display: none;">
<div class="fixed inset-0 bg-black/50" @click="confirmSave = false"></div>
<div class="relative bg-white rounded-2xl shadow-2xl p-6 max-w-md w-full">
<h3 class="text-lg font-bold text-gray-900 mb-2">{{ __('تأكيد التعيين') }}</h3>
<p class="text-sm text-gray-600 mb-6">{{ __('سيتم حفظ هذا التعيين وسيُستخدم تلقائياً عند إنشاء حصص جديدة لهذا الموعد. هل تريد المتابعة؟') }}</p>
<div class="flex gap-3">
<button wire:click="saveAssignment" @click="confirmSave = false" type="button"
class="flex-1 py-2.5 bg-blue-600 text-white font-medium rounded-xl hover:bg-blue-700 transition-colors">
<span wire:loading.remove wire:target="saveAssignment">{{ __('نعم، حفظ') }}</span>
<span wire:loading wire:target="saveAssignment">{{ __('جارٍ الحفظ...') }}</span>
</button>
<button @click="confirmSave = false" type="button"
class="flex-1 py-2.5 bg-gray-100 text-gray-700 font-medium rounded-xl hover:bg-gray-200 transition-colors">
{{ __('إلغاء') }}
</button>
</div>
</div>
</div>
@endif
<!-- STEP 5: Success -->
@if($step === 5)
<div class="bg-white rounded-2xl border border-gray-200 p-12 text-center shadow-sm">
<div class="w-20 h-20 bg-emerald-100 rounded-full flex items-center justify-center mx-auto mb-4">
<svg class="w-10 h-10 text-emerald-600" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
</div>
<h2 class="text-xl font-bold text-gray-900 mb-2">{{ __('تم التعيين بنجاح!') }}</h2>
<p class="text-gray-600 mb-8">{{ __('المساحة المحددة ستُحجز تلقائياً عند إنشاء حصص جديدة لهذا الموعد.') }}</p>
<div class="flex flex-col sm:flex-row gap-3 justify-center">
<button wire:click="startOver" type="button" class="px-6 py-3 bg-blue-600 text-white font-medium rounded-xl hover:bg-blue-700 transition-colors">
{{ __('تعيين مجموعة أخرى') }}
</button>
<a href="{{ route('facilities.list') }}" class="px-6 py-3 bg-gray-100 text-gray-700 font-medium rounded-xl hover:bg-gray-200 transition-colors">
{{ __('العودة للمنشآت') }}
</a>
</div>
</div>
@endif
</div>
<div>
<!-- Header -->
<div class="mb-6 flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-gray-900">{{ __('تخطيط المنشأة') }}: {{ $facility->name_ar }}</h1>
<p class="mt-1 text-sm text-gray-500">{{ __('إدارة تقسيمات الملعب (شبكة، حارات، مناطق)') }}</p>
</div>
<button wire:click="openCreateForm" type="button"
class="px-4 py-2.5 bg-blue-600 text-white font-medium rounded-xl hover:bg-blue-700 transition-colors flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
{{ __('تخطيط جديد') }}
</button>
</div>
@if(session('success'))
<div class="mb-4 p-3 bg-emerald-50 border border-emerald-200 text-emerald-700 rounded-xl text-sm">{{ session('success') }}</div>
@endif
@if(session('error'))
<div class="mb-4 p-3 bg-red-50 border border-red-200 text-red-700 rounded-xl text-sm">{{ session('error') }}</div>
@endif
<!-- Create/Edit Form -->
@if($showForm)
<div class="mb-6 bg-white rounded-2xl border border-gray-200 p-6 shadow-sm">
<h3 class="text-lg font-bold text-gray-900 mb-4">{{ $editingLayoutId ? __('تعديل التخطيط') : __('تخطيط جديد') }}</h3>
<form wire:submit="save" class="space-y-5">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الاسم بالعربية') }} *</label>
<input type="text" wire:model="name_ar" class="w-full border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="{{ __('مثال: تقسيم صباحي') }}">
@error('name_ar') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الاسم بالإنجليزية') }} *</label>
<input type="text" wire:model="name" dir="ltr" class="w-full border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Morning layout">
@error('name') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
</div>
<!-- Layout Type -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __('نوع التخطيط') }}</label>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
@foreach([
'grid' => ['شبكة', 'M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z'],
'lanes' => ['حارات', 'M4 6h16M4 10h16M4 14h16M4 18h16'],
'zones' => ['مناطق', 'M9 3v18m12-13H9m12 8H9'],
'custom' => ['مخصص', 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35'],
] as $type => [$label, $icon])
<label class="cursor-pointer">
<input type="radio" wire:model.live="layout_type" value="{{ $type }}" class="sr-only peer">
<div class="p-3 rounded-xl border-2 text-center transition-all peer-checked:border-blue-500 peer-checked:bg-blue-50 border-gray-200 hover:border-gray-300">
<svg class="w-6 h-6 mx-auto text-gray-600 peer-checked:text-blue-600 mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="{{ $icon }}"/></svg>
<span class="text-xs font-medium">{{ __($label) }}</span>
</div>
</label>
@endforeach
</div>
</div>
<!-- Config based on type -->
@if($layout_type === 'grid')
<div class="grid grid-cols-2 gap-4 p-4 bg-gray-50 rounded-xl">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('عدد الصفوف') }}</label>
<input type="number" wire:model="grid_rows" min="1" max="10" dir="ltr" class="w-full border-gray-300 rounded-lg">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('عدد الأعمدة') }}</label>
<input type="number" wire:model="grid_columns" min="1" max="10" dir="ltr" class="w-full border-gray-300 rounded-lg">
</div>
<p class="col-span-2 text-xs text-gray-500">{{ __('سيتم إنشاء') }} {{ $grid_rows * $grid_columns }} {{ __('قطعة') }}</p>
</div>
@elseif($layout_type === 'lanes')
<div class="p-4 bg-gray-50 rounded-xl">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('عدد الحارات') }}</label>
<input type="number" wire:model="lane_count" min="1" max="20" dir="ltr" class="w-32 border-gray-300 rounded-lg">
</div>
@elseif($layout_type === 'zones' || $layout_type === 'custom')
<div class="p-4 bg-gray-50 rounded-xl space-y-3">
<div class="flex items-center justify-between">
<label class="text-sm font-medium text-gray-700">{{ __('المناطق') }}</label>
<button wire:click="addZone" type="button" class="text-xs px-3 py-1 bg-blue-100 text-blue-700 rounded-lg hover:bg-blue-200">+ {{ __('إضافة منطقة') }}</button>
</div>
@foreach($zone_definitions as $index => $zone)
<div class="flex items-center gap-2 p-2 bg-white rounded-lg border">
<input type="text" wire:model="zone_definitions.{{ $index }}.code" dir="ltr" placeholder="Z1" class="w-16 text-xs border-gray-300 rounded">
<input type="text" wire:model="zone_definitions.{{ $index }}.name_ar" placeholder="{{ __('اسم المنطقة') }}" class="flex-1 text-sm border-gray-300 rounded">
<input type="number" wire:model="zone_definitions.{{ $index }}.capacity" placeholder="{{ __('السعة') }}" dir="ltr" class="w-20 text-xs border-gray-300 rounded">
<button wire:click="removeZone({{ $index }})" type="button" class="text-red-500 hover:text-red-700 p-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</div>
@endforeach
</div>
@endif
<!-- Time & Scheduling -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('من الساعة') }}</label>
<input type="time" wire:model="start_time" dir="ltr" class="w-full border-gray-300 rounded-lg">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('إلى الساعة') }}</label>
<input type="time" wire:model="end_time" dir="ltr" class="w-full border-gray-300 rounded-lg">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نوع الجدولة') }}</label>
<select wire:model.live="is_recurring" class="w-full border-gray-300 rounded-lg">
<option value="1">{{ __('متكرر (يوم محدد)') }}</option>
<option value="0">{{ __('تاريخ محدد') }}</option>
</select>
</div>
</div>
@if($is_recurring)
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('يوم الأسبوع') }}</label>
<select wire:model="effective_day_of_week" class="w-full max-w-xs border-gray-300 rounded-lg">
@foreach(['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'] as $i => $day)
<option value="{{ $i }}">{{ $day }}</option>
@endforeach
</select>
</div>
@else
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('التاريخ') }}</label>
<input type="date" wire:model="effective_date" dir="ltr" class="w-full max-w-xs border-gray-300 rounded-lg">
</div>
@endif
<div class="flex gap-3 pt-2">
<button type="submit" wire:loading.attr="disabled" wire:target="save"
class="px-6 py-2.5 bg-blue-600 text-white font-medium rounded-xl hover:bg-blue-700 disabled:bg-blue-400 transition-colors">
<span wire:loading.remove wire:target="save">{{ $editingLayoutId ? __('تحديث') : __('إنشاء') }}</span>
<span wire:loading wire:target="save">{{ __('جارٍ الحفظ...') }}</span>
</button>
<button wire:click="$set('showForm', false)" type="button"
class="px-6 py-2.5 bg-gray-100 text-gray-700 font-medium rounded-xl hover:bg-gray-200 transition-colors">
{{ __('إلغاء') }}
</button>
</div>
</form>
</div>
@endif
<!-- Existing Layouts -->
@if(empty($layouts) && !$showForm)
<div class="bg-white rounded-2xl border border-gray-200 p-12 text-center">
<svg class="mx-auto h-16 w-16 text-gray-300 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"/></svg>
<p class="font-medium text-gray-700 mb-2">{{ __('لا توجد تخطيطات بعد') }}</p>
<p class="text-sm text-gray-500 mb-4">{{ __('أنشئ أول تخطيط لهذه المنشأة لبدء تعيين المجموعات') }}</p>
<button wire:click="openCreateForm" type="button" class="px-6 py-2.5 bg-blue-600 text-white font-medium rounded-xl hover:bg-blue-700 transition-colors">
{{ __('إنشاء تخطيط') }}
</button>
</div>
@else
<div class="space-y-4">
@foreach($layouts as $layout)
<div class="bg-white rounded-xl border border-gray-200 overflow-hidden">
<!-- Layout header -->
<div class="p-4 flex items-center justify-between border-b border-gray-100">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-indigo-100 flex items-center justify-center">
@if($layout['layout_type'] === 'grid')
<svg class="w-5 h-5 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"/></svg>
@else
<svg class="w-5 h-5 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"/></svg>
@endif
</div>
<div>
<h4 class="font-semibold text-gray-900">{{ $layout['name_ar'] }}</h4>
<div class="flex items-center gap-2 text-xs text-gray-500">
<span class="px-1.5 py-0.5 bg-gray-100 rounded">{{ $layout['layout_type'] }}</span>
<span>{{ $layout['start_time'] }} - {{ $layout['end_time'] }}</span>
@if($layout['is_recurring'])
@php $days = ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت']; @endphp
<span>— {{ $days[$layout['effective_day_of_week']] ?? '' }}</span>
@else
<span>— {{ $layout['effective_date'] }}</span>
@endif
</div>
</div>
</div>
<div class="flex items-center gap-2">
<button wire:click="editLayout({{ $layout['id'] }})" class="p-2 text-gray-400 hover:text-blue-600 rounded-lg hover:bg-blue-50 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/></svg>
</button>
<button wire:click="deleteLayout({{ $layout['id'] }})" wire:confirm="{{ __('هل أنت متأكد من حذف هذا التخطيط؟ سيتم حذف جميع القطع المرتبطة.') }}"
class="p-2 text-gray-400 hover:text-red-600 rounded-lg hover:bg-red-50 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</button>
</div>
</div>
<!-- Visual segments preview -->
@if(!empty($layout['segments']))
<div class="p-4">
@if($layout['layout_type'] === 'grid')
@php
$config = $layout['layout_config'] ?? [];
$cols = $config['columns'] ?? 3;
@endphp
<div class="grid gap-2" style="grid-template-columns: repeat({{ $cols }}, 1fr);">
@foreach($layout['segments'] as $seg)
<button wire:click="toggleSegmentAvailability({{ $seg['id'] }})" type="button"
@class([
'p-2 rounded-lg text-center text-xs font-medium border transition-colors',
'bg-white border-gray-200 text-gray-700 hover:border-blue-300' => $seg['is_available'],
'bg-gray-100 border-gray-300 text-gray-400 line-through' => !$seg['is_available'],
])
title="{{ $seg['is_available'] ? __('انقر لتعطيل') : __('انقر لتفعيل') }}">
{{ $seg['code'] }}
</button>
@endforeach
</div>
@else
<div class="flex flex-wrap gap-2">
@foreach($layout['segments'] as $seg)
<button wire:click="toggleSegmentAvailability({{ $seg['id'] }})" type="button"
@class([
'px-3 py-1.5 rounded-lg text-xs font-medium border transition-colors',
'bg-white border-gray-200 text-gray-700 hover:border-blue-300' => $seg['is_available'],
'bg-gray-100 border-gray-300 text-gray-400 line-through' => !$seg['is_available'],
])>
{{ $seg['code'] }} — {{ $seg['name_ar'] }}
</button>
@endforeach
</div>
@endif
<p class="mt-2 text-xs text-gray-400">{{ __('انقر على القطعة لتفعيلها/تعطيلها') }}</p>
</div>
@endif
</div>
@endforeach
</div>
@endif
</div>
......@@ -193,6 +193,10 @@
->middleware('permission:facilities.create');
Route::get('/facilities/{facility}/edit', FacilityForm::class)->name('facilities.edit')
->middleware('permission:facilities.update');
Route::get('/facilities/{facility}/layouts', \App\Livewire\Facilities\SpaceLayoutManager::class)->name('facilities.layouts')
->middleware('permission:facilities.manage_layouts');
Route::get('/facilities/space-assignment', \App\Livewire\Facilities\SpaceAssignmentWizard::class)->name('facilities.space-assignment')
->middleware('permission:facilities.manage_layouts');
// Attendance
Route::get('/attendance', AttendanceList::class)->name('attendance.list')
......
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