Commit eea6ad2e authored by Mahmoud Aglan's avatar Mahmoud Aglan

Split attendance page: trainers on top, players below with role-based access

Trainers section (purple header) shows first with max 2-3 records.
Players section (blue header) shows below with full table.

Access control:
- Trainer account: can only mark their own attendance (others disabled)
- Supervisor/head_trainer (role level >= 60): can mark all trainers
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 04dcc7e4
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
use App\Domain\Participant\Models\Participant; use App\Domain\Participant\Models\Participant;
use App\Domain\Training\Models\Enrollment; use App\Domain\Training\Models\Enrollment;
use App\Domain\Training\Models\TrainingSession; use App\Domain\Training\Models\TrainingSession;
use App\Models\User;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Component; use Livewire\Component;
...@@ -55,6 +56,11 @@ public function saveRecordNote(int $recordId): void ...@@ -55,6 +56,11 @@ public function saveRecordNote(int $recordId): void
public function markAs(int $recordId, string $status, AttendanceMarkingService $service): void public function markAs(int $recordId, string $status, AttendanceMarkingService $service): void
{ {
$record = AttendanceRecord::findOrFail($recordId); $record = AttendanceRecord::findOrFail($recordId);
if (!$this->canMarkRecord($record)) {
return;
}
$statusEnum = AttendanceStatus::from($status); $statusEnum = AttendanceStatus::from($status);
$service->markStatus($record, $statusEnum, auth()->user()); $service->markStatus($record, $statusEnum, auth()->user());
} }
...@@ -62,6 +68,11 @@ public function markAs(int $recordId, string $status, AttendanceMarkingService $ ...@@ -62,6 +68,11 @@ public function markAs(int $recordId, string $status, AttendanceMarkingService $
public function markPresent(int $recordId, AttendanceMarkingService $service): void public function markPresent(int $recordId, AttendanceMarkingService $service): void
{ {
$record = AttendanceRecord::findOrFail($recordId); $record = AttendanceRecord::findOrFail($recordId);
if (!$this->canMarkRecord($record)) {
return;
}
$service->markPresent($record, auth()->user()); $service->markPresent($record, auth()->user());
} }
...@@ -72,10 +83,43 @@ public function markAllPresent(AttendanceMarkingService $service): void ...@@ -72,10 +83,43 @@ public function markAllPresent(AttendanceMarkingService $service): void
->get(); ->get();
foreach ($records as $record) { foreach ($records as $record) {
$service->markPresent($record, auth()->user()); if ($this->canMarkRecord($record)) {
$service->markPresent($record, auth()->user());
}
} }
} }
private function canMarkRecord(AttendanceRecord $record): bool
{
$user = auth()->user();
if ($record->subject_type === Participant::class) {
return true;
}
if ($record->subject_type === User::class) {
if ($this->canManageTrainers()) {
return true;
}
return $record->subject_id === $user->id;
}
return true;
}
private function canManageTrainers(): bool
{
$user = auth()->user();
if ($user->is_super_admin) {
return true;
}
$roleLevel = $user->roles->max('level') ?? 0;
return $roleLevel >= 60;
}
public function render() public function render()
{ {
$records = AttendanceRecord::where('training_session_id', $this->session->id) $records = AttendanceRecord::where('training_session_id', $this->session->id)
...@@ -84,6 +128,10 @@ public function render() ...@@ -84,6 +128,10 @@ public function render()
->orderBy('status') ->orderBy('status')
->get(); ->get();
// Split into trainers and participants
$trainerRecords = $records->where('subject_type', User::class)->values();
$participantRecords = $records->where('subject_type', Participant::class)->values();
// Populate per-record notes from DB (only on first load or if not yet set) // Populate per-record notes from DB (only on first load or if not yet set)
foreach ($records as $record) { foreach ($records as $record) {
if (!array_key_exists($record->id, $this->recordNotes)) { if (!array_key_exists($record->id, $this->recordNotes)) {
...@@ -92,8 +140,7 @@ public function render() ...@@ -92,8 +140,7 @@ public function render()
} }
// Detect unpaid/unrenewed participants for red highlight // Detect unpaid/unrenewed participants for red highlight
$participantIds = $records $participantIds = $participantRecords
->where('subject_type', Participant::class)
->pluck('subject_id') ->pluck('subject_id')
->unique() ->unique()
->toArray(); ->toArray();
...@@ -130,11 +177,17 @@ public function render() ...@@ -130,11 +177,17 @@ public function render()
'excused' => $records->where('status', AttendanceStatus::Excused)->count(), 'excused' => $records->where('status', AttendanceStatus::Excused)->count(),
]; ];
$canManageTrainers = $this->canManageTrainers();
return view('livewire.attendance.take-attendance', [ return view('livewire.attendance.take-attendance', [
'trainerRecords' => $trainerRecords,
'participantRecords' => $participantRecords,
'records' => $records, 'records' => $records,
'summary' => $summary, 'summary' => $summary,
'statuses' => AttendanceStatus::cases(), 'statuses' => AttendanceStatus::cases(),
'notPaidParticipantIds' => $notPaidParticipantIds, 'notPaidParticipantIds' => $notPaidParticipantIds,
'canManageTrainers' => $canManageTrainers,
'currentUserId' => auth()->id(),
]); ]);
} }
} }
...@@ -91,28 +91,33 @@ class="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-medium text-white ...@@ -91,28 +91,33 @@ class="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-medium text-white
</div> </div>
@endif @endif
<!-- Attendance Records --> <div wire:loading.class="opacity-50 pointer-events-none" wire:target="markAs, markPresent, markAllPresent, saveRecordNote">
<div class="bg-white border border-gray-200 rounded-lg overflow-hidden" wire:loading.class="opacity-50 pointer-events-none" wire:target="markAs, markPresent, markAllPresent, saveRecordNote">
@if($records->isEmpty()) {{-- ========== TRAINERS SECTION ========== --}}
<div class="p-12 text-center"> @if($trainerRecords->isNotEmpty())
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg> <div class="mb-6">
<p class="mt-4 text-sm text-gray-500">{{ __('لا توجد سجلات حضور لهذه الجلسة') }}</p> <div class="flex items-center gap-2 mb-3">
<div class="w-8 h-8 bg-purple-100 rounded-lg flex items-center justify-center">
<svg class="w-4 h-4 text-purple-600" 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>
</div> </div>
@else <h2 class="text-base font-bold text-gray-800">{{ __('المدربون') }}</h2>
<span class="text-xs text-gray-500">({{ $trainerRecords->count() }})</span>
</div>
<div class="bg-white border border-purple-200 rounded-lg overflow-hidden">
{{-- Mobile cards --}} {{-- Mobile cards --}}
<div class="md:hidden space-y-2 p-3"> <div class="md:hidden space-y-2 p-3">
@foreach($records as $record) @foreach($trainerRecords as $record)
@php $isUnpaid = $record->subject_type === \App\Domain\Participant\Models\Participant::class && in_array($record->subject_id, $notPaidParticipantIds); @endphp @php
<div class="{{ $isUnpaid ? 'bg-red-50 border-red-300' : 'bg-white border-gray-200' }} border rounded-lg p-3"> $isOwnRecord = $record->subject_id === $currentUserId;
$canMark = $canManageTrainers || $isOwnRecord;
@endphp
<div class="border {{ $isOwnRecord ? 'border-purple-300 bg-purple-50' : 'border-gray-200 bg-white' }} rounded-lg p-3 {{ !$canMark ? 'opacity-50' : '' }}">
<div class="flex items-center justify-between mb-2"> <div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium {{ $isUnpaid ? 'text-red-700' : 'text-gray-900' }}"> <span class="text-sm font-medium text-gray-900">
@if($record->subject_type === \App\Domain\Participant\Models\Participant::class) {{ $record->subject?->name ?? '-' }}
{{ $record->subject?->person?->name_ar ?? '-' }} @if($isOwnRecord)
@if($isUnpaid) <span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-bold bg-purple-600 text-white ms-1">{{ __('أنت') }}</span>
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-bold bg-red-600 text-white ms-1">{{ __('غير مدفوع') }}</span>
@endif
@else
{{ $record->subject?->name_ar ?? $record->subject?->name ?? '-' }}
@endif @endif
</span> </span>
@php $color = $record->status->color(); @endphp @php $color = $record->status->color(); @endphp
...@@ -122,9 +127,6 @@ class="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-medium text-white ...@@ -122,9 +127,6 @@ class="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-medium text-white
@case('amber') bg-amber-100 text-amber-800 @break @case('amber') bg-amber-100 text-amber-800 @break
@case('red') bg-red-100 text-red-800 @break @case('red') bg-red-100 text-red-800 @break
@case('blue') bg-blue-100 text-blue-800 @break @case('blue') bg-blue-100 text-blue-800 @break
@case('orange') bg-orange-100 text-orange-800 @break
@case('yellow') bg-yellow-100 text-yellow-800 @break
@case('purple') bg-purple-100 text-purple-800 @break
@default bg-gray-100 text-gray-800 @default bg-gray-100 text-gray-800
@endswitch @endswitch
"> ">
...@@ -132,30 +134,15 @@ class="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-medium text-white ...@@ -132,30 +134,15 @@ class="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-medium text-white
</span> </span>
</div> </div>
<div class="grid grid-cols-4 gap-1.5"> <div class="grid grid-cols-4 gap-1.5">
<button wire:click="markAs({{ $record->id }}, 'present')" wire:loading.attr="disabled" wire:target="markAs({{ $record->id }}, 'present')" @foreach(['present' => ['حاضر', 'green'], 'late' => ['متأخر', 'amber'], 'absent' => ['غائب', 'red'], 'excused' => ['معذور', 'blue']] as $st => [$label, $clr])
<button wire:click="markAs({{ $record->id }}, '{{ $st }}')" wire:loading.attr="disabled"
@if(!$canMark) disabled @endif
class="py-2.5 text-xs font-medium rounded border text-center class="py-2.5 text-xs font-medium rounded border text-center
{{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Present ? 'bg-green-600 text-white border-green-600' : 'text-green-700 border-green-300 hover:bg-green-50' }} disabled:opacity-50"> {{ $record->status->value === $st ? "bg-{$clr}-600 text-white border-{$clr}-600" : "text-{$clr}-700 border-{$clr}-300 hover:bg-{$clr}-50" }}
<span wire:loading.remove wire:target="markAs({{ $record->id }}, 'present')">{{ __('حاضر') }}</span> disabled:opacity-50 disabled:cursor-not-allowed">
<span wire:loading wire:target="markAs({{ $record->id }}, 'present')">...</span> {{ __($label) }}
</button>
<button wire:click="markAs({{ $record->id }}, 'late')" wire:loading.attr="disabled" wire:target="markAs({{ $record->id }}, 'late')"
class="py-2.5 text-xs font-medium rounded border text-center
{{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Late ? 'bg-amber-600 text-white border-amber-600' : 'text-amber-700 border-amber-300 hover:bg-amber-50' }} disabled:opacity-50">
<span wire:loading.remove wire:target="markAs({{ $record->id }}, 'late')">{{ __('متأخر') }}</span>
<span wire:loading wire:target="markAs({{ $record->id }}, 'late')">...</span>
</button>
<button wire:click="markAs({{ $record->id }}, 'absent')" wire:loading.attr="disabled" wire:target="markAs({{ $record->id }}, 'absent')"
class="py-2.5 text-xs font-medium rounded border text-center
{{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Absent ? 'bg-red-600 text-white border-red-600' : 'text-red-700 border-red-300 hover:bg-red-50' }} disabled:opacity-50">
<span wire:loading.remove wire:target="markAs({{ $record->id }}, 'absent')">{{ __('غائب') }}</span>
<span wire:loading wire:target="markAs({{ $record->id }}, 'absent')">...</span>
</button>
<button wire:click="markAs({{ $record->id }}, 'excused')" wire:loading.attr="disabled" wire:target="markAs({{ $record->id }}, 'excused')"
class="py-2.5 text-xs font-medium rounded border text-center
{{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Excused ? 'bg-blue-600 text-white border-blue-600' : 'text-blue-700 border-blue-300 hover:bg-blue-50' }} disabled:opacity-50">
<span wire:loading.remove wire:target="markAs({{ $record->id }}, 'excused')">{{ __('معذور') }}</span>
<span wire:loading wire:target="markAs({{ $record->id }}, 'excused')">...</span>
</button> </button>
@endforeach
</div> </div>
</div> </div>
@endforeach @endforeach
...@@ -164,180 +151,301 @@ class="py-2.5 text-xs font-medium rounded border text-center ...@@ -164,180 +151,301 @@ class="py-2.5 text-xs font-medium rounded border text-center
{{-- Desktop table --}} {{-- Desktop table --}}
<div class="hidden md:block overflow-x-auto"> <div class="hidden md:block overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200"> <table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50"> <thead class="bg-purple-50">
<tr> <tr>
<th class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase">{{ __('الاسم') }}</th> <th class="px-4 py-3 text-start text-xs font-medium text-purple-700 uppercase">{{ __('المدرب') }}</th>
<th class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase">{{ __('النوع') }}</th> <th class="px-4 py-3 text-start text-xs font-medium text-purple-700 uppercase">{{ __('الحالة') }}</th>
<th class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase">{{ __('الحالة') }}</th> <th class="px-4 py-3 text-start text-xs font-medium text-purple-700 uppercase">{{ __('وقت الحضور') }}</th>
<th class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase">{{ __('وقت الحضور') }}</th> <th class="px-4 py-3 text-start text-xs font-medium text-purple-700 uppercase">{{ __('الإجراءات') }}</th>
<th class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase">{{ __('ملاحظة') }}</th>
<th class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase">{{ __('الإجراءات') }}</th>
</tr> </tr>
</thead> </thead>
<tbody class="bg-white divide-y divide-gray-200"> <tbody class="bg-white divide-y divide-gray-200">
@foreach($records as $record) @foreach($trainerRecords as $record)
@php $isUnpaidRow = $record->subject_type === \App\Domain\Participant\Models\Participant::class && in_array($record->subject_id, $notPaidParticipantIds); @endphp @php
<tr class="{{ $isUnpaidRow ? 'bg-red-50 hover:bg-red-100' : 'hover:bg-gray-50' }}"> $isOwnRecord = $record->subject_id === $currentUserId;
<!-- Subject Name --> $canMark = $canManageTrainers || $isOwnRecord;
<td class="px-4 py-3 whitespace-nowrap"> @endphp
<span class="text-sm font-medium {{ $isUnpaidRow ? 'text-red-700' : 'text-gray-900' }}"> <tr class="{{ $isOwnRecord ? 'bg-purple-50' : 'hover:bg-gray-50' }} {{ !$canMark ? 'opacity-50' : '' }}">
@if($record->subject_type === \App\Domain\Participant\Models\Participant::class) <td class="px-4 py-3 whitespace-nowrap">
<span class="text-sm font-medium text-gray-900">
{{ $record->subject?->name ?? '-' }}
</span>
@if($isOwnRecord)
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-bold bg-purple-600 text-white ms-1">{{ __('أنت') }}</span>
@endif
</td>
<td class="px-4 py-3 whitespace-nowrap">
@php $color = $record->status->color(); @endphp
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
@switch($color)
@case('green') bg-green-100 text-green-800 @break
@case('amber') bg-amber-100 text-amber-800 @break
@case('red') bg-red-100 text-red-800 @break
@case('blue') bg-blue-100 text-blue-800 @break
@default bg-gray-100 text-gray-800
@endswitch
">
{{ $record->status->label() }}
</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500" dir="ltr">
{{ $record->check_in_at?->format('H:i') ?? '-' }}
</td>
<td class="px-4 py-3 whitespace-nowrap">
<div class="flex items-center gap-1">
@foreach(['present' => ['حاضر', 'green', \App\Domain\Attendance\Enums\AttendanceStatus::Present], 'late' => ['متأخر', 'amber', \App\Domain\Attendance\Enums\AttendanceStatus::Late], 'absent' => ['غائب', 'red', \App\Domain\Attendance\Enums\AttendanceStatus::Absent], 'excused' => ['معذور', 'blue', \App\Domain\Attendance\Enums\AttendanceStatus::Excused]] as $st => [$label, $clr, $enum])
<button
wire:click="markAs({{ $record->id }}, '{{ $st }}')"
wire:loading.attr="disabled"
@if(!$canMark) disabled @endif
class="inline-flex items-center px-2 py-1 text-xs font-medium rounded border
{{ $record->status === $enum ? "bg-{$clr}-600 text-white border-{$clr}-600" : "text-{$clr}-700 border-{$clr}-300 hover:bg-{$clr}-50" }}
disabled:opacity-50 disabled:cursor-not-allowed"
title="{{ __($label) }}">
{{ __($label) }}
</button>
@endforeach
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endif
{{-- ========== PARTICIPANTS SECTION ========== --}}
@if($participantRecords->isNotEmpty())
<div>
<div class="flex items-center gap-2 mb-3">
<div class="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
<svg class="w-4 h-4 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-base font-bold text-gray-800">{{ __('اللاعبون') }}</h2>
<span class="text-xs text-gray-500">({{ $participantRecords->count() }})</span>
</div>
<div class="bg-white border border-gray-200 rounded-lg overflow-hidden">
@if($participantRecords->isEmpty())
<div class="p-12 text-center">
<p class="text-sm text-gray-500">{{ __('لا يوجد لاعبون في هذه الجلسة') }}</p>
</div>
@else
{{-- Mobile cards --}}
<div class="md:hidden space-y-2 p-3">
@foreach($participantRecords as $record)
@php $isUnpaid = in_array($record->subject_id, $notPaidParticipantIds); @endphp
<div class="{{ $isUnpaid ? 'bg-red-50 border-red-300' : 'bg-white border-gray-200' }} border rounded-lg p-3">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium {{ $isUnpaid ? 'text-red-700' : 'text-gray-900' }}">
{{ $record->subject?->person?->name_ar ?? '-' }}
@if($isUnpaid)
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-bold bg-red-600 text-white ms-1">{{ __('غير مدفوع') }}</span>
@endif
</span>
@php $color = $record->status->color(); @endphp
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium
@switch($color)
@case('green') bg-green-100 text-green-800 @break
@case('amber') bg-amber-100 text-amber-800 @break
@case('red') bg-red-100 text-red-800 @break
@case('blue') bg-blue-100 text-blue-800 @break
@case('orange') bg-orange-100 text-orange-800 @break
@case('yellow') bg-yellow-100 text-yellow-800 @break
@case('purple') bg-purple-100 text-purple-800 @break
@default bg-gray-100 text-gray-800
@endswitch
">
{{ $record->status->label() }}
</span>
</div>
<div class="grid grid-cols-4 gap-1.5">
<button wire:click="markAs({{ $record->id }}, 'present')" wire:loading.attr="disabled" wire:target="markAs({{ $record->id }}, 'present')"
class="py-2.5 text-xs font-medium rounded border text-center
{{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Present ? 'bg-green-600 text-white border-green-600' : 'text-green-700 border-green-300 hover:bg-green-50' }} disabled:opacity-50">
<span wire:loading.remove wire:target="markAs({{ $record->id }}, 'present')">{{ __('حاضر') }}</span>
<span wire:loading wire:target="markAs({{ $record->id }}, 'present')">...</span>
</button>
<button wire:click="markAs({{ $record->id }}, 'late')" wire:loading.attr="disabled" wire:target="markAs({{ $record->id }}, 'late')"
class="py-2.5 text-xs font-medium rounded border text-center
{{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Late ? 'bg-amber-600 text-white border-amber-600' : 'text-amber-700 border-amber-300 hover:bg-amber-50' }} disabled:opacity-50">
<span wire:loading.remove wire:target="markAs({{ $record->id }}, 'late')">{{ __('متأخر') }}</span>
<span wire:loading wire:target="markAs({{ $record->id }}, 'late')">...</span>
</button>
<button wire:click="markAs({{ $record->id }}, 'absent')" wire:loading.attr="disabled" wire:target="markAs({{ $record->id }}, 'absent')"
class="py-2.5 text-xs font-medium rounded border text-center
{{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Absent ? 'bg-red-600 text-white border-red-600' : 'text-red-700 border-red-300 hover:bg-red-50' }} disabled:opacity-50">
<span wire:loading.remove wire:target="markAs({{ $record->id }}, 'absent')">{{ __('غائب') }}</span>
<span wire:loading wire:target="markAs({{ $record->id }}, 'absent')">...</span>
</button>
<button wire:click="markAs({{ $record->id }}, 'excused')" wire:loading.attr="disabled" wire:target="markAs({{ $record->id }}, 'excused')"
class="py-2.5 text-xs font-medium rounded border text-center
{{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Excused ? 'bg-blue-600 text-white border-blue-600' : 'text-blue-700 border-blue-300 hover:bg-blue-50' }} disabled:opacity-50">
<span wire:loading.remove wire:target="markAs({{ $record->id }}, 'excused')">{{ __('معذور') }}</span>
<span wire:loading wire:target="markAs({{ $record->id }}, 'excused')">...</span>
</button>
</div>
</div>
@endforeach
</div>
{{-- Desktop table --}}
<div class="hidden md:block overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase">{{ __('الاسم') }}</th>
<th class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase">{{ __('الحالة') }}</th>
<th class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase">{{ __('وقت الحضور') }}</th>
<th class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase">{{ __('ملاحظة') }}</th>
<th class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase">{{ __('الإجراءات') }}</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@foreach($participantRecords as $record)
@php $isUnpaidRow = in_array($record->subject_id, $notPaidParticipantIds); @endphp
<tr class="{{ $isUnpaidRow ? 'bg-red-50 hover:bg-red-100' : 'hover:bg-gray-50' }}">
<td class="px-4 py-3 whitespace-nowrap">
<span class="text-sm font-medium {{ $isUnpaidRow ? 'text-red-700' : 'text-gray-900' }}">
{{ $record->subject?->person?->name_ar ?? '-' }} {{ $record->subject?->person?->name_ar ?? '-' }}
@if($isUnpaidRow) @if($isUnpaidRow)
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-bold bg-red-600 text-white ms-1">{{ __('غير مدفوع') }}</span> <span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-bold bg-red-600 text-white ms-1">{{ __('غير مدفوع') }}</span>
@endif @endif
@else
{{ $record->subject?->name_ar ?? $record->subject?->name ?? '-' }}
@endif
</span>
</td>
<!-- Subject Type Badge -->
<td class="px-4 py-3 whitespace-nowrap">
@if($record->subject_type === \App\Domain\Participant\Models\Participant::class)
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
{{ __('مشترك') }}
</span> </span>
@else </td>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800"> <td class="px-4 py-3 whitespace-nowrap">
{{ __('مدرب') }} @php $color = $record->status->color(); @endphp
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
@switch($color)
@case('green') bg-green-100 text-green-800 @break
@case('amber') bg-amber-100 text-amber-800 @break
@case('red') bg-red-100 text-red-800 @break
@case('blue') bg-blue-100 text-blue-800 @break
@case('orange') bg-orange-100 text-orange-800 @break
@case('yellow') bg-yellow-100 text-yellow-800 @break
@case('purple') bg-purple-100 text-purple-800 @break
@default bg-gray-100 text-gray-800
@endswitch
">
{{ $record->status->label() }}
</span> </span>
@endif @if($record->late_minutes)
</td> <span class="ms-1 text-xs text-gray-500" dir="ltr">({{ $record->late_minutes }} {{ __('د') }})</span>
@endif
<!-- Status Badge --> </td>
<td class="px-4 py-3 whitespace-nowrap"> <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500" dir="ltr">
@php $color = $record->status->color(); @endphp {{ $record->check_in_at?->format('H:i') ?? '-' }}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium </td>
@switch($color) <td class="px-4 py-3" x-data="{ expanded: false }">
@case('green') bg-green-100 text-green-800 @break <div class="flex items-center gap-1">
@case('amber') bg-amber-100 text-amber-800 @break <input
@case('red') bg-red-100 text-red-800 @break type="text"
@case('blue') bg-blue-100 text-blue-800 @break wire:model.blur="recordNotes.{{ $record->id }}"
@case('orange') bg-orange-100 text-orange-800 @break wire:change="saveRecordNote({{ $record->id }})"
@case('yellow') bg-yellow-100 text-yellow-800 @break class="w-32 text-xs rounded border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 placeholder-gray-400"
@case('purple') bg-purple-100 text-purple-800 @break placeholder="{{ __('ملاحظة...') }}"
@default bg-gray-100 text-gray-800 x-show="!expanded"
@endswitch >
"> <textarea
{{ $record->status->label() }} wire:model.blur="recordNotes.{{ $record->id }}"
</span> wire:change="saveRecordNote({{ $record->id }})"
@if($record->late_minutes) rows="2"
<span class="ms-1 text-xs text-gray-500" dir="ltr">({{ $record->late_minutes }} {{ __('د') }})</span> class="w-48 text-xs rounded border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 placeholder-gray-400"
@endif placeholder="{{ __('ملاحظة...') }}"
</td> x-show="expanded"
x-cloak
<!-- Check-in Time --> ></textarea>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500" dir="ltr"> <button
{{ $record->check_in_at?->format('H:i') ?? '-' }} type="button"
</td> @click="expanded = !expanded"
class="text-gray-400 hover:text-gray-600 shrink-0"
<!-- Per-Record Note --> :title="expanded ? '{{ __('تصغير') }}' : '{{ __('توسيع') }}'"
<td class="px-4 py-3" x-data="{ expanded: false }"> >
<div class="flex items-center gap-1"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<input <path x-show="!expanded" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"/>
type="text" <path x-show="expanded" x-cloak stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 4H5v4M15 4h4v4M9 20H5v-4M15 20h4v-4"/>
wire:model.blur="recordNotes.{{ $record->id }}" </svg>
wire:change="saveRecordNote({{ $record->id }})" </button>
class="w-32 text-xs rounded border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 placeholder-gray-400" </div>
placeholder="{{ __('ملاحظة...') }}" @if(session("record_note_saved_{$record->id}"))
x-show="!expanded" <span class="text-xs text-green-600 mt-0.5 block">{{ __('تم') }}</span>
> @endif
<textarea </td>
wire:model.blur="recordNotes.{{ $record->id }}" <td class="px-4 py-3 whitespace-nowrap">
wire:change="saveRecordNote({{ $record->id }})" <div class="flex items-center gap-1">
rows="2" <button
class="w-48 text-xs rounded border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 placeholder-gray-400" wire:click="markAs({{ $record->id }}, 'present')"
placeholder="{{ __('ملاحظة...') }}" wire:loading.attr="disabled"
x-show="expanded" wire:target="markAs({{ $record->id }}, 'present')"
x-cloak class="inline-flex items-center px-2 py-1 text-xs font-medium rounded border
></textarea> {{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Present ? 'bg-green-600 text-white border-green-600' : 'text-green-700 border-green-300 hover:bg-green-50' }}
<button disabled:opacity-50"
type="button" title="{{ __('حاضر') }}">
@click="expanded = !expanded" <span wire:loading.remove wire:target="markAs({{ $record->id }}, 'present')">{{ __('حاضر') }}</span>
class="text-gray-400 hover:text-gray-600 shrink-0" <span wire:loading wire:target="markAs({{ $record->id }}, 'present')" class="w-3 h-3">
:title="expanded ? '{{ __('تصغير') }}' : '{{ __('توسيع') }}'" <svg class="animate-spin w-3 h-3" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>
> </span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> </button>
<path x-show="!expanded" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"/> <button
<path x-show="expanded" x-cloak stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 4H5v4M15 4h4v4M9 20H5v-4M15 20h4v-4"/> wire:click="markAs({{ $record->id }}, 'late')"
</svg> wire:loading.attr="disabled"
</button> wire:target="markAs({{ $record->id }}, 'late')"
</div> class="inline-flex items-center px-2 py-1 text-xs font-medium rounded border
@if(session("record_note_saved_{$record->id}")) {{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Late ? 'bg-amber-600 text-white border-amber-600' : 'text-amber-700 border-amber-300 hover:bg-amber-50' }}
<span class="text-xs text-green-600 mt-0.5 block">{{ __('تم') }}</span> disabled:opacity-50"
@endif title="{{ __('متأخر') }}">
</td> <span wire:loading.remove wire:target="markAs({{ $record->id }}, 'late')">{{ __('متأخر') }}</span>
<span wire:loading wire:target="markAs({{ $record->id }}, 'late')" class="w-3 h-3">
<!-- Actions --> <svg class="animate-spin w-3 h-3" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>
<td class="px-4 py-3 whitespace-nowrap"> </span>
<div class="flex items-center gap-1"> </button>
<!-- Present --> <button
<button wire:click="markAs({{ $record->id }}, 'absent')"
wire:click="markAs({{ $record->id }}, 'present')" wire:loading.attr="disabled"
wire:loading.attr="disabled" wire:target="markAs({{ $record->id }}, 'absent')"
wire:target="markAs({{ $record->id }}, 'present')" class="inline-flex items-center px-2 py-1 text-xs font-medium rounded border
class="inline-flex items-center px-2 py-1 text-xs font-medium rounded border {{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Absent ? 'bg-red-600 text-white border-red-600' : 'text-red-700 border-red-300 hover:bg-red-50' }}
{{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Present ? 'bg-green-600 text-white border-green-600' : 'text-green-700 border-green-300 hover:bg-green-50' }} disabled:opacity-50"
disabled:opacity-50" title="{{ __('غائب') }}">
title="{{ __('حاضر') }}"> <span wire:loading.remove wire:target="markAs({{ $record->id }}, 'absent')">{{ __('غائب') }}</span>
<span wire:loading.remove wire:target="markAs({{ $record->id }}, 'present')">{{ __('حاضر') }}</span> <span wire:loading wire:target="markAs({{ $record->id }}, 'absent')" class="w-3 h-3">
<span wire:loading wire:target="markAs({{ $record->id }}, 'present')" class="w-3 h-3"> <svg class="animate-spin w-3 h-3" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>
<svg class="animate-spin w-3 h-3" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg> </span>
</span> </button>
</button> <button
wire:click="markAs({{ $record->id }}, 'excused')"
<!-- Late --> wire:loading.attr="disabled"
<button wire:target="markAs({{ $record->id }}, 'excused')"
wire:click="markAs({{ $record->id }}, 'late')" class="inline-flex items-center px-2 py-1 text-xs font-medium rounded border
wire:loading.attr="disabled" {{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Excused ? 'bg-blue-600 text-white border-blue-600' : 'text-blue-700 border-blue-300 hover:bg-blue-50' }}
wire:target="markAs({{ $record->id }}, 'late')" disabled:opacity-50"
class="inline-flex items-center px-2 py-1 text-xs font-medium rounded border title="{{ __('معذور') }}">
{{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Late ? 'bg-amber-600 text-white border-amber-600' : 'text-amber-700 border-amber-300 hover:bg-amber-50' }} <span wire:loading.remove wire:target="markAs({{ $record->id }}, 'excused')">{{ __('معذور') }}</span>
disabled:opacity-50" <span wire:loading wire:target="markAs({{ $record->id }}, 'excused')" class="w-3 h-3">
title="{{ __('متأخر') }}"> <svg class="animate-spin w-3 h-3" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>
<span wire:loading.remove wire:target="markAs({{ $record->id }}, 'late')">{{ __('متأخر') }}</span> </span>
<span wire:loading wire:target="markAs({{ $record->id }}, 'late')" class="w-3 h-3"> </button>
<svg class="animate-spin w-3 h-3" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg> </div>
</span> </td>
</button> </tr>
@endforeach
</tbody>
</table>
</div>
@endif
</div>
</div>
@endif
<!-- Absent --> {{-- Empty state --}}
<button @if($trainerRecords->isEmpty() && $participantRecords->isEmpty())
wire:click="markAs({{ $record->id }}, 'absent')" <div class="bg-white border border-gray-200 rounded-lg p-12 text-center">
wire:loading.attr="disabled" <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
wire:target="markAs({{ $record->id }}, 'absent')" <p class="mt-4 text-sm text-gray-500">{{ __('لا توجد سجلات حضور لهذه الجلسة') }}</p>
class="inline-flex items-center px-2 py-1 text-xs font-medium rounded border </div>
{{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Absent ? 'bg-red-600 text-white border-red-600' : 'text-red-700 border-red-300 hover:bg-red-50' }} @endif
disabled:opacity-50"
title="{{ __('غائب') }}">
<span wire:loading.remove wire:target="markAs({{ $record->id }}, 'absent')">{{ __('غائب') }}</span>
<span wire:loading wire:target="markAs({{ $record->id }}, 'absent')" class="w-3 h-3">
<svg class="animate-spin w-3 h-3" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>
</span>
</button>
<!-- Excused -->
<button
wire:click="markAs({{ $record->id }}, 'excused')"
wire:loading.attr="disabled"
wire:target="markAs({{ $record->id }}, 'excused')"
class="inline-flex items-center px-2 py-1 text-xs font-medium rounded border
{{ $record->status === \App\Domain\Attendance\Enums\AttendanceStatus::Excused ? 'bg-blue-600 text-white border-blue-600' : 'text-blue-700 border-blue-300 hover:bg-blue-50' }}
disabled:opacity-50"
title="{{ __('معذور') }}">
<span wire:loading.remove wire:target="markAs({{ $record->id }}, 'excused')">{{ __('معذور') }}</span>
<span wire:loading wire:target="markAs({{ $record->id }}, 'excused')" class="w-3 h-3">
<svg class="animate-spin w-3 h-3" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>
</span>
</button>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
</div> </div>
</div> </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