Commit c000ea07 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Redesign all entity creation flows into integrated workflows

- CreateTrainerWizard: 9-step flow with activities, user account creation,
  and initial group assignment (was 6 disconnected steps)
- CreateProgramWizard: 7-step flow with head trainer, schedule template,
  initial group, BasePrice record, facility (was 4 steps, no group/trainer)
- NewRegistrationWizard: add parent account creation step so guardians
  can log in to the Parent Portal immediately
- UserForm: role-aware prompts — assigning trainer role creates
  Trainer+Employee records, parent role links Guardian, staff creates Employee
- EnrollmentForm: 3-panel guided experience with group cards, capacity bars,
  live price preview from PricingService, and schedule conflict detection
- TrainerCredentialsGenerated event for async email delivery

Closes the "isolated CRUD screens" problem — every entity creation now
handles the full business workflow in a single transaction.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 055ddfa1
<?php
namespace App\Domain\HR\Events;
use App\Models\User;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class TrainerCredentialsGenerated implements ShouldDispatchAfterCommit
{
use Dispatchable, SerializesModels;
public function __construct(
public readonly User $user,
public readonly string $plainPassword,
) {}
}
......@@ -11,9 +11,9 @@
class TrainingProgramService
{
public function create(array $data, User $actor): TrainingProgram
public function create(array $data, User $actor, bool $skipDefaultGroup = false): TrainingProgram
{
return DB::transaction(function () use ($data, $actor) {
return DB::transaction(function () use ($data, $actor, $skipDefaultGroup) {
$slug = Str::limit($data['slug'] ?? Str::slug($data['name']), 90, '');
$exists = TrainingProgram::withTrashed()->where('slug', $slug)->exists();
if ($exists) {
......@@ -25,7 +25,9 @@ public function create(array $data, User $actor): TrainingProgram
'created_by' => $actor->id,
]));
$this->createDefaultGroup($program, $actor);
if (!$skipDefaultGroup) {
$this->createDefaultGroup($program, $actor);
}
return $program;
});
......
......@@ -3,9 +3,16 @@
namespace App\Livewire\Enrollments;
use App\Domain\Participant\Models\Participant;
use App\Domain\Pricing\Services\PricingService;
use App\Domain\Pricing\Services\PriceResult;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Services\SettingsService;
use App\Domain\Training\Models\Enrollment;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Models\TrainingSchedule;
use App\Domain\Training\Services\EnrollmentService;
use App\Domain\Shared\Exceptions\DomainException;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
......@@ -14,81 +21,368 @@
#[Title('تسجيل في مجموعة')]
class EnrollmentForm extends Component
{
public ?int $participant_id = null;
public ?int $training_group_id = null;
public ?string $start_date = null;
public string $payment_status = 'pending';
// ─── Panel 1: Participant ─────────────────────────────────
public string $participantSearch = '';
public ?int $participantId = null;
// ─── Panel 2: Group ──────────────────────────────────────
public ?int $programFilter = null;
public ?int $trainingGroupId = null;
// ─── Panel 3: Confirmation ───────────────────────────────
public ?string $startDate = null;
public string $notes = '';
public bool $autoInvoice = true;
// ─── Internal State ──────────────────────────────────────
public ?array $participantData = null;
public ?array $selectedGroupData = null;
public ?array $pricePreview = null;
public array $scheduleConflicts = [];
public bool $isGroupFull = false;
public ?string $priceError = null;
public function mount(): void
{
$this->authorize('enrollments.create');
$settings = app(SettingsService::class);
$this->autoInvoice = (bool) $settings->get('auto_invoice_on_enrollment', true);
$this->startDate = now()->toDateString();
}
public function rules(): array
{
return [
'participant_id' => 'required|exists:participants,id',
'training_group_id' => 'required|exists:training_groups,id',
'start_date' => 'nullable|date',
'payment_status' => 'required|in:paid,pending,partial,overdue,waived',
'participantId' => 'required|exists:participants,id',
'trainingGroupId' => 'required|exists:training_groups,id',
'startDate' => 'nullable|date|after_or_equal:today',
'notes' => 'nullable|string|max:1000',
'autoInvoice' => 'boolean',
];
}
public function messages(): array
{
return [
'participant_id.required' => 'اختيار المشترك مطلوب',
'participant_id.exists' => 'المشترك المختار غير موجود',
'training_group_id.required' => 'اختيار المجموعة مطلوب',
'training_group_id.exists' => 'المجموعة المختارة غير موجودة',
'start_date.date' => 'تاريخ البدء غير صحيح',
'payment_status.required' => 'حالة الدفع مطلوبة',
'payment_status.in' => 'حالة الدفع غير صحيحة',
'participantId.required' => 'اختيار المشترك مطلوب',
'participantId.exists' => 'المشترك المختار غير موجود',
'trainingGroupId.required' => 'اختيار المجموعة مطلوب',
'trainingGroupId.exists' => 'المجموعة المختارة غير موجودة',
'startDate.date' => 'تاريخ البدء غير صحيح',
'startDate.after_or_equal' => 'تاريخ البدء يجب أن يكون اليوم أو بعده',
'notes.max' => 'الملاحظات يجب ألا تتجاوز 1000 حرف',
];
}
// ─── Lifecycle Hooks ─────────────────────────────────────
public function updatedParticipantId(?int $value): void
{
$this->reset(['trainingGroupId', 'selectedGroupData', 'pricePreview', 'scheduleConflicts', 'isGroupFull', 'priceError']);
if (!$value) {
$this->participantData = null;
return;
}
$participant = Participant::with(['person', 'activeEnrollments.group.schedules', 'wallet'])
->find($value);
if (!$participant) {
$this->participantData = null;
return;
}
$this->participantData = [
'id' => $participant->id,
'name' => $participant->person?->name_ar ?? $participant->person?->name ?? '',
'status' => $participant->status->value,
'age' => $participant->age,
'enrollments_count' => $participant->activeEnrollments->count(),
'wallet_balance' => $participant->wallet?->balance ?? 0,
'is_blocked' => in_array($participant->status->value, ['frozen', 'suspended', 'blacklisted']),
];
}
public function updatedTrainingGroupId(?int $value): void
{
$this->reset(['selectedGroupData', 'pricePreview', 'scheduleConflicts', 'isGroupFull', 'priceError']);
if (!$value) {
return;
}
$group = TrainingGroup::with(['program', 'headTrainer', 'schedules' => fn ($q) => $q->where('is_active', true)])
->find($value);
if (!$group) {
return;
}
$this->selectedGroupData = [
'id' => $group->id,
'name' => $group->name_ar,
'code' => $group->code,
'status' => $group->status->value,
'trainer' => $group->headTrainer?->name ?? '',
'current_count' => $group->current_count,
'max_capacity' => $group->max_capacity,
'program_name' => $group->program?->name_ar ?? '',
'schedules' => $group->schedules->map(fn ($s) => [
'day' => $s->day_name,
'day_of_week' => $s->day_of_week,
'start_time' => $s->start_time,
'end_time' => $s->end_time,
])->toArray(),
];
$this->isGroupFull = $group->isFull();
// Check schedule conflicts
$this->detectScheduleConflicts($group);
// Calculate price preview
$this->calculatePrice($group);
}
public function updatedProgramFilter(): void
{
$this->reset(['trainingGroupId', 'selectedGroupData', 'pricePreview', 'scheduleConflicts', 'isGroupFull', 'priceError']);
}
// ─── Price Calculation ───────────────────────────────────
private function calculatePrice(TrainingGroup $group): void
{
if (!$this->participantId || !$group->program) {
$this->pricePreview = null;
return;
}
try {
$participant = Participant::find($this->participantId);
$pricingService = app(PricingService::class);
$result = $pricingService->calculate(
priceable: $group->program,
participant: $participant,
branchId: $group->branch_id,
);
$this->pricePreview = [
'base_amount' => $result->baseAmount,
'final_amount' => $result->finalAmount,
'total_discount' => $result->totalDiscount,
'discount_percentage' => $result->discountPercentage(),
'has_discount' => $result->hasDiscount(),
'applied_rules' => $result->appliedRules,
];
$this->priceError = null;
} catch (DomainException $e) {
$this->pricePreview = null;
$this->priceError = $e->getMessage();
}
}
// ─── Schedule Conflict Detection ─────────────────────────
private function detectScheduleConflicts(TrainingGroup $selectedGroup): void
{
$this->scheduleConflicts = [];
if (!$this->participantId) {
return;
}
// Get participant's current active enrollments with their group schedules
$existingEnrollments = Enrollment::with(['group.schedules' => fn ($q) => $q->where('is_active', true), 'group'])
->where('participant_id', $this->participantId)
->whereIn('status', ['active', 'pending'])
->get();
$selectedSchedules = $selectedGroup->schedules->where('is_active', true);
foreach ($existingEnrollments as $enrollment) {
if (!$enrollment->group || !$enrollment->group->schedules) {
continue;
}
foreach ($enrollment->group->schedules as $existingSchedule) {
foreach ($selectedSchedules as $newSchedule) {
if ($this->schedulesOverlap($existingSchedule, $newSchedule)) {
$this->scheduleConflicts[] = [
'day' => $newSchedule->day_name,
'time' => substr($newSchedule->start_time, 0, 5) . ' - ' . substr($newSchedule->end_time, 0, 5),
'conflicting_group' => $enrollment->group->name_ar,
];
}
}
}
}
}
private function schedulesOverlap(TrainingSchedule $a, TrainingSchedule $b): bool
{
// Must be on same day
if ($a->day_of_week !== $b->day_of_week) {
return false;
}
// Check time overlap: A starts before B ends AND A ends after B starts
return $a->start_time < $b->end_time && $a->end_time > $b->start_time;
}
// ─── Group Selection ─────────────────────────────────────
public function selectGroup(int $groupId): void
{
$this->trainingGroupId = $groupId;
$this->updatedTrainingGroupId($groupId);
}
// ─── Computed Properties ─────────────────────────────────
#[Computed]
public function searchResults(): \Illuminate\Support\Collection
{
if (strlen($this->participantSearch) < 2) {
return collect();
}
return Participant::with('person')
->whereIn('status', ['registered', 'active', 'frozen', 'suspended', 'blacklisted'])
->where(function ($query) {
$search = $this->participantSearch;
$query->whereHas('person', function ($q) use ($search) {
$q->where('name_ar', 'like', "%{$search}%")
->orWhere('name', 'like', "%{$search}%")
->orWhere('phone', 'like', "%{$search}%");
})
->orWhere('participant_number', 'like', "%{$search}%");
})
->limit(10)
->get();
}
#[Computed]
public function availablePrograms(): \Illuminate\Support\Collection
{
return TrainingProgram::where('registration_open', true)
->whereIn('status', ['active'])
->orderBy('name_ar')
->get(['id', 'name_ar', 'name']);
}
#[Computed]
public function availableGroups(): \Illuminate\Support\Collection
{
$query = TrainingGroup::with(['program', 'headTrainer', 'schedules' => fn ($q) => $q->where('is_active', true)])
->whereIn('status', ['forming', 'active', 'full']);
if ($this->programFilter) {
$query->where('training_program_id', $this->programFilter);
}
return $query->orderBy('name_ar')->get();
}
// ─── Helpers ─────────────────────────────────────────────
public function getStatusBadgeClass(string $status): string
{
return match ($status) {
'active' => 'bg-green-100 text-green-800',
'registered' => 'bg-blue-100 text-blue-800',
'frozen' => 'bg-cyan-100 text-cyan-800',
'suspended' => 'bg-orange-100 text-orange-800',
'blacklisted' => 'bg-red-100 text-red-800',
'forming' => 'bg-yellow-100 text-yellow-800',
'full' => 'bg-red-100 text-red-800',
default => 'bg-gray-100 text-gray-800',
};
}
public function getStatusLabel(string $status): string
{
return match ($status) {
'active' => 'نشط',
'registered' => 'مسجل',
'frozen' => 'متجمد',
'suspended' => 'موقوف',
'blacklisted' => 'محظور',
'forming' => 'قيد التشكيل',
'full' => 'ممتلئة',
'on_hold' => 'معلقة',
'completed' => 'مكتملة',
default => $status,
};
}
public function formatMoney(int $piasters): string
{
return number_format($piasters / 100, 2) . ' ج.م';
}
public function getCapacityPercentage(int $current, int $max): int
{
if ($max <= 0) {
return 0;
}
return (int) min(100, round(($current / $max) * 100));
}
public function getCapacityColor(int $current, int $max): string
{
$pct = $this->getCapacityPercentage($current, $max);
if ($pct >= 100) {
return 'bg-red-500';
}
if ($pct >= 80) {
return 'bg-amber-500';
}
return 'bg-green-500';
}
// ─── Form Submission ─────────────────────────────────────
public function save(EnrollmentService $service): void
{
$this->validate();
// Guard: blocked participant
if ($this->participantData && $this->participantData['is_blocked']) {
session()->flash('error', 'لا يمكن تسجيل هذا المشترك بسبب حالته الحالية');
return;
}
try {
$participant = Participant::findOrFail($this->participant_id);
$group = TrainingGroup::findOrFail($this->training_group_id);
$participant = Participant::findOrFail($this->participantId);
$group = TrainingGroup::findOrFail($this->trainingGroupId);
$service->enroll($participant, $group, auth()->user(), [
'start_date' => $this->start_date ?: null,
'payment_status' => $this->payment_status,
]);
$options = [
'start_date' => $this->startDate ?: null,
'payment_status' => 'pending',
];
session()->flash('success', __('تم التسجيل بنجاح'));
// If auto-invoice is disabled, pass a flag so service won't create one
if (!$this->autoInvoice) {
$options['skip_invoice'] = true;
}
$enrollment = $service->enroll($participant, $group, auth()->user(), $options);
session()->flash('success', 'تم التسجيل بنجاح في مجموعة "' . $group->name_ar . '"');
$this->redirect(route('enrollments.list'), navigate: true);
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
// ─── Render ──────────────────────────────────────────────
public function render()
{
return view('livewire.enrollments.enrollment-form', [
'participants' => Participant::with('person')
->whereIn('status', ['registered', 'active'])
->orderBy('created_at', 'desc')
->limit(200)
->get(),
'groups' => TrainingGroup::with('program')
->whereIn('status', ['forming', 'active'])
->orderBy('name_ar')
->get(),
'paymentOptions' => [
'paid' => 'مدفوع',
'pending' => 'معلق',
'partial' => 'جزئي',
'overdue' => 'متأخر',
'waived' => 'معفى',
],
]);
return view('livewire.enrollments.enrollment-form');
}
}
......@@ -9,8 +9,16 @@
use App\Domain\HR\Services\TrainerService;
use App\Domain\Identity\Models\Branch;
use App\Domain\Identity\Models\Person;
use App\Domain\Identity\Models\Role;
use App\Domain\Scheduling\Enums\AssignmentScope;
use App\Domain\Scheduling\Models\Assignment;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Training\Models\Activity;
use App\Domain\Training\Models\TrainingGroup;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
......@@ -20,7 +28,7 @@
class CreateTrainerWizard extends Component
{
public int $currentStep = 1;
public int $totalSteps = 6;
public int $totalSteps = 9;
public bool $completed = false;
// Step 1: Person
......@@ -29,6 +37,7 @@ class CreateTrainerWizard extends Component
public bool $createNewPerson = false;
public string $personNameAr = '';
public string $personName = '';
public string $personEmail = '';
public string $personPhone = '';
public string $personNationalId = '';
public string $personDateOfBirth = '';
......@@ -41,7 +50,18 @@ class CreateTrainerWizard extends Component
public string $startDate = '';
public ?int $branchId = null;
// Step 3: Compensation
// Step 3: Activities & Sports
public array $selectedActivities = [];
public ?int $primaryActivityId = null;
// Step 4: User Account
public bool $createUserAccount = false;
public string $userEmail = '';
public string $userPassword = '';
public bool $autoGeneratePassword = true;
public bool $sendCredentials = false;
// Step 5: Compensation
public string $compensationModel = '';
public string $hourlyRate = '';
public string $sessionRate = '';
......@@ -49,12 +69,17 @@ class CreateTrainerWizard extends Component
public string $playerRate = '';
public string $revenueSharePercent = '';
// Step 4: Availability
// Step 6: Availability
public array $availabilities = [];
// Step 5: Qualifications
// Step 7: Qualifications
public array $qualifications = [];
// Step 8: Initial Assignment
public bool $assignToGroup = false;
public ?int $assignToGroupId = null;
public string $assignmentScope = 'full';
public function mount(): void
{
$this->authorize('trainers.create');
......@@ -85,11 +110,14 @@ public function getStepLabels(): array
{
return [
1 => 'بيانات الشخص',
2 => 'بيانات التوظيف',
3 => 'التعويض',
4 => 'التوفر',
5 => 'المؤهلات',
6 => 'مراجعة',
2 => 'التوظيف',
3 => 'الأنشطة',
4 => 'حساب الدخول',
5 => 'التعويض',
6 => 'التوفر',
7 => 'المؤهلات',
8 => 'التعيين',
9 => 'مراجعة',
];
}
......@@ -104,7 +132,22 @@ public function getPersonResultsProperty(): \Illuminate\Support\Collection
->orWhere('name', 'ilike', "%{$this->personSearch}%")
->orWhere('phone', 'like', "%{$this->personSearch}%")
->orWhere('national_id', 'like', "%{$this->personSearch}%");
})->limit(10)->get(['id', 'name', 'name_ar', 'phone', 'national_id']);
})->limit(10)->get(['id', 'name', 'name_ar', 'phone', 'email', 'national_id']);
}
public function getAvailableGroupsProperty(): \Illuminate\Support\Collection
{
if (empty($this->selectedActivities)) {
return collect();
}
return TrainingGroup::whereHas('program', function ($q) {
$q->whereIn('activity_id', $this->selectedActivities);
})
->whereIn('status', ['forming', 'active'])
->with('program:id,name_ar,activity_id')
->orderBy('name_ar')
->get(['id', 'name_ar', 'training_program_id', 'status', 'current_count', 'max_capacity']);
}
public function selectPerson(int $id): void
......@@ -113,12 +156,18 @@ public function selectPerson(int $id): void
$this->personId = $person->id;
$this->personNameAr = $person->name_ar ?? '';
$this->personName = $person->name ?? '';
$this->personEmail = $person->email ?? '';
$this->personPhone = $person->phone ?? '';
$this->personNationalId = $person->national_id ?? '';
$this->personDateOfBirth = $person->date_of_birth?->toDateString() ?? '';
$this->personGender = $person->gender ?? '';
$this->createNewPerson = false;
$this->personSearch = '';
// Pre-fill user email from person if available
if ($person->email && empty($this->userEmail)) {
$this->userEmail = $person->email;
}
}
public function toggleCreateNew(): void
......@@ -129,6 +178,48 @@ public function toggleCreateNew(): void
}
}
public function updatedCreateUserAccount(): void
{
if ($this->createUserAccount && $this->autoGeneratePassword) {
$this->userPassword = Str::random(10);
}
// Pre-fill email from person data
if ($this->createUserAccount && empty($this->userEmail)) {
$this->userEmail = $this->personEmail;
}
}
public function updatedAutoGeneratePassword(): void
{
if ($this->autoGeneratePassword) {
$this->userPassword = Str::random(10);
} else {
$this->userPassword = '';
}
}
public function toggleActivity(int $activityId): void
{
if (in_array($activityId, $this->selectedActivities)) {
$this->selectedActivities = array_values(array_diff($this->selectedActivities, [$activityId]));
// Reset primary if removed
if ($this->primaryActivityId === $activityId) {
$this->primaryActivityId = !empty($this->selectedActivities) ? $this->selectedActivities[0] : null;
}
// Reset group assignment if no activities selected
if (empty($this->selectedActivities)) {
$this->assignToGroup = false;
$this->assignToGroupId = null;
}
} else {
$this->selectedActivities[] = $activityId;
// Auto-set primary to first selected
if ($this->primaryActivityId === null) {
$this->primaryActivityId = $activityId;
}
}
}
public function addQualification(): void
{
$this->qualifications[] = ['name' => '', 'issuer' => '', 'issue_date' => '', 'expiry_date' => ''];
......@@ -158,10 +249,23 @@ public function rulesForStep(int $step): array
'branchId' => 'required|exists:branches,id',
],
3 => [
'selectedActivities' => 'required|array|min:1',
'selectedActivities.*' => 'exists:activities,id',
'primaryActivityId' => 'required|in_array:selectedActivities.*',
],
4 => $this->createUserAccount ? [
'userEmail' => 'required|email|unique:users,email',
'userPassword' => 'required|string|min:8',
] : [],
5 => [
'compensationModel' => 'required|in:' . implode(',', array_column(CompensationModel::cases(), 'value')),
],
4 => [],
5 => [],
6 => [],
7 => [],
8 => $this->assignToGroup ? [
'assignToGroupId' => 'required|exists:training_groups,id',
'assignmentScope' => 'required|in:' . implode(',', array_column(AssignmentScope::cases(), 'value')),
] : [],
default => [],
};
}
......@@ -187,9 +291,28 @@ public function messagesForStep(int $step): array
'branchId.exists' => 'الفرع غير موجود',
],
3 => [
'selectedActivities.required' => 'يجب اختيار نشاط واحد على الأقل',
'selectedActivities.min' => 'يجب اختيار نشاط واحد على الأقل',
'primaryActivityId.required' => 'يجب تحديد التخصص الأساسي',
'primaryActivityId.in_array' => 'التخصص الأساسي يجب أن يكون من الأنشطة المختارة',
],
4 => [
'userEmail.required' => 'البريد الإلكتروني مطلوب لإنشاء حساب الدخول',
'userEmail.email' => 'صيغة البريد الإلكتروني غير صحيحة',
'userEmail.unique' => 'البريد الإلكتروني مستخدم بالفعل',
'userPassword.required' => 'كلمة المرور مطلوبة',
'userPassword.min' => 'كلمة المرور يجب أن تكون 8 أحرف على الأقل',
],
5 => [
'compensationModel.required' => 'نموذج التعويض مطلوب',
'compensationModel.in' => 'نموذج التعويض غير صالح',
],
8 => [
'assignToGroupId.required' => 'يجب اختيار المجموعة',
'assignToGroupId.exists' => 'المجموعة غير موجودة',
'assignmentScope.required' => 'يجب تحديد نطاق التعيين',
'assignmentScope.in' => 'نطاق التعيين غير صالح',
],
default => [],
};
}
......@@ -226,11 +349,46 @@ public function confirm(): void
DB::transaction(function () {
$person = $this->resolveOrCreatePerson();
$academyId = app('current_academy')->id;
$user = null;
// Step 4: Create User Account if requested
if ($this->createUserAccount) {
$trainerRole = Role::where('academy_id', $academyId)
->where('slug', 'trainer')
->first();
$user = User::create([
'academy_id' => $academyId,
'person_id' => $person->id,
'name' => $this->personName ?: $this->personNameAr,
'name_ar' => $this->personNameAr,
'email' => $this->userEmail,
'phone' => $this->personPhone ?: null,
'gender' => $this->personGender ?: null,
'password' => Hash::make($this->userPassword),
'role_id' => $trainerRole?->id,
'status' => 'active',
'force_password_change' => !$this->autoGeneratePassword ? false : true,
]);
// Link person to user
$person->update(['user_id' => $user->id]);
// Assign trainer role via pivot
if ($trainerRole) {
$user->roles()->attach($trainerRole->id, [
'branch_id' => $this->branchId,
'assigned_by' => auth()->id(),
'created_at' => now(),
]);
}
}
// Create Employee
// Step 2: Create Employee
$employeeData = [
'academy_id' => $academyId,
'person_id' => $person->id,
'user_id' => $user?->id,
'department' => $this->department,
'position' => $this->position,
'employment_type' => $this->employmentType,
......@@ -240,20 +398,23 @@ public function confirm(): void
$employee = app(EmployeeService::class)->create($employeeData, auth()->user());
// Create Trainer
// Step 5: Create Trainer
$trainerData = [
'person_id' => $person->id,
'compensation_model' => $this->compensationModel,
'hourly_rate' => $this->hourlyRate ? (int) round((float) $this->hourlyRate * 100) : null,
'session_rate' => $this->sessionRate ? (int) round((float) $this->sessionRate * 100) : null,
'group_rate' => $this->groupRate ? (int) round((float) $this->groupRate * 100) : null,
'player_rate' => $this->playerRate ? (int) round((float) $this->playerRate * 100) : null,
'revenue_share_percent' => $this->revenueSharePercent ?: null,
'sports' => $this->selectedActivities,
'specializations' => [$this->primaryActivityId],
];
$trainerService = app(TrainerService::class);
$trainer = $trainerService->create($employee, $trainerData, auth()->user());
// Set Availability
// Step 6: Set Availability
$slots = collect($this->availabilities)
->filter(fn ($a) => $a['enabled'])
->map(fn ($a) => [
......@@ -269,7 +430,7 @@ public function confirm(): void
$trainerService->setAvailability($trainer, $slots);
}
// Add Qualifications
// Step 7: Add Qualifications
$validQualifications = collect($this->qualifications)
->filter(fn ($q) => !empty($q['name']));
......@@ -281,12 +442,40 @@ public function confirm(): void
'expiry_date' => $qual['expiry_date'] ?: null,
]);
}
// Step 8: Initial Assignment
if ($this->assignToGroup && $this->assignToGroupId && $user) {
Assignment::create([
'academy_id' => $academyId,
'user_id' => $user->id,
'assignable_type' => TrainingGroup::class,
'assignable_id' => $this->assignToGroupId,
'role_label' => 'مدرب',
'scope' => $this->assignmentScope,
'status' => 'active',
'start_date' => $this->startDate,
'is_primary' => true,
'created_by' => auth()->id(),
]);
}
// Dispatch credential email event if requested
if ($this->createUserAccount && $this->sendCredentials && $user) {
// Dispatch event — listener will handle the email
event(new \App\Domain\HR\Events\TrainerCredentialsGenerated($user, $this->userPassword));
}
});
$this->completed = true;
session()->flash('success', 'تم إضافة المدرب بنجاح');
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'users_email_unique')) {
session()->flash('error', 'البريد الإلكتروني مستخدم بالفعل في حساب آخر');
} else {
session()->flash('error', 'حدث خطأ أثناء الحفظ: ' . $e->getMessage());
}
}
}
......@@ -300,6 +489,7 @@ private function resolveOrCreatePerson(): Person
'academy_id' => app('current_academy')->id,
'name_ar' => $this->personNameAr,
'name' => $this->personName ?: null,
'email' => $this->personEmail ?: null,
'phone' => $this->personPhone,
'national_id' => $this->personNationalId ?: null,
'date_of_birth' => $this->personDateOfBirth ?: null,
......@@ -316,6 +506,8 @@ public function render()
'employmentTypes' => EmploymentType::cases(),
'compensationModels' => CompensationModel::cases(),
'availabilityTypes' => AvailabilityType::cases(),
'activities' => Activity::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar', 'name', 'icon', 'color']),
'assignmentScopes' => AssignmentScope::cases(),
]);
}
}
......@@ -2,11 +2,18 @@
namespace App\Livewire\Programs;
use App\Domain\Facility\Models\Facility;
use App\Domain\Identity\Models\Branch;
use App\Domain\Pricing\Models\BasePrice;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Training\Models\Activity;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Models\TrainingSchedule;
use App\Domain\Training\Services\TrainingProgramService;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
......@@ -16,7 +23,7 @@
class CreateProgramWizard extends Component
{
public int $currentStep = 1;
public int $totalSteps = 4;
public int $totalSteps = 7;
public bool $completed = false;
// Step 1: Basic Info
......@@ -29,54 +36,126 @@ class CreateProgramWizard extends Component
public string $ageMax = '';
public string $gender = '';
public string $descriptionAr = '';
public string $minParticipants = '5';
public string $maxParticipants = '20';
public string $programDurationWeeks = '12';
public string $sessionDurationMinutes = '60';
// Step 2: Schedule & Duration
public string $sessionsPerWeek = '';
public string $sessionDurationMinutes = '';
public string $programDurationWeeks = '';
public string $minParticipants = '';
public string $maxParticipants = '';
// Step 2: Head Trainer
public ?int $headTrainerId = null;
public bool $skipTrainer = false;
// Step 3: Pricing
// Step 3: Schedule Template
public array $scheduleRows = [];
// Step 4: Initial Group
public bool $createInitialGroup = true;
public string $groupName = '';
public string $groupCode = '';
public string $groupCapacity = '';
// Step 5: Pricing
public string $basePrice = '';
// Step 6: Facility
public bool $assignFacility = false;
public ?int $facilityId = null;
public function mount(): void
{
$this->authorize('programs.create');
$this->addScheduleRow();
}
public function getStepLabels(): array
{
return [
1 => 'المعلومات الأساسية',
2 => 'الجدول والمدة',
3 => 'التسعير',
4 => 'مراجعة',
2 => 'المدرب الرئيسي',
3 => 'جدول التدريب',
4 => 'المجموعة الأولى',
5 => 'التسعير',
6 => 'المنشأة',
7 => 'مراجعة وتأكيد',
];
}
public function addScheduleRow(): void
{
$this->scheduleRows[] = [
'day_of_week' => '0',
'start_time' => '16:00',
'end_time' => '17:00',
];
}
public function removeScheduleRow(int $index): void
{
if (count($this->scheduleRows) > 1) {
unset($this->scheduleRows[$index]);
$this->scheduleRows = array_values($this->scheduleRows);
}
}
public function updatedBranchId(): void
{
// Reset facility when branch changes
$this->facilityId = null;
}
public function updatedCreateInitialGroup(): void
{
if ($this->createInitialGroup && empty($this->groupName)) {
$this->generateGroupDefaults();
}
}
private function generateGroupDefaults(): void
{
$programName = $this->nameAr ?: 'برنامج';
$this->groupName = "{$programName} - مجموعة 1";
$this->groupCode = strtoupper(substr(preg_replace('/[^a-zA-Z0-9]/', '', $this->name ?: Str::random(4)), 0, 6)) . '01';
$this->groupCapacity = $this->maxParticipants ?: '20';
}
public function rulesForStep(int $step): array
{
return match ($step) {
1 => [
'nameAr' => 'required|string|max:255',
'name' => 'nullable|string|max:255',
'activityId' => 'required|exists:activities,id',
'branchId' => 'required|exists:branches,id',
'skillLevel' => 'nullable|string|max:50',
'skillLevel' => 'nullable|in:all,beginner,intermediate,advanced,professional',
'ageMin' => 'nullable|integer|min:2|max:99',
'ageMax' => 'nullable|integer|min:2|max:99|gte:ageMin',
'gender' => 'nullable|in:male,female',
],
2 => [
'sessionsPerWeek' => 'required|integer|min:1|max:14',
'sessionDurationMinutes' => 'required|integer|min:15|max:300',
'programDurationWeeks' => 'required|integer|min:1|max:104',
'gender' => 'nullable|in:all,male,female',
'minParticipants' => 'required|integer|min:1',
'maxParticipants' => 'required|integer|min:1|gte:minParticipants',
'programDurationWeeks' => 'required|integer|min:1|max:104',
'sessionDurationMinutes' => 'required|integer|min:15|max:300',
],
2 => [
'headTrainerId' => $this->skipTrainer ? 'nullable' : 'required|exists:users,id',
],
3 => [
'scheduleRows' => 'required|array|min:1',
'scheduleRows.*.day_of_week' => 'required|integer|min:0|max:6',
'scheduleRows.*.start_time' => 'required|date_format:H:i',
'scheduleRows.*.end_time' => 'required|date_format:H:i|after:scheduleRows.*.start_time',
],
4 => [
'createInitialGroup' => 'boolean',
'groupName' => $this->createInitialGroup ? 'required|string|max:255' : 'nullable',
'groupCode' => $this->createInitialGroup ? 'required|string|max:10' : 'nullable',
'groupCapacity' => $this->createInitialGroup ? 'required|integer|min:1' : 'nullable',
],
5 => [
'basePrice' => 'required|numeric|min:0',
],
6 => [
'facilityId' => $this->assignFacility ? 'required|exists:facilities,id' : 'nullable',
],
default => [],
};
}
......@@ -93,25 +172,39 @@ public function messagesForStep(int $step): array
'ageMin.integer' => 'الحد الأدنى للعمر يجب أن يكون رقم صحيح',
'ageMax.gte' => 'الحد الأقصى للعمر يجب أن يكون أكبر من أو يساوي الحد الأدنى',
'gender.in' => 'قيمة الجنس غير صالحة',
],
2 => [
'sessionsPerWeek.required' => 'عدد الحصص في الأسبوع مطلوب',
'sessionsPerWeek.integer' => 'عدد الحصص يجب أن يكون رقم صحيح',
'sessionsPerWeek.min' => 'عدد الحصص يجب أن يكون حصة واحدة على الأقل',
'sessionDurationMinutes.required' => 'مدة الحصة مطلوبة',
'sessionDurationMinutes.integer' => 'مدة الحصة يجب أن تكون رقم صحيح',
'sessionDurationMinutes.min' => 'مدة الحصة يجب أن تكون 15 دقيقة على الأقل',
'programDurationWeeks.required' => 'مدة البرنامج مطلوبة',
'programDurationWeeks.integer' => 'مدة البرنامج يجب أن تكون رقم صحيح',
'minParticipants.required' => 'الحد الأدنى للمشتركين مطلوب',
'maxParticipants.required' => 'الحد الأقصى للمشتركين مطلوب',
'maxParticipants.gte' => 'الحد الأقصى يجب أن يكون أكبر من أو يساوي الحد الأدنى',
'programDurationWeeks.required' => 'مدة البرنامج مطلوبة',
'sessionDurationMinutes.required' => 'مدة الحصة مطلوبة',
],
2 => [
'headTrainerId.required' => 'يجب اختيار المدرب الرئيسي أو تخطي هذه الخطوة',
'headTrainerId.exists' => 'المدرب المختار غير موجود',
],
3 => [
'scheduleRows.required' => 'يجب إضافة يوم تدريب واحد على الأقل',
'scheduleRows.min' => 'يجب إضافة يوم تدريب واحد على الأقل',
'scheduleRows.*.day_of_week.required' => 'يجب اختيار اليوم',
'scheduleRows.*.start_time.required' => 'وقت البدء مطلوب',
'scheduleRows.*.end_time.required' => 'وقت الانتهاء مطلوب',
'scheduleRows.*.end_time.after' => 'وقت الانتهاء يجب أن يكون بعد وقت البدء',
],
4 => [
'groupName.required' => 'اسم المجموعة مطلوب',
'groupCode.required' => 'كود المجموعة مطلوب',
'groupCapacity.required' => 'سعة المجموعة مطلوبة',
'groupCapacity.integer' => 'السعة يجب أن تكون رقم صحيح',
],
5 => [
'basePrice.required' => 'السعر الأساسي مطلوب',
'basePrice.numeric' => 'السعر يجب أن يكون رقم',
'basePrice.min' => 'السعر لا يمكن أن يكون سالب',
],
6 => [
'facilityId.required' => 'يجب اختيار المنشأة',
'facilityId.exists' => 'المنشأة المختارة غير موجودة',
],
default => [],
};
}
......@@ -123,6 +216,11 @@ public function nextStep(): void
$this->messagesForStep($this->currentStep)
);
// Auto-generate group defaults when moving past step 1
if ($this->currentStep === 1 && $this->createInitialGroup && empty($this->groupName)) {
$this->generateGroupDefaults();
}
if ($this->currentStep < $this->totalSteps) {
$this->currentStep++;
}
......@@ -142,22 +240,34 @@ public function goToStep(int $step): void
}
}
public function toggleSkipTrainer(): void
{
$this->skipTrainer = !$this->skipTrainer;
if ($this->skipTrainer) {
$this->headTrainerId = null;
}
}
public function confirm(): void
{
try {
DB::transaction(function () {
$data = [
'academy_id' => app('current_academy')->id,
$academyId = app('current_academy')->id;
$actor = auth()->user();
// 1. Create Training Program
$programData = [
'academy_id' => $academyId,
'name_ar' => $this->nameAr,
'name' => $this->name ?: null,
'name' => $this->name ?: $this->nameAr,
'activity_id' => $this->activityId,
'branch_id' => $this->branchId,
'skill_level' => $this->skillLevel ?: null,
'skill_level' => $this->skillLevel ?: 'all',
'age_min' => $this->ageMin ?: null,
'age_max' => $this->ageMax ?: null,
'gender' => $this->gender ?: null,
'gender' => $this->gender ?: 'all',
'description_ar' => $this->descriptionAr ?: null,
'sessions_per_week' => (int) $this->sessionsPerWeek,
'sessions_per_week' => count($this->scheduleRows),
'session_duration_minutes' => (int) $this->sessionDurationMinutes,
'program_duration_weeks' => (int) $this->programDurationWeeks,
'min_participants' => (int) $this->minParticipants,
......@@ -165,22 +275,108 @@ public function confirm(): void
'status' => 'active',
];
app(TrainingProgramService::class)->create($data, auth()->user());
$program = app(TrainingProgramService::class)->create($programData, $actor, skipDefaultGroup: true);
// 2. Create Initial Group (if enabled)
$group = null;
if ($this->createInitialGroup) {
// Ensure unique code
$code = $this->groupCode;
$attempt = 0;
while (TrainingGroup::withTrashed()->where('academy_id', $academyId)->where('code', $code)->exists()) {
$attempt++;
$code = $this->groupCode . $attempt;
}
$group = TrainingGroup::create([
'academy_id' => $academyId,
'training_program_id' => $program->id,
'branch_id' => $this->branchId,
'name' => $this->groupName,
'name_ar' => $this->groupName,
'code' => $code,
'head_trainer_id' => $this->headTrainerId,
'max_capacity' => (int) $this->groupCapacity,
'current_count' => 0,
'waitlist_count' => 0,
'status' => 'forming',
'status_changed_at' => now(),
'start_date' => now()->toDateString(),
'created_by' => $actor->id,
]);
}
// 3. Create Schedule Records (linked to the group if created)
if ($group) {
foreach ($this->scheduleRows as $row) {
TrainingSchedule::create([
'academy_id' => $academyId,
'training_group_id' => $group->id,
'facility_id' => $this->assignFacility ? $this->facilityId : null,
'day_of_week' => (int) $row['day_of_week'],
'start_time' => $row['start_time'],
'end_time' => $row['end_time'],
'trainer_id' => $this->headTrainerId,
'effective_from' => now()->toDateString(),
'is_active' => true,
]);
}
}
// 4. Create BasePrice record
$amountPiasters = (int) round((float) $this->basePrice * 100);
BasePrice::create([
'academy_id' => $academyId,
'priceable_type' => TrainingProgram::class,
'priceable_id' => $program->id,
'branch_id' => $this->branchId,
'name_ar' => "اشتراك {$this->nameAr}",
'name' => "Subscription {$this->name}",
'amount' => $amountPiasters,
'currency' => 'EGP',
'effective_from' => now()->toDateString(),
'is_active' => true,
'priority' => 0,
'created_by' => $actor->id,
]);
});
$this->completed = true;
session()->flash('success', 'تم إنشاء البرنامج التدريبي بنجاح');
session()->flash('success', 'تم إنشاء البرنامج التدريبي بنجاح مع كل الإعدادات المطلوبة');
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
} catch (\Exception $e) {
session()->flash('error', 'حدث خطأ غير متوقع: ' . $e->getMessage());
}
}
public function render()
{
$trainers = collect();
if ($this->currentStep === 2 || $this->currentStep === 7) {
$trainerRoleSlugs = ['trainer', 'head_trainer'];
$trainers = User::where('status', 'active')
->whereHas('roles', function ($q) use ($trainerRoleSlugs) {
$q->whereIn('slug', $trainerRoleSlugs);
})
->orderBy('name_ar')
->get(['id', 'name', 'name_ar']);
}
$facilities = collect();
if (($this->currentStep === 6 || $this->currentStep === 7) && $this->branchId) {
$facilities = Facility::where('status', 'active')
->where('branch_id', $this->branchId)
->orderBy('name_ar')
->get(['id', 'name_ar', 'name', 'type']);
}
return view('livewire.programs.create-program-wizard', [
'stepLabels' => $this->getStepLabels(),
'activities' => Activity::orderBy('name_ar')->get(['id', 'name_ar', 'name']),
'branches' => Branch::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar']),
'trainers' => $trainers,
'facilities' => $facilities,
]);
}
}
......@@ -8,6 +8,7 @@
use App\Domain\Identity\Models\Branch;
use App\Domain\Identity\Models\Guardian;
use App\Domain\Identity\Models\Person;
use App\Domain\Identity\Models\Role;
use App\Domain\Identity\Services\PersonService;
use App\Domain\Inventory\Models\Kit;
use App\Domain\Inventory\Models\Product;
......@@ -21,7 +22,10 @@
use App\Domain\Training\Models\Activity;
use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Services\EnrollmentService;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
......@@ -36,7 +40,7 @@ class NewRegistrationWizard extends Component
public ?int $branchId = null;
public int $currentStep = 1;
public int $totalSteps = 6;
public int $totalSteps = 7;
// Step 1: Guardian info
public string $guardian_name_ar = '';
......@@ -45,7 +49,13 @@ class NewRegistrationWizard extends Component
public string $guardian_national_id = '';
public string $guardian_relation = 'father';
// Step 2: Participant (the actual player/child)
// Step 2: Parent Account
public bool $createParentAccount = true;
public string $parentEmail = '';
public string $parentPassword = '';
public bool $sendParentCredentials = true;
// Step 3: Participant (the actual player/child)
public string $participant_name_ar = '';
public string $participant_name = '';
public ?string $participant_date_of_birth = null;
......@@ -56,11 +66,11 @@ class NewRegistrationWizard extends Component
public string $membership_type = 'non_member';
public string $membership_id = '';
// Step 3: Program selection
// Step 4: Program selection
public ?int $selected_activity_id = null;
public ?int $selected_program_id = null;
// Step 5: Payment
// Step 6: Payment
public bool $pay_now = false;
public string $payment_method = 'cash';
......@@ -146,8 +156,18 @@ public function nextStep(): void
return;
}
// Price guard on step 3 — block if no base price for selected program
if ($this->currentStep === 3 && $this->selected_program_id) {
// Pre-fill parent email from guardian person data when moving to step 2
if ($this->currentStep === 1 && $this->duplicateCheckDone && empty($this->parentEmail)) {
if ($this->useExistingPersonId) {
$existingPerson = Person::find($this->useExistingPersonId);
if ($existingPerson && $existingPerson->email) {
$this->parentEmail = $existingPerson->email;
}
}
}
// Price guard on step 4 — block if no base price for selected program
if ($this->currentStep === 4 && $this->selected_program_id) {
$program = TrainingProgram::find($this->selected_program_id);
if ($program && $this->resolveProgramFee($program) === 0) {
$this->addError('selected_program_id', 'لا يوجد سعر محدد لهذا البرنامج — تواصل مع المسؤول');
......@@ -167,7 +187,11 @@ private function rulesForStep(int $step): array
'guardian_national_id' => 'nullable|string|max:14',
'guardian_relation' => 'required|in:father,mother,brother,sister,uncle,aunt,grandfather,grandmother,guardian,other',
],
2 => [
2 => $this->createParentAccount ? [
'parentEmail' => 'required|email|max:255|unique:users,email',
'parentPassword' => 'required|string|min:6|max:100',
] : [],
3 => [
'participant_name_ar' => 'required|string|max:255',
'participant_date_of_birth' => 'required|date|before:today',
'participant_gender' => 'required|in:male,female',
......@@ -177,11 +201,11 @@ private function rulesForStep(int $step): array
'membership_type' => 'required|in:member,non_member',
'membership_id' => 'required_if:membership_type,member|nullable|string|max:50',
],
3 => [
4 => [
'selected_activity_id' => 'required|exists:activities,id',
'selected_program_id' => 'required|exists:training_programs,id',
],
5 => [
6 => [
'payment_method' => 'required_if:pay_now,true|in:cash,card,wallet',
],
default => [],
......@@ -195,6 +219,11 @@ public function messages(): array
'guardian_phone.required' => 'رقم الهاتف مطلوب',
'guardian_relation.required' => 'صلة القرابة مطلوبة',
'guardian_relation.in' => 'صلة القرابة غير صالحة',
'parentEmail.required' => 'البريد الإلكتروني مطلوب لإنشاء الحساب',
'parentEmail.email' => 'صيغة البريد الإلكتروني غير صالحة',
'parentEmail.unique' => 'البريد الإلكتروني مستخدم بالفعل',
'parentPassword.required' => 'كلمة المرور مطلوبة',
'parentPassword.min' => 'كلمة المرور يجب أن تكون 6 أحرف على الأقل',
'participant_name_ar.required' => 'اسم المشترك مطلوب',
'participant_date_of_birth.required' => 'تاريخ الميلاد مطلوب',
'participant_date_of_birth.before' => 'تاريخ الميلاد يجب أن يكون في الماضي',
......@@ -282,6 +311,11 @@ private function resetDuplicateCheck(): void
$this->useExistingPersonId = null;
}
public function generateParentPassword(): void
{
$this->parentPassword = Str::random(8);
}
public function updatedSelectedActivityId(): void
{
$this->selected_program_id = null;
......@@ -419,6 +453,39 @@ public function confirm(): void
]
);
// 2b. Create parent User account if requested
if ($this->createParentAccount && $this->parentEmail) {
$parentRole = Role::where('slug', 'parent')
->where('academy_id', app('current_academy')->id)
->first();
// Only create if no existing user is already linked
if (!$guardian->user_id) {
$parentUser = User::create([
'academy_id' => app('current_academy')->id,
'name' => $guardianPerson->name ?: $guardianPerson->name_ar,
'name_ar' => $guardianPerson->name_ar,
'email' => $this->parentEmail,
'phone' => $guardianPerson->phone,
'password' => Hash::make($this->parentPassword),
'person_id' => $guardianPerson->id,
'role_id' => $parentRole?->id,
'status' => 'active',
]);
$guardian->update(['user_id' => $parentUser->id]);
$guardianPerson->update(['user_id' => $parentUser->id]);
// Attach the parent role via pivot if role exists
if ($parentRole) {
$parentUser->roles()->attach($parentRole->id, [
'assigned_by' => $actor->id,
'created_at' => now(),
]);
}
}
}
// 3. Create participant's Person record
$participantPerson = $personService->create([
'name_ar' => $this->participant_name_ar,
......@@ -551,7 +618,7 @@ public function confirm(): void
if ($invoice) {
$this->invoice_uuid = $invoice->uuid;
}
$this->currentStep = 6;
$this->currentStep = 7;
});
session()->flash('success', __('تم التسجيل بنجاح'));
......
......@@ -2,9 +2,19 @@
namespace App\Livewire\Users;
use App\Domain\HR\Enums\CompensationModel;
use App\Domain\HR\Enums\EmploymentType;
use App\Domain\HR\Models\Employee;
use App\Domain\HR\Models\Trainer;
use App\Domain\HR\Services\EmployeeService;
use App\Domain\HR\Services\TrainerService;
use App\Domain\Identity\Models\Branch;
use App\Domain\Identity\Models\Guardian;
use App\Domain\Identity\Models\Person;
use App\Domain\Identity\Models\Role;
use App\Domain\Participant\Models\Participant;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Layout;
......@@ -18,6 +28,7 @@ class UserForm extends Component
public ?User $user = null;
public bool $editing = false;
// ─── User Fields ─────────────────────────────────────────────
public string $name = '';
public string $name_ar = '';
public string $email = '';
......@@ -27,9 +38,36 @@ class UserForm extends Component
public ?int $person_id = null;
public string $status = 'active';
// ─── Role-Aware: Selected role slug (computed from role_id) ──
public string $selectedRoleSlug = '';
// ─── Trainer Setup Fields ────────────────────────────────────
public bool $createTrainer = true;
public string $trainerCompensationModel = 'per_session';
public string $trainerRateAmount = '';
public ?int $trainerBranchId = null;
// ─── Employee Setup Fields ───────────────────────────────────
public bool $createEmployee = true;
public string $employeeDepartment = '';
public string $employeePosition = '';
public string $employeeEmploymentType = 'full_time';
public ?int $employeeBranchId = null;
public string $employeeStartDate = '';
// ─── Guardian Setup Fields ───────────────────────────────────
public string $guardianMode = 'new'; // 'new' or 'existing'
public ?int $existingGuardianId = null;
public string $guardianRelationshipType = 'father';
public array $guardianParticipantIds = [];
// ─── State Tracking ──────────────────────────────────────────
public bool $roleChanged = false;
public function mount(?User $user = null): void
{
if ($user && $user->exists) {
$this->authorize('users.update');
$this->user = $user;
$this->editing = true;
$this->name = $user->name;
......@@ -38,6 +76,25 @@ public function mount(?User $user = null): void
$this->role_id = $user->role_id;
$this->person_id = $user->person_id;
$this->status = $user->status ?? 'active';
$this->resolveRoleSlug();
} else {
$this->authorize('users.create');
$this->employeeStartDate = now()->toDateString();
}
}
public function updatedRoleId(): void
{
$this->resolveRoleSlug();
$this->roleChanged = true;
}
private function resolveRoleSlug(): void
{
if ($this->role_id) {
$this->selectedRoleSlug = Role::find($this->role_id)?->slug ?? '';
} else {
$this->selectedRoleSlug = '';
}
}
......@@ -52,12 +109,35 @@ public function rules(): array
'status' => 'required|in:active,inactive,suspended,pending',
];
if (!$this->editing) {
if (! $this->editing) {
$rules['password'] = 'required|string|min:8|confirmed';
} else {
$rules['password'] = 'nullable|string|min:8|confirmed';
}
// Trainer-specific rules
if ($this->shouldShowTrainerSection() && $this->createTrainer) {
$rules['trainerCompensationModel'] = 'required|in:salary,hourly,per_session,per_group,per_player,revenue_share,contract,hybrid';
$rules['trainerRateAmount'] = 'required|numeric|min:0';
$rules['trainerBranchId'] = 'nullable|exists:branches,id';
}
// Employee-specific rules (for staff roles)
if ($this->shouldShowEmployeeSection() && $this->createEmployee) {
$rules['employeeEmploymentType'] = 'required|in:full_time,part_time,contract,intern,volunteer';
$rules['employeeBranchId'] = 'nullable|exists:branches,id';
$rules['employeeStartDate'] = 'required|date';
}
// Guardian-specific rules
if ($this->shouldShowGuardianSection()) {
if ($this->guardianMode === 'existing') {
$rules['existingGuardianId'] = 'required|exists:guardians,id';
} else {
$rules['guardianRelationshipType'] = 'required|in:father,mother,grandfather,grandmother,uncle,aunt,sibling,legal_guardian,other';
}
}
return $rules;
}
......@@ -72,6 +152,13 @@ public function messages(): array
'password.required' => 'كلمة المرور مطلوبة',
'password.min' => 'كلمة المرور يجب أن تكون 8 أحرف على الأقل',
'password.confirmed' => 'تأكيد كلمة المرور غير متطابق',
'trainerCompensationModel.required' => 'نموذج التعويض مطلوب',
'trainerRateAmount.required' => 'قيمة المعدل مطلوبة',
'trainerRateAmount.numeric' => 'قيمة المعدل يجب أن تكون رقماً',
'employeeEmploymentType.required' => 'نوع التوظيف مطلوب',
'employeeStartDate.required' => 'تاريخ البدء مطلوب',
'existingGuardianId.required' => 'يجب اختيار ولي أمر',
'guardianRelationshipType.required' => 'نوع العلاقة مطلوب',
];
}
......@@ -82,51 +169,292 @@ public function save(): void
if ($this->role_id) {
$targetRole = Role::find($this->role_id);
$currentUserLevel = auth()->user()->primaryRole?->level ?? 0;
if ($targetRole && $targetRole->level >= $currentUserLevel && !auth()->user()->is_super_admin) {
if ($targetRole && $targetRole->level >= $currentUserLevel && ! auth()->user()->is_super_admin) {
$this->addError('role_id', 'لا يمكنك تعيين دور بمستوى أعلى من أو يساوي مستواك');
return;
}
}
$data = [
'name' => $this->name,
'name_ar' => $this->name_ar,
'email' => $this->email,
'role_id' => $this->role_id,
'person_id' => $this->person_id,
'status' => $this->status,
];
try {
DB::transaction(function () {
$academyId = app('current_academy')->id;
$actor = auth()->user();
// Create or get Person record
$person = $this->resolveOrCreatePerson($academyId, $actor);
// Build user data
$data = [
'name' => $this->name,
'name_ar' => $this->name_ar,
'email' => $this->email,
'role_id' => $this->role_id,
'person_id' => $person?->id ?? $this->person_id,
'status' => $this->status,
];
if ($this->password) {
$data['password'] = Hash::make($this->password);
}
if ($this->editing) {
$this->user->update($data);
$user = $this->user;
} else {
$data['academy_id'] = $academyId;
$user = User::create($data);
}
// Link person to user
if ($person && ! $person->user_id) {
$person->update(['user_id' => $user->id]);
}
// Handle role-specific entity creation
$this->handleRoleEntities($user, $person, $academyId, $actor);
});
session()->flash('success', $this->editing ? 'تم تحديث المستخدم بنجاح' : 'تم إنشاء المستخدم بنجاح');
} catch (\Throwable $e) {
session()->flash('error', 'حدث خطأ: ' . $e->getMessage());
return;
}
$this->redirect(route('users.list'), navigate: true);
}
private function resolveOrCreatePerson(int $academyId, User $actor): ?Person
{
// If person_id already selected, use it
if ($this->person_id) {
return Person::find($this->person_id);
}
// For roles that need a person record, create one
if ($this->needsPersonRecord()) {
return Person::create([
'academy_id' => $academyId,
'name' => $this->name,
'name_ar' => $this->name_ar,
'email' => $this->email,
'created_by' => $actor->id,
]);
}
return null;
}
private function needsPersonRecord(): bool
{
if ($this->selectedRoleSlug === 'trainer' || $this->selectedRoleSlug === 'head_trainer') {
return $this->createTrainer;
}
if ($this->selectedRoleSlug === 'parent') {
return $this->guardianMode === 'new';
}
if (in_array($this->selectedRoleSlug, ['receptionist', 'accountant', 'branch_manager', 'academy_admin'])) {
return $this->createEmployee;
}
return false;
}
if ($this->password) {
$data['password'] = Hash::make($this->password);
private function handleRoleEntities(User $user, ?Person $person, int $academyId, User $actor): void
{
// Only process for new users or role changes
if ($this->editing && ! $this->roleChanged) {
return;
}
if ($this->editing) {
$this->user->update($data);
session()->flash('success', 'تم تحديث المستخدم بنجاح');
if ($this->shouldShowTrainerSection() && $this->createTrainer) {
$this->createTrainerEntities($user, $person, $academyId, $actor);
} elseif ($this->shouldShowGuardianSection()) {
$this->createGuardianEntities($user, $person, $academyId);
} elseif ($this->shouldShowEmployeeSection() && $this->createEmployee) {
$this->createEmployeeEntity($user, $person, $academyId, $actor);
}
}
private function createTrainerEntities(User $user, ?Person $person, int $academyId, User $actor): void
{
if (! $person) {
return;
}
// Check if employee already exists for this person
$employee = Employee::where('person_id', $person->id)->first();
if (! $employee) {
$employeeService = app(EmployeeService::class);
$employee = $employeeService->create([
'academy_id' => $academyId,
'person_id' => $person->id,
'user_id' => $user->id,
'employment_type' => 'full_time',
'start_date' => now()->toDateString(),
'department' => 'التدريب',
'position' => 'مدرب',
'branch_id' => $this->trainerBranchId,
'status' => 'active',
], $actor);
} else {
$data['academy_id'] = app('current_academy')->id;
User::create($data);
session()->flash('success', 'تم إنشاء المستخدم بنجاح');
// Ensure user_id is linked
if (! $employee->user_id) {
$employee->update(['user_id' => $user->id]);
}
}
$this->redirect(route('users.list'), navigate: true);
// Check if trainer record already exists
$existingTrainer = Trainer::where('employee_id', $employee->id)->first();
if (! $existingTrainer) {
$trainerService = app(TrainerService::class);
$rateField = $this->getRateFieldForCompensation($this->trainerCompensationModel);
$rateInPiasters = (int) round((float) $this->trainerRateAmount * 100);
$trainerData = [
'compensation_model' => $this->trainerCompensationModel,
'person_id' => $person->id,
'status' => 'active',
];
if ($rateField) {
$trainerData[$rateField] = $rateInPiasters;
}
$trainerService->create($employee, $trainerData, $actor);
}
}
private function createGuardianEntities(User $user, ?Person $person, int $academyId): void
{
if ($this->guardianMode === 'existing' && $this->existingGuardianId) {
// Link existing guardian to this user
$guardian = Guardian::find($this->existingGuardianId);
if ($guardian && ! $guardian->user_id) {
$guardian->update(['user_id' => $user->id]);
}
} elseif ($this->guardianMode === 'new' && $person) {
// Create new guardian record
$guardian = Guardian::create([
'academy_id' => $academyId,
'person_id' => $person->id,
'user_id' => $user->id,
'relationship_type' => $this->guardianRelationshipType,
'is_financial_responsible' => true,
]);
// Link to selected participants
if (! empty($this->guardianParticipantIds)) {
foreach ($this->guardianParticipantIds as $participantId) {
$guardian->participants()->attach($participantId, [
'relationship_type' => $this->guardianRelationshipType,
'is_primary' => true,
'receives_notifications' => true,
'can_pickup' => true,
]);
}
}
}
}
private function createEmployeeEntity(User $user, ?Person $person, int $academyId, User $actor): void
{
if (! $person) {
return;
}
// Check if employee already exists
$existing = Employee::where('person_id', $person->id)->first();
if ($existing) {
if (! $existing->user_id) {
$existing->update(['user_id' => $user->id]);
}
return;
}
$employeeService = app(EmployeeService::class);
$employeeService->create([
'academy_id' => $academyId,
'person_id' => $person->id,
'user_id' => $user->id,
'employment_type' => $this->employeeEmploymentType,
'start_date' => $this->employeeStartDate ?: now()->toDateString(),
'department' => $this->employeeDepartment ?: null,
'position' => $this->employeePosition ?: null,
'branch_id' => $this->employeeBranchId,
'status' => 'active',
], $actor);
}
private function getRateFieldForCompensation(string $model): ?string
{
return match ($model) {
'hourly' => 'hourly_rate',
'per_session' => 'session_rate',
'per_group' => 'group_rate',
'per_player' => 'player_rate',
default => 'session_rate',
};
}
// ─── Section Visibility Helpers ──────────────────────────────
public function shouldShowTrainerSection(): bool
{
return in_array($this->selectedRoleSlug, ['trainer', 'head_trainer']);
}
public function shouldShowGuardianSection(): bool
{
return $this->selectedRoleSlug === 'parent';
}
public function shouldShowEmployeeSection(): bool
{
return in_array($this->selectedRoleSlug, ['receptionist', 'accountant', 'branch_manager', 'academy_admin']);
}
public function render()
{
$rolesQuery = Role::orderBy('level', 'desc');
if (!auth()->user()->is_super_admin) {
if (! auth()->user()->is_super_admin) {
$currentUserLevel = auth()->user()->primaryRole?->level ?? 0;
$rolesQuery->where('level', '<', $currentUserLevel);
}
return view('livewire.users.user-form', [
$viewData = [
'roles' => $rolesQuery->get(['id', 'name_ar', 'slug', 'level']),
'people' => Person::orderBy('name_ar')
->get(['id', 'name_ar'])
->map(fn ($p) => ['id' => $p->id, 'name' => $p->name_ar]),
]);
'branches' => Branch::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar']),
'compensationModels' => CompensationModel::cases(),
'employmentTypes' => EmploymentType::cases(),
];
// Load guardians list for parent role
if ($this->shouldShowGuardianSection()) {
$viewData['guardians'] = Guardian::with('person')
->orderBy('id', 'desc')
->get()
->map(fn ($g) => [
'id' => $g->id,
'name' => $g->person?->name_ar ?? __('بدون اسم'),
'phone' => $g->person?->phone ?? '',
]);
$viewData['participants'] = Participant::with('person')
->orderBy('id', 'desc')
->get()
->map(fn ($p) => [
'id' => $p->id,
'name' => $p->person?->name_ar ?? $p->person?->name ?? '',
]);
}
return view('livewire.users.user-form', $viewData);
}
}
<div>
{{-- Header --}}
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-4 sm:mb-6">
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('تسجيل في مجموعة') }}</h1>
<div>
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('تسجيل في مجموعة') }}</h1>
<p class="text-sm text-gray-500 mt-1">{{ __('اختر المشترك والمجموعة لإتمام التسجيل') }}</p>
</div>
<a href="{{ route('enrollments.list') }}" wire:navigate
class="text-sm text-gray-600 hover:text-gray-800">
class="text-sm text-gray-600 hover:text-gray-800 flex items-center gap-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="M11 17l-5-5m0 0l5-5m-5 5h12"/>
</svg>
{{ __('العودة للقائمة') }}
</a>
</div>
{{-- Flash Messages --}}
@if(session('error'))
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm">
{{ session('error') }}
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm flex items-start gap-2">
<svg class="w-5 h-5 shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
</svg>
<span>{{ session('error') }}</span>
</div>
@endif
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6">
<form wire:submit="save" class="space-y-6">
{{-- Participant Selection --}}
<div>
<label for="participant_id" class="block text-sm font-medium text-gray-700 mb-1">
{{ __('المشترك') }} <span class="text-red-500">*</span>
</label>
<select wire:model="participant_id" id="participant_id"
class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 @error('participant_id') border-red-500 @enderror">
<option value="">{{ __('اختر المشترك...') }}</option>
@foreach($participants as $participant)
<option value="{{ $participant->id }}">
{{ $participant->person?->name_ar }}
@if($participant->participant_number)
({{ $participant->participant_number }})
@endif
</option>
@endforeach
</select>
@error('participant_id')
<p class="mt-1 text-xs text-red-600">{{ $message }}</p>
@enderror
@if(session('success'))
<div class="mb-4 p-4 bg-green-50 border border-green-200 rounded-lg text-green-700 text-sm flex items-start gap-2">
<svg class="w-5 h-5 shrink-0 mt-0.5" 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>
<span>{{ session('success') }}</span>
</div>
@endif
{{-- Progress Steps Indicator --}}
<div class="mb-6">
<div class="flex items-center justify-center gap-2 sm:gap-4">
{{-- Step 1 --}}
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold
{{ $participantId ? 'bg-green-500 text-white' : 'bg-blue-600 text-white' }}">
@if($participantId)
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"/>
</svg>
@else
1
@endif
</div>
<span class="text-xs sm:text-sm font-medium text-gray-700 hidden sm:inline">{{ __('المشترك') }}</span>
</div>
{{-- Group Selection --}}
<div>
<label for="training_group_id" class="block text-sm font-medium text-gray-700 mb-1">
{{ __('المجموعة') }} <span class="text-red-500">*</span>
</label>
<select wire:model="training_group_id" id="training_group_id"
class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 @error('training_group_id') border-red-500 @enderror">
<option value="">{{ __('اختر المجموعة...') }}</option>
@foreach($groups as $group)
<option value="{{ $group->id }}">
{{ $group->name_ar }}
@if($group->program)
— {{ $group->program->name_ar }}
@endif
({{ $group->current_count ?? 0 }}/{{ $group->max_capacity ?? '—' }})
</option>
@endforeach
</select>
@error('training_group_id')
<p class="mt-1 text-xs text-red-600">{{ $message }}</p>
@enderror
<div class="flex-1 h-0.5 max-w-12 {{ $participantId ? 'bg-green-300' : 'bg-gray-200' }}"></div>
{{-- Step 2 --}}
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold
{{ $trainingGroupId ? 'bg-green-500 text-white' : ($participantId ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-500') }}">
@if($trainingGroupId)
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"/>
</svg>
@else
2
@endif
</div>
<span class="text-xs sm:text-sm font-medium text-gray-700 hidden sm:inline">{{ __('المجموعة') }}</span>
</div>
{{-- Start Date --}}
<div>
<label for="start_date" class="block text-sm font-medium text-gray-700 mb-1">
{{ __('تاريخ البدء') }}
</label>
<input type="date" wire:model="start_date" id="start_date" dir="ltr"
class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 @error('start_date') border-red-500 @enderror">
<p class="mt-1 text-xs text-gray-500">{{ __('اتركه فارغاً لاستخدام تاريخ بدء المجموعة') }}</p>
@error('start_date')
<p class="mt-1 text-xs text-red-600">{{ $message }}</p>
@enderror
<div class="flex-1 h-0.5 max-w-12 {{ $trainingGroupId ? 'bg-green-300' : 'bg-gray-200' }}"></div>
{{-- Step 3 --}}
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold
{{ $trainingGroupId ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-500' }}">
3
</div>
<span class="text-xs sm:text-sm font-medium text-gray-700 hidden sm:inline">{{ __('التأكيد') }}</span>
</div>
</div>
</div>
{{-- Payment Status --}}
<div>
<label for="payment_status" class="block text-sm font-medium text-gray-700 mb-1">
{{ __('حالة الدفع') }} <span class="text-red-500">*</span>
</label>
<select wire:model="payment_status" id="payment_status"
class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 @error('payment_status') border-red-500 @enderror">
@foreach($paymentOptions as $value => $label)
<option value="{{ $value }}">{{ __($label) }}</option>
@endforeach
</select>
@error('payment_status')
<p class="mt-1 text-xs text-red-600">{{ $message }}</p>
<div class="space-y-6">
{{-- ═══════════════════════════════════════════════════════════════
PANEL 1: Select Participant
═══════════════════════════════════════════════════════════════ --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-4 sm:px-6 py-4 border-b border-gray-100 bg-gray-50/50">
<h2 class="text-base font-semibold text-gray-800 flex items-center gap-2">
<svg class="w-5 h-5 text-blue-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>
{{ __('اختيار المشترك') }}
</h2>
</div>
<div class="p-4 sm:p-6">
{{-- Search Input --}}
<div class="relative">
<label for="participantSearch" class="block text-sm font-medium text-gray-700 mb-2">
{{ __('ابحث عن المشترك') }} <span class="text-red-500">*</span>
</label>
<div class="relative">
<input type="text"
id="participantSearch"
wire:model.live.debounce.300ms="participantSearch"
placeholder="{{ __('اكتب اسم المشترك أو رقم العضوية أو رقم الهاتف...') }}"
class="w-full ps-10 pe-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
autocomplete="off">
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
<svg class="w-4 h-4 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>
<div wire:loading wire:target="participantSearch" class="absolute inset-y-0 end-0 flex items-center pe-3">
<svg class="animate-spin w-4 h-4 text-blue-500" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/>
</svg>
</div>
</div>
{{-- Search Results Dropdown --}}
@if(strlen($participantSearch) >= 2 && !$participantId)
<div class="absolute z-20 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg max-h-60 overflow-y-auto">
@forelse($this->searchResults as $result)
<button type="button"
wire:click="$set('participantId', {{ $result->id }})"
class="w-full text-start px-4 py-3 hover:bg-blue-50 border-b border-gray-100 last:border-0 transition-colors">
<div class="flex items-center justify-between">
<div>
<span class="text-sm font-medium text-gray-800">{{ $result->person?->name_ar ?? $result->person?->name }}</span>
@if($result->participant_number)
<span class="text-xs text-gray-500 ms-2">#{{ $result->participant_number }}</span>
@endif
</div>
<span class="text-xs px-2 py-0.5 rounded-full {{ $this->getStatusBadgeClass($result->status->value) }}">
{{ $this->getStatusLabel($result->status->value) }}
</span>
</div>
@if($result->person?->phone)
<p class="text-xs text-gray-500 mt-0.5" dir="ltr">{{ $result->person->phone }}</p>
@endif
</button>
@empty
<div class="px-4 py-3 text-sm text-gray-500 text-center">
{{ __('لا توجد نتائج') }}
</div>
@endforelse
</div>
@endif
</div>
@error('participantId')
<p class="mt-2 text-xs text-red-600">{{ $message }}</p>
@enderror
{{-- Selected Participant Card --}}
@if($participantData)
<div class="mt-4 p-4 rounded-lg border {{ $participantData['is_blocked'] ? 'border-red-200 bg-red-50' : 'border-green-200 bg-green-50' }}">
<div class="flex items-start justify-between">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center">
<svg class="w-5 h-5 text-gray-500" 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>
<h3 class="text-sm font-semibold text-gray-800">{{ $participantData['name'] }}</h3>
<div class="flex items-center gap-3 mt-1 text-xs text-gray-600">
<span class="px-2 py-0.5 rounded-full {{ $this->getStatusBadgeClass($participantData['status']) }}">
{{ $this->getStatusLabel($participantData['status']) }}
</span>
@if($participantData['age'])
<span>{{ $participantData['age'] }} {{ __('سنة') }}</span>
@endif
<span>{{ $participantData['enrollments_count'] }} {{ __('تسجيل نشط') }}</span>
</div>
</div>
</div>
<div class="flex items-center gap-2">
<div class="text-end">
<p class="text-xs text-gray-500">{{ __('رصيد المحفظة') }}</p>
<p class="text-sm font-semibold text-gray-800" dir="ltr">{{ $this->formatMoney($participantData['wallet_balance']) }}</p>
</div>
<button type="button"
wire:click="$set('participantId', null)"
class="ms-2 p-1 text-gray-400 hover:text-red-500 transition-colors"
title="{{ __('تغيير المشترك') }}">
<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>
</div>
{{-- Blocked Status Warning --}}
@if($participantData['is_blocked'])
<div class="mt-3 p-3 bg-red-100 border border-red-300 rounded-lg flex items-center gap-2">
<svg class="w-5 h-5 text-red-600 shrink-0" 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>
<p class="text-sm font-medium text-red-800">
{{ __('لا يمكن تسجيل هذا المشترك — الحالة:') }} {{ $this->getStatusLabel($participantData['status']) }}
</p>
</div>
@endif
</div>
@endif
</div>
</div>
{{-- ═══════════════════════════════════════════════════════════════
PANEL 2: Select Group (only visible after participant selected)
═══════════════════════════════════════════════════════════════ --}}
@if($participantId && $participantData && !$participantData['is_blocked'])
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden" wire:transition>
<div class="px-4 sm:px-6 py-4 border-b border-gray-100 bg-gray-50/50">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<h2 class="text-base font-semibold text-gray-800 flex items-center gap-2">
<svg class="w-5 h-5 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 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
</svg>
{{ __('اختيار المجموعة') }}
</h2>
{{-- Program Filter --}}
<div class="flex items-center gap-2">
<label for="programFilter" class="text-xs text-gray-600 whitespace-nowrap">{{ __('فلترة بالبرنامج:') }}</label>
<select wire:model.live="programFilter" id="programFilter"
class="text-sm border border-gray-300 rounded-lg px-3 py-1.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="">{{ __('الكل') }}</option>
@foreach($this->availablePrograms as $program)
<option value="{{ $program->id }}">{{ $program->name_ar }}</option>
@endforeach
</select>
</div>
</div>
</div>
{{-- Notes --}}
<div>
<label for="notes" class="block text-sm font-medium text-gray-700 mb-1">
{{ __('ملاحظات') }}
</label>
<textarea wire:model="notes" id="notes" rows="3"
class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 @error('notes') border-red-500 @enderror"
placeholder="{{ __('ملاحظات إضافية...') }}"></textarea>
@error('notes')
<p class="mt-1 text-xs text-red-600">{{ $message }}</p>
<div class="p-4 sm:p-6">
{{-- Loading state --}}
<div wire:loading wire:target="programFilter" class="flex justify-center py-8">
<svg class="animate-spin w-6 h-6 text-blue-500" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/>
</svg>
</div>
{{-- Groups Grid --}}
<div wire:loading.remove wire:target="programFilter" class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
@forelse($this->availableGroups as $group)
@php
$isSelected = $trainingGroupId === $group->id;
$isFull = $group->isFull();
$capacityPct = $this->getCapacityPercentage($group->current_count, $group->max_capacity);
@endphp
<button type="button"
wire:click="selectGroup({{ $group->id }})"
class="text-start p-4 rounded-xl border-2 transition-all duration-200 hover:shadow-md
{{ $isSelected ? 'border-blue-500 bg-blue-50 ring-2 ring-blue-200' : 'border-gray-200 hover:border-blue-300 bg-white' }}">
{{-- Group Header --}}
<div class="flex items-start justify-between mb-3">
<div>
<h3 class="text-sm font-bold text-gray-800">{{ $group->name_ar }}</h3>
@if($group->code)
<p class="text-xs text-gray-500 mt-0.5">{{ $group->code }}</p>
@endif
</div>
<span class="text-xs px-2 py-0.5 rounded-full {{ $this->getStatusBadgeClass($group->status->value) }}">
@if($isFull)
{{ __('ممتلئة') }}
@else
{{ $this->getStatusLabel($group->status->value) }}
@endif
</span>
</div>
{{-- Program Name --}}
@if($group->program)
<p class="text-xs text-blue-600 font-medium mb-2">{{ $group->program->name_ar }}</p>
@endif
{{-- Trainer --}}
@if($group->headTrainer)
<div class="flex items-center gap-1.5 text-xs text-gray-600 mb-2">
<svg class="w-3.5 h-3.5" 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>
<span>{{ $group->headTrainer->name }}</span>
</div>
@endif
{{-- Schedule --}}
@if($group->schedules->where('is_active', true)->isNotEmpty())
<div class="flex flex-wrap gap-1 mb-3">
@foreach($group->schedules->where('is_active', true) as $schedule)
<span class="inline-flex items-center gap-1 text-xs bg-gray-100 text-gray-700 px-2 py-1 rounded">
<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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
{{ $schedule->day_name }} {{ substr($schedule->start_time, 0, 5) }}-{{ substr($schedule->end_time, 0, 5) }}
</span>
@endforeach
</div>
@endif
{{-- Capacity Bar --}}
<div class="mt-auto">
<div class="flex items-center justify-between text-xs text-gray-600 mb-1">
<span>{{ __('السعة') }}</span>
<span class="font-medium" dir="ltr">{{ $group->current_count }}/{{ $group->max_capacity }}</span>
</div>
<div class="w-full h-2 bg-gray-200 rounded-full overflow-hidden">
<div class="h-full rounded-full transition-all duration-300 {{ $this->getCapacityColor($group->current_count, $group->max_capacity) }}"
style="width: {{ min($capacityPct, 100) }}%"></div>
</div>
@if($isFull && $group->program?->allow_waitlist)
<p class="text-xs text-amber-600 font-medium mt-1">{{ __('ممتلئة - قائمة انتظار') }}</p>
@elseif($isFull)
<p class="text-xs text-red-600 font-medium mt-1">{{ __('ممتلئة - لا تقبل تسجيلات') }}</p>
@endif
</div>
{{-- Selected Indicator --}}
@if($isSelected)
<div class="mt-3 flex items-center justify-center gap-1 text-xs text-blue-700 font-medium">
<svg class="w-4 h-4" 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>
{{ __('تم الاختيار') }}
</div>
@endif
</button>
@empty
<div class="col-span-full flex flex-col items-center justify-center py-12 text-gray-400">
<svg class="w-12 h-12 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" 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 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
</svg>
<p class="text-sm font-medium">{{ __('لا توجد مجموعات متاحة') }}</p>
<p class="text-xs mt-1">{{ __('جرب تغيير فلتر البرنامج') }}</p>
</div>
@endforelse
</div>
@error('trainingGroupId')
<p class="mt-3 text-xs text-red-600">{{ $message }}</p>
@enderror
{{-- Schedule Conflict Warning --}}
@if(!empty($scheduleConflicts))
<div class="mt-4 p-4 bg-amber-50 border border-amber-200 rounded-lg">
<div class="flex items-start gap-2">
<svg class="w-5 h-5 text-amber-600 shrink-0 mt-0.5" 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>
<div>
<h4 class="text-sm font-semibold text-amber-800">{{ __('تعارض في الجدول') }}</h4>
<ul class="mt-1 space-y-1">
@foreach($scheduleConflicts as $conflict)
<li class="text-xs text-amber-700">
{{ $conflict['day'] }} {{ $conflict['time'] }} {{ __('مع') }} "{{ $conflict['conflicting_group'] }}"
</li>
@endforeach
</ul>
<p class="text-xs text-amber-600 mt-2">{{ __('يمكنك المتابعة لكن قد يحدث تداخل في المواعيد') }}</p>
</div>
</div>
</div>
@endif
{{-- Waitlist Notice --}}
@if($isGroupFull && $selectedGroupData)
<div class="mt-4 p-4 bg-blue-50 border border-blue-200 rounded-lg flex items-start gap-2">
<svg class="w-5 h-5 text-blue-600 shrink-0 mt-0.5" 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 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
</svg>
<p class="text-sm text-blue-800">
{{ __('هذه المجموعة ممتلئة. سيتم إضافة المشترك إلى قائمة الانتظار عند التأكيد.') }}
</p>
</div>
@endif
</div>
</div>
@endif
{{-- ═══════════════════════════════════════════════════════════════
PANEL 3: Price & Confirmation (only visible after group selected)
═══════════════════════════════════════════════════════════════ --}}
@if($trainingGroupId && $selectedGroupData)
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden" wire:transition>
<div class="px-4 sm:px-6 py-4 border-b border-gray-100 bg-gray-50/50">
<h2 class="text-base font-semibold text-gray-800 flex items-center gap-2">
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
{{ __('معاينة السعر والتأكيد') }}
</h2>
</div>
{{-- Submit --}}
<div class="flex flex-col-reverse sm:flex-row items-stretch sm:items-center justify-end gap-3 pt-4 border-t border-gray-200">
<a href="{{ route('enrollments.list') }}" wire:navigate
class="text-center px-4 sm:px-6 py-2.5 text-sm text-gray-700 hover:bg-gray-100 rounded-lg border border-gray-300 font-medium">
{{ __('إلغاء') }}
</a>
<button type="submit"
wire:loading.attr="disabled"
wire:target="save"
class="text-center px-4 sm:px-6 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-sm font-medium disabled:opacity-50">
<span wire:loading.remove wire:target="save">{{ __('تسجيل') }}</span>
<span wire:loading wire:target="save">{{ __('جارٍ التسجيل...') }}</span>
</button>
<div class="p-4 sm:p-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
{{-- Price Preview Card --}}
<div>
<h3 class="text-sm font-semibold text-gray-700 mb-3">{{ __('تفاصيل السعر') }}</h3>
@if($pricePreview)
<div class="border border-gray-200 rounded-lg overflow-hidden">
{{-- Base Price --}}
<div class="flex items-center justify-between px-4 py-3 border-b border-gray-100">
<span class="text-sm text-gray-600">{{ __('السعر الأساسي') }}</span>
<span class="text-sm font-medium text-gray-800" dir="ltr">{{ $this->formatMoney($pricePreview['base_amount']) }}</span>
</div>
{{-- Applied Discounts --}}
@if(!empty($pricePreview['applied_rules']))
@foreach($pricePreview['applied_rules'] as $rule)
<div class="flex items-center justify-between px-4 py-2 border-b border-gray-100 bg-green-50/50">
<span class="text-xs text-green-700 flex items-center gap-1">
<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="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"/>
</svg>
{{ $rule['rule_name'] }}
</span>
<span class="text-xs font-medium text-green-700" dir="ltr">-{{ $this->formatMoney($rule['discount']) }}</span>
</div>
@endforeach
@endif
{{-- Total Discount Summary --}}
@if($pricePreview['has_discount'])
<div class="flex items-center justify-between px-4 py-2 border-b border-gray-100 bg-green-50">
<span class="text-xs font-medium text-green-800">{{ __('إجمالي الخصم') }} ({{ $pricePreview['discount_percentage'] }}%)</span>
<span class="text-xs font-bold text-green-800" dir="ltr">-{{ $this->formatMoney($pricePreview['total_discount']) }}</span>
</div>
@endif
{{-- Final Amount --}}
<div class="flex items-center justify-between px-4 py-3 bg-blue-50">
<span class="text-sm font-bold text-blue-900">{{ __('المبلغ المطلوب') }}</span>
<span class="text-lg font-bold text-blue-900" dir="ltr">{{ $this->formatMoney($pricePreview['final_amount']) }}</span>
</div>
</div>
@elseif($priceError)
<div class="p-4 bg-amber-50 border border-amber-200 rounded-lg">
<div class="flex items-center gap-2">
<svg class="w-5 h-5 text-amber-600" 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 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
</svg>
<p class="text-sm text-amber-800">{{ $priceError }}</p>
</div>
<p class="text-xs text-amber-600 mt-1">{{ __('سيتم التسجيل بدون فاتورة تلقائية') }}</p>
</div>
@else
<div class="p-4 bg-gray-50 border border-gray-200 rounded-lg text-center">
<svg class="w-8 h-8 text-gray-300 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<p class="text-xs text-gray-500">{{ __('جارٍ حساب السعر...') }}</p>
</div>
@endif
</div>
{{-- Enrollment Options --}}
<div class="space-y-4">
<h3 class="text-sm font-semibold text-gray-700 mb-3">{{ __('خيارات التسجيل') }}</h3>
{{-- Start Date --}}
<div>
<label for="startDate" class="block text-sm font-medium text-gray-700 mb-1">
{{ __('تاريخ البدء') }}
</label>
<input type="date" wire:model="startDate" id="startDate" dir="ltr"
class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 @error('startDate') border-red-500 @enderror">
@error('startDate')
<p class="mt-1 text-xs text-red-600">{{ $message }}</p>
@enderror
</div>
{{-- Auto Invoice Toggle --}}
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg border border-gray-200">
<div>
<label for="autoInvoice" class="text-sm font-medium text-gray-700 cursor-pointer">
{{ __('إنشاء فاتورة تلقائياً') }}
</label>
<p class="text-xs text-gray-500 mt-0.5">{{ __('سيتم إنشاء فاتورة بالمبلغ المحسوب') }}</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" wire:model="autoInvoice" id="autoInvoice" class="sr-only peer">
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
</label>
</div>
{{-- Notes --}}
<div>
<label for="notes" class="block text-sm font-medium text-gray-700 mb-1">
{{ __('ملاحظات') }}
</label>
<textarea wire:model="notes" id="notes" rows="3"
class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 @error('notes') border-red-500 @enderror"
placeholder="{{ __('ملاحظات إضافية (اختياري)...') }}"></textarea>
@error('notes')
<p class="mt-1 text-xs text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
</div>
{{-- Enrollment Summary --}}
<div class="mt-6 p-4 bg-gray-50 rounded-lg border border-gray-200">
<h4 class="text-xs font-semibold text-gray-500 uppercase mb-2">{{ __('ملخص التسجيل') }}</h4>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3 text-sm">
<div>
<span class="text-gray-500">{{ __('المشترك:') }}</span>
<span class="font-medium text-gray-800 ms-1">{{ $participantData['name'] ?? '' }}</span>
</div>
<div>
<span class="text-gray-500">{{ __('المجموعة:') }}</span>
<span class="font-medium text-gray-800 ms-1">{{ $selectedGroupData['name'] ?? '' }}</span>
</div>
<div>
<span class="text-gray-500">{{ __('البرنامج:') }}</span>
<span class="font-medium text-gray-800 ms-1">{{ $selectedGroupData['program_name'] ?? '' }}</span>
</div>
</div>
</div>
{{-- Submit Actions --}}
<div class="mt-6 flex flex-col-reverse sm:flex-row items-stretch sm:items-center justify-end gap-3 pt-4 border-t border-gray-200">
<a href="{{ route('enrollments.list') }}" wire:navigate
class="text-center px-6 py-2.5 text-sm text-gray-700 hover:bg-gray-100 rounded-lg border border-gray-300 font-medium transition-colors">
{{ __('إلغاء') }}
</a>
<button type="button"
wire:click="save"
wire:loading.attr="disabled"
wire:target="save"
@if(!empty($scheduleConflicts))
wire:confirm="{{ __('يوجد تعارض في الجدول. هل تريد المتابعة؟') }}"
@endif
@if($participantData && $participantData['is_blocked'])
disabled
@endif
class="text-center px-6 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center justify-center gap-2">
<span wire:loading.remove wire:target="save">
<svg class="w-4 h-4 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
</svg>
{{ __('تسجيل في المجموعة') }}
</span>
<span wire:loading wire:target="save" class="flex items-center gap-2">
<svg class="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/>
</svg>
{{ __('جارٍ التسجيل...') }}
</span>
</button>
</div>
</div>
</form>
</div>
@endif
</div>
</div>
......@@ -4,22 +4,22 @@
</div>
{{-- Step Indicator --}}
<div class="flex items-center justify-center gap-2 mb-8">
<div class="flex items-center justify-center gap-1 sm:gap-2 mb-8 overflow-x-auto pb-2">
@foreach($stepLabels as $num => $label)
<button wire:click="goToStep({{ $num }})"
class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<span class="flex items-center justify-center w-8 h-8 rounded-full text-sm font-bold
class="flex items-center gap-1 sm:gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }} shrink-0">
<span class="flex items-center justify-center w-7 h-7 sm:w-8 sm:h-8 rounded-full text-xs sm:text-sm font-bold
{{ $num < $currentStep ? 'bg-green-500 text-white' : ($num === $currentStep ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-500') }}">
@if($num < $currentStep)
<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>
<svg class="w-3.5 h-3.5 sm:w-4 sm: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
</span>
<span class="hidden sm:inline text-sm {{ $num === $currentStep ? 'text-blue-700 font-medium' : 'text-gray-500' }}">{{ $label }}</span>
<span class="hidden lg:inline text-xs sm:text-sm {{ $num === $currentStep ? 'text-blue-700 font-medium' : 'text-gray-500' }}">{{ $label }}</span>
</button>
@if(!$loop->last)
<div class="w-8 h-0.5 {{ $num < $currentStep ? 'bg-green-400' : 'bg-gray-200' }}"></div>
<div class="w-4 sm:w-6 h-0.5 {{ $num < $currentStep ? 'bg-green-400' : 'bg-gray-200' }} shrink-0"></div>
@endif
@endforeach
</div>
......@@ -65,6 +65,9 @@ class="w-full text-start px-4 py-3 hover:bg-blue-50 transition-colors">
<div>
<p class="font-medium text-green-800">{{ $personNameAr }}</p>
<p class="text-sm text-green-600">{{ $personPhone }}</p>
@if($personEmail)
<p class="text-sm text-green-600" dir="ltr">{{ $personEmail }}</p>
@endif
</div>
<button wire:click="$set('personId', null)" class="text-sm text-red-600 hover:text-red-800">{{ __('تغيير') }}</button>
</div>
......@@ -88,6 +91,10 @@ class="w-full text-start px-4 py-3 hover:bg-blue-50 transition-colors">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الاسم بالإنجليزي') }}</label>
<input type="text" wire:model="personName" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('البريد الإلكتروني') }}</label>
<input type="email" wire:model="personEmail" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('رقم الهاتف') }} <span class="text-red-500">*</span></label>
<input type="text" wire:model="personPhone" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
......@@ -159,8 +166,138 @@ class="w-full text-start px-4 py-3 hover:bg-blue-50 transition-colors">
</div>
@endif
{{-- Step 3: Compensation --}}
{{-- Step 3: Activities & Sports --}}
@if($currentStep === 3)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('الأنشطة والرياضات') }}</h2>
<p class="text-sm text-gray-600 mb-4">{{ __('اختر الأنشطة التي يتخصص فيها المدرب وحدد التخصص الأساسي') }}</p>
@if($activities->isEmpty())
<div class="text-center py-8 text-gray-500">
<svg class="w-12 h-12 mx-auto mb-3 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
</svg>
<p>{{ __('لا توجد أنشطة مُفعّلة. يرجى إضافة أنشطة أولاً.') }}</p>
</div>
@else
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
@foreach($activities as $activity)
@php $isSelected = in_array($activity->id, $selectedActivities); @endphp
<div wire:click="toggleActivity({{ $activity->id }})"
class="relative p-4 border-2 rounded-xl cursor-pointer transition-all
{{ $isSelected ? 'border-blue-500 bg-blue-50 shadow-sm' : 'border-gray-200 hover:border-gray-300 hover:bg-gray-50' }}">
<div class="flex items-center gap-3">
@if($activity->icon)
<span class="text-2xl">{{ $activity->icon }}</span>
@else
<span class="flex items-center justify-center w-10 h-10 rounded-lg text-white text-sm font-bold"
style="background-color: {{ $activity->color ?? '#6B7280' }}">
{{ mb_substr($activity->name_ar, 0, 1) }}
</span>
@endif
<div class="flex-1">
<p class="font-medium text-gray-800 text-sm">{{ $activity->name_ar }}</p>
@if($activity->name)
<p class="text-xs text-gray-500" dir="ltr">{{ $activity->name }}</p>
@endif
</div>
@if($isSelected)
<svg class="w-5 h-5 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
</div>
{{-- Primary indicator --}}
@if($isSelected && $primaryActivityId === $activity->id)
<span class="absolute top-1 start-1 px-2 py-0.5 bg-blue-600 text-white text-[10px] font-bold rounded-full">
{{ __('أساسي') }}
</span>
@endif
</div>
@endforeach
</div>
{{-- Primary selection --}}
@if(count($selectedActivities) > 1)
<div class="mt-4 p-4 bg-gray-50 rounded-lg border border-gray-200">
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __('التخصص الأساسي') }} <span class="text-red-500">*</span></label>
<select wire:model="primaryActivityId" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@foreach($activities->whereIn('id', $selectedActivities) as $activity)
<option value="{{ $activity->id }}">{{ $activity->name_ar }}</option>
@endforeach
</select>
</div>
@endif
@endif
@error('selectedActivities') <p class="mt-2 text-xs text-red-600">{{ $message }}</p> @enderror
@error('primaryActivityId') <p class="mt-2 text-xs text-red-600">{{ $message }}</p> @enderror
@endif
{{-- Step 4: User Account --}}
@if($currentStep === 4)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('حساب الدخول') }}</h2>
<p class="text-sm text-gray-600 mb-4">{{ __('إنشاء حساب دخول يتيح للمدرب تسجيل الحضور وعرض جدول التدريبات والتعيين للمجموعات') }}</p>
<div class="p-4 bg-amber-50 border border-amber-200 rounded-lg mb-4">
<div class="flex items-start gap-2">
<svg class="w-5 h-5 text-amber-600 mt-0.5 shrink-0" 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>
<p class="text-sm text-amber-800">{{ __('بدون حساب دخول، لن يتمكن المدرب من تسجيل الدخول للنظام أو التعيين كمدرب رئيسي لمجموعة.') }}</p>
</div>
</div>
<label class="flex items-center gap-3 p-4 border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-50 transition-colors mb-4">
<input type="checkbox" wire:model.live="createUserAccount"
class="w-5 h-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<div>
<span class="font-medium text-gray-800">{{ __('إنشاء حساب دخول للمدرب') }}</span>
<p class="text-xs text-gray-500 mt-0.5">{{ __('سيتمكن المدرب من تسجيل الدخول وإدارة المجموعات المُعيّن لها') }}</p>
</div>
</label>
@if($createUserAccount)
<div class="space-y-4 border border-gray-200 rounded-lg p-4" x-data>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('البريد الإلكتروني') }} <span class="text-red-500">*</span></label>
<input type="email" wire:model="userEmail" dir="ltr"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
placeholder="trainer@example.com">
@error('userEmail') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="flex items-center gap-2 mb-2">
<input type="checkbox" wire:model.live="autoGeneratePassword"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<span class="text-sm text-gray-700">{{ __('توليد كلمة مرور تلقائياً') }}</span>
</label>
<div class="relative">
<input type="{{ $autoGeneratePassword ? 'text' : 'password' }}"
wire:model="userPassword" dir="ltr"
{{ $autoGeneratePassword ? 'readonly' : '' }}
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm font-mono {{ $autoGeneratePassword ? 'bg-gray-50' : '' }}"
placeholder="{{ __('كلمة المرور') }}">
</div>
@error('userPassword') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<label class="flex items-center gap-3 p-3 bg-blue-50 border border-blue-200 rounded-lg cursor-pointer">
<input type="checkbox" wire:model="sendCredentials"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<div>
<span class="text-sm font-medium text-blue-800">{{ __('إرسال بيانات الدخول بالبريد الإلكتروني') }}</span>
<p class="text-xs text-blue-600 mt-0.5">{{ __('سيتم إرسال رسالة تحتوي على البريد وكلمة المرور') }}</p>
</div>
</label>
</div>
@endif
@endif
{{-- Step 5: Compensation --}}
@if($currentStep === 5)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('التعويض') }}</h2>
<div class="space-y-4">
<div>
......@@ -231,9 +368,10 @@ class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:r
</div>
@endif
{{-- Step 4: Availability --}}
@if($currentStep === 4)
{{-- Step 6: Availability --}}
@if($currentStep === 6)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('أوقات التوفر') }}</h2>
<p class="text-sm text-gray-600 mb-4">{{ __('حدد الأيام والأوقات التي يكون فيها المدرب متاحاً للتدريب') }}</p>
<div class="space-y-3">
@foreach($availabilities as $index => $day)
<div class="flex items-center gap-3 p-3 border border-gray-200 rounded-lg {{ $day['enabled'] ? 'bg-blue-50 border-blue-200' : '' }}">
......@@ -260,8 +398,8 @@ class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:
</div>
@endif
{{-- Step 5: Qualifications --}}
@if($currentStep === 5)
{{-- Step 7: Qualifications --}}
@if($currentStep === 7)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('المؤهلات والشهادات') }}</h2>
<div class="space-y-4">
@foreach($qualifications as $index => $qual)
......@@ -303,55 +441,249 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 f
</div>
@endif
{{-- Step 6: Review --}}
@if($currentStep === 6 && !$completed)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('مراجعة وتأكيد') }}</h2>
<dl class="space-y-3">
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الاسم') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $personNameAr }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الهاتف') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ $personPhone }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('المسمى الوظيفي') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $position }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('نوع التوظيف') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ \App\Domain\HR\Enums\EmploymentType::from($employmentType)->label() }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('نموذج التعويض') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ \App\Domain\HR\Enums\CompensationModel::from($compensationModel)->label() }}</dd>
</div>
@if($hourlyRate)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('سعر الساعة') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ number_format((float)$hourlyRate, 2) }} {{ __('ج.م') }}</dd>
{{-- Step 8: Initial Assignment --}}
@if($currentStep === 8)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('التعيين المبدئي') }}</h2>
<p class="text-sm text-gray-600 mb-4">{{ __('يمكنك تعيين المدرب لمجموعة تدريبية حالية (اختياري)') }}</p>
@if(!$createUserAccount)
<div class="p-4 bg-amber-50 border border-amber-200 rounded-lg mb-4">
<div class="flex items-start gap-2">
<svg class="w-5 h-5 text-amber-600 mt-0.5 shrink-0" 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>
<p class="text-sm text-amber-800">{{ __('لم يتم إنشاء حساب دخول للمدرب. التعيين للمجموعات يتطلب حساب مستخدم. يمكنك تخطي هذه الخطوة وتعيينه لاحقاً بعد إنشاء الحساب.') }}</p>
</div>
</div>
@else
<label class="flex items-center gap-3 p-4 border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-50 transition-colors mb-4">
<input type="checkbox" wire:model.live="assignToGroup"
class="w-5 h-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<div>
<span class="font-medium text-gray-800">{{ __('تعيين المدرب لمجموعة حالية') }}</span>
<p class="text-xs text-gray-500 mt-0.5">{{ __('سيتم إنشاء تعيين نشط للمدرب في المجموعة المختارة') }}</p>
</div>
</label>
@if($assignToGroup)
<div class="space-y-4 border border-gray-200 rounded-lg p-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('المجموعة') }} <span class="text-red-500">*</span></label>
@if($this->availableGroups->isEmpty())
<div class="p-3 bg-gray-50 rounded-lg text-sm text-gray-500 text-center">
{{ __('لا توجد مجموعات متاحة للأنشطة المختارة') }}
</div>
@else
<select wire:model="assignToGroupId" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('اختر المجموعة...') }}</option>
@foreach($this->availableGroups as $group)
<option value="{{ $group->id }}">
{{ $group->name_ar }}
({{ $group->program?->name_ar }})
— {{ $group->current_count }}/{{ $group->max_capacity }}
</option>
@endforeach
</select>
@endif
@error('assignToGroupId') <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">{{ __('نطاق التعيين') }} <span class="text-red-500">*</span></label>
<select wire:model="assignmentScope" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@foreach($assignmentScopes as $scope)
<option value="{{ $scope->value }}">{{ $scope->label() }}</option>
@endforeach
</select>
@error('assignmentScope') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
</div>
@endif
@if($sessionRate)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('سعر الحصة') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ number_format((float)$sessionRate, 2) }} {{ __('ج.م') }}</dd>
@endif
@endif
{{-- Step 9: Review --}}
@if($currentStep === 9 && !$completed)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('مراجعة وتأكيد') }}</h2>
{{-- Personal Info Card --}}
<div class="mb-4 p-4 bg-gray-50 rounded-lg border border-gray-200">
<h3 class="text-sm font-bold text-gray-700 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="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"/></svg>
{{ __('البيانات الشخصية') }}
</h3>
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<div>
<dt class="text-xs text-gray-500">{{ __('الاسم') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $personNameAr }}</dd>
</div>
<div>
<dt class="text-xs text-gray-500">{{ __('الهاتف') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ $personPhone }}</dd>
</div>
@if($personGender)
<div>
<dt class="text-xs text-gray-500">{{ __('الجنس') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $personGender === 'male' ? __('ذكر') : __('أنثى') }}</dd>
</div>
@endif
</dl>
</div>
{{-- Employment Card --}}
<div class="mb-4 p-4 bg-gray-50 rounded-lg border border-gray-200">
<h3 class="text-sm font-bold text-gray-700 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="M6 6V5a3 3 0 013-3h2a3 3 0 013 3v1h2a2 2 0 012 2v3.57A22.952 22.952 0 0110 13a22.95 22.95 0 01-8-1.43V8a2 2 0 012-2h2zm2-1a1 1 0 011-1h2a1 1 0 011 1v1H8V5zm-2 5a1 1 0 100 2h8a1 1 0 100-2H6z" clip-rule="evenodd"/></svg>
{{ __('التوظيف') }}
</h3>
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<div>
<dt class="text-xs text-gray-500">{{ __('المسمى الوظيفي') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $position }}</dd>
</div>
<div>
<dt class="text-xs text-gray-500">{{ __('نوع التوظيف') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ \App\Domain\HR\Enums\EmploymentType::from($employmentType)->label() }}</dd>
</div>
<div>
<dt class="text-xs text-gray-500">{{ __('تاريخ البدء') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ $startDate }}</dd>
</div>
<div>
<dt class="text-xs text-gray-500">{{ __('القسم') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $department }}</dd>
</div>
</dl>
</div>
{{-- Activities Card --}}
<div class="mb-4 p-4 bg-gray-50 rounded-lg border border-gray-200">
<h3 class="text-sm font-bold text-gray-700 mb-2 flex items-center gap-2">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z"/></svg>
{{ __('الأنشطة والرياضات') }}
</h3>
<div class="flex flex-wrap gap-2">
@foreach($activities->whereIn('id', $selectedActivities) as $activity)
<span class="inline-flex items-center gap-1 px-3 py-1 rounded-full text-sm
{{ $activity->id === $primaryActivityId ? 'bg-blue-100 text-blue-800 font-medium' : 'bg-gray-200 text-gray-700' }}">
@if($activity->icon) {{ $activity->icon }} @endif
{{ $activity->name_ar }}
@if($activity->id === $primaryActivityId)
<span class="text-[10px] bg-blue-600 text-white px-1.5 py-0.5 rounded-full">{{ __('أساسي') }}</span>
@endif
</span>
@endforeach
</div>
</div>
{{-- User Account Card --}}
<div class="mb-4 p-4 rounded-lg border {{ $createUserAccount ? 'bg-green-50 border-green-200' : 'bg-gray-50 border-gray-200' }}">
<h3 class="text-sm font-bold text-gray-700 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="M18 8a6 6 0 01-7.743 5.743L10 14l-1 1-1 1H6v2H2v-4l4.257-4.257A6 6 0 1118 8zm-6-4a1 1 0 100 2 2 2 0 012 2 1 1 0 102 0 4 4 0 00-4-4z" clip-rule="evenodd"/></svg>
{{ __('حساب الدخول') }}
</h3>
@if($createUserAccount)
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<div>
<dt class="text-xs text-gray-500">{{ __('البريد الإلكتروني') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ $userEmail }}</dd>
</div>
<div>
<dt class="text-xs text-gray-500">{{ __('إرسال البيانات') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $sendCredentials ? __('نعم') : __('لا') }}</dd>
</div>
</dl>
@else
<p class="text-sm text-gray-500">{{ __('لن يتم إنشاء حساب دخول') }}</p>
@endif
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('أيام التوفر') }}</dt>
<dd class="text-sm font-medium text-gray-800">
{{ collect($availabilities)->filter(fn($a) => $a['enabled'])->pluck('day_name')->join('، ') ?: __('لم يتم التحديد') }}
</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('المؤهلات') }}</dt>
<dd class="text-sm font-medium text-gray-800">
{{ collect($qualifications)->filter(fn($q) => !empty($q['name']))->count() }} {{ __('شهادة') }}
</dd>
</div>
{{-- Compensation Card --}}
<div class="mb-4 p-4 bg-gray-50 rounded-lg border border-gray-200">
<h3 class="text-sm font-bold text-gray-700 mb-2 flex items-center gap-2">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path d="M4 4a2 2 0 00-2 2v1h16V6a2 2 0 00-2-2H4z"/><path fill-rule="evenodd" d="M18 9H2v5a2 2 0 002 2h12a2 2 0 002-2V9zM4 13a1 1 0 011-1h1a1 1 0 110 2H5a1 1 0 01-1-1zm5-1a1 1 0 100 2h1a1 1 0 100-2H9z" clip-rule="evenodd"/></svg>
{{ __('التعويض') }}
</h3>
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<div>
<dt class="text-xs text-gray-500">{{ __('نموذج التعويض') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ \App\Domain\HR\Enums\CompensationModel::from($compensationModel)->label() }}</dd>
</div>
@if($hourlyRate)
<div>
<dt class="text-xs text-gray-500">{{ __('سعر الساعة') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ number_format((float)$hourlyRate, 2) }} {{ __('ج.م') }}</dd>
</div>
@endif
@if($sessionRate)
<div>
<dt class="text-xs text-gray-500">{{ __('سعر الحصة') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ number_format((float)$sessionRate, 2) }} {{ __('ج.م') }}</dd>
</div>
@endif
@if($groupRate)
<div>
<dt class="text-xs text-gray-500">{{ __('سعر المجموعة') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ number_format((float)$groupRate, 2) }} {{ __('ج.م') }}</dd>
</div>
@endif
@if($playerRate)
<div>
<dt class="text-xs text-gray-500">{{ __('سعر اللاعب') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ number_format((float)$playerRate, 2) }} {{ __('ج.م') }}</dd>
</div>
@endif
@if($revenueSharePercent)
<div>
<dt class="text-xs text-gray-500">{{ __('نسبة الإيرادات') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ $revenueSharePercent }}%</dd>
</div>
@endif
</dl>
</div>
{{-- Availability & Qualifications Summary --}}
<div class="mb-4 p-4 bg-gray-50 rounded-lg border border-gray-200">
<h3 class="text-sm font-bold text-gray-700 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="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd"/></svg>
{{ __('التوفر والمؤهلات') }}
</h3>
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<div>
<dt class="text-xs text-gray-500">{{ __('أيام التوفر') }}</dt>
<dd class="text-sm font-medium text-gray-800">
{{ collect($availabilities)->filter(fn($a) => $a['enabled'])->pluck('day_name')->join('، ') ?: __('لم يتم التحديد') }}
</dd>
</div>
<div>
<dt class="text-xs text-gray-500">{{ __('المؤهلات') }}</dt>
<dd class="text-sm font-medium text-gray-800">
{{ collect($qualifications)->filter(fn($q) => !empty($q['name']))->count() }} {{ __('شهادة') }}
</dd>
</div>
</dl>
</div>
{{-- Assignment Card --}}
@if($createUserAccount && $assignToGroup && $assignToGroupId)
<div class="mb-4 p-4 bg-blue-50 rounded-lg border border-blue-200">
<h3 class="text-sm font-bold text-gray-700 mb-2 flex items-center gap-2">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z"/></svg>
{{ __('التعيين المبدئي') }}
</h3>
@php $selectedGroup = $this->availableGroups->firstWhere('id', $assignToGroupId); @endphp
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<div>
<dt class="text-xs text-gray-500">{{ __('المجموعة') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $selectedGroup?->name_ar ?? '-' }}</dd>
</div>
<div>
<dt class="text-xs text-gray-500">{{ __('نطاق التعيين') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ \App\Domain\Scheduling\Enums\AssignmentScope::from($assignmentScope)->label() }}</dd>
</div>
</dl>
</div>
</dl>
@endif
@endif
{{-- Success State --}}
......@@ -363,7 +695,13 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 f
</svg>
</div>
<h3 class="text-lg font-semibold text-gray-800 mb-2">{{ __('تم إضافة المدرب بنجاح') }}</h3>
<p class="text-sm text-gray-600 mb-6">{{ __('يمكنك الآن عرض بيانات المدرب أو إضافة مدرب آخر') }}</p>
<p class="text-sm text-gray-600 mb-6">
@if($createUserAccount)
{{ __('تم إنشاء حساب الدخول بنجاح. يمكن للمدرب تسجيل الدخول الآن.') }}
@else
{{ __('يمكنك إنشاء حساب دخول للمدرب لاحقاً من صفحة التعديل.') }}
@endif
</p>
<div class="flex items-center justify-center gap-3">
<a href="{{ route('trainers.index') }}" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors">
{{ __('قائمة المدربين') }}
......@@ -388,13 +726,15 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 f
</div>
<div>
@if($currentStep < $totalSteps)
<button wire:click="nextStep" class="px-5 py-2.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
{{ __('التالي') }}
<button wire:click="nextStep" wire:loading.attr="disabled" wire:target="nextStep"
class="px-5 py-2.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors disabled:opacity-50">
<span wire:loading.remove wire:target="nextStep">{{ __('التالي') }}</span>
<span wire:loading wire:target="nextStep">{{ __('جارٍ التحقق...') }}</span>
</button>
@elseif($currentStep === $totalSteps)
<button wire:click="confirm" wire:loading.attr="disabled" wire:target="confirm"
class="px-5 py-2.5 bg-green-600 text-white rounded-lg text-sm font-medium hover:bg-green-700 transition-colors disabled:opacity-50">
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد') }}</span>
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد وإنشاء المدرب') }}</span>
<span wire:loading wire:target="confirm">{{ __('جارٍ الحفظ...') }}</span>
</button>
@endif
......
<div>
<div class="flex items-center justify-between mb-6">
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('إنشاء برنامج تدريبي') }}</h1>
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('إنشاء برنامج تدريبي متكامل') }}</h1>
</div>
{{-- Step Indicator --}}
<div class="flex items-center justify-center gap-2 mb-8">
<div class="flex items-center justify-center gap-1 sm:gap-2 mb-8 overflow-x-auto pb-2">
@foreach($stepLabels as $num => $label)
<button wire:click="goToStep({{ $num }})"
class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<span class="flex items-center justify-center w-8 h-8 rounded-full text-sm font-bold
class="flex items-center gap-1 sm:gap-2 flex-shrink-0 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<span class="flex items-center justify-center w-7 h-7 sm:w-8 sm:h-8 rounded-full text-xs sm:text-sm font-bold
{{ $num < $currentStep ? 'bg-green-500 text-white' : ($num === $currentStep ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-500') }}">
@if($num < $currentStep)
<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>
<svg class="w-3.5 h-3.5 sm:w-4 sm: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
</span>
<span class="hidden sm:inline text-sm {{ $num === $currentStep ? 'text-blue-700 font-medium' : 'text-gray-500' }}">{{ $label }}</span>
<span class="hidden lg:inline text-xs sm:text-sm {{ $num === $currentStep ? 'text-blue-700 font-medium' : 'text-gray-500' }}">{{ $label }}</span>
</button>
@if(!$loop->last)
<div class="w-8 h-0.5 {{ $num < $currentStep ? 'bg-green-400' : 'bg-gray-200' }}"></div>
<div class="w-4 sm:w-6 h-0.5 flex-shrink-0 {{ $num < $currentStep ? 'bg-green-400' : 'bg-gray-200' }}"></div>
@endif
@endforeach
</div>
......@@ -30,9 +30,13 @@ class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'curs
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
{{-- Step 1: Basic Info --}}
{{-- ═══════════════════════════════════════════════════════════════════
Step 1: Basic Info
═══════════════════════════════════════════════════════════════════ --}}
@if($currentStep === 1)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('المعلومات الأساسية') }}</h2>
<h2 class="text-lg font-semibold text-gray-800 mb-1">{{ __('المعلومات الأساسية') }}</h2>
<p class="text-sm text-gray-500 mb-5">{{ __('أدخل بيانات البرنامج التدريبي الأساسية') }}</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('اسم البرنامج بالعربي') }} <span class="text-red-500">*</span></label>
......@@ -55,7 +59,7 @@ class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'curs
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الفرع') }} <span class="text-red-500">*</span></label>
<select wire:model="branchId" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<select wire:model.live="branchId" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('اختر الفرع...') }}</option>
@foreach($branches as $branch)
<option value="{{ $branch->id }}">{{ $branch->name_ar }}</option>
......@@ -93,39 +97,20 @@ class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:r
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('ageMax') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الوصف') }}</label>
<textarea wire:model="descriptionAr" rows="3"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"></textarea>
</div>
</div>
@endif
{{-- Step 2: Schedule & Duration --}}
@if($currentStep === 2)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('الجدول والمدة') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
{{-- Duration & Capacity --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('عدد الحصص في الأسبوع') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="sessionsPerWeek" dir="ltr" min="1" max="14"
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('مدة البرنامج (أسابيع)') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="programDurationWeeks" dir="ltr" min="1" max="104"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('sessionsPerWeek') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
@error('programDurationWeeks') <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">{{ __('مدة الحصة (بالدقائق)') }} <span class="text-red-500">*</span></label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('مدة الحصة (دقيقة)') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="sessionDurationMinutes" dir="ltr" min="15" max="300" step="5"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('sessionDurationMinutes') <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">{{ __('مدة البرنامج (بالأسابيع)') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="programDurationWeeks" dir="ltr" min="1" max="104"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('programDurationWeeks') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
{{-- Spacer --}}
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الحد الأدنى للمشتركين') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="minParticipants" dir="ltr" min="1"
......@@ -138,12 +123,179 @@ class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:r
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('maxParticipants') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الوصف') }}</label>
<textarea wire:model="descriptionAr" rows="3"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"></textarea>
</div>
</div>
@endif
{{-- ═══════════════════════════════════════════════════════════════════
Step 2: Head Trainer
═══════════════════════════════════════════════════════════════════ --}}
@if($currentStep === 2)
<h2 class="text-lg font-semibold text-gray-800 mb-1">{{ __('المدرب الرئيسي') }}</h2>
<p class="text-sm text-gray-500 mb-5">{{ __('من هو المدرب الرئيسي لهذا البرنامج؟') }}</p>
<div class="max-w-lg">
@if(!$skipTrainer)
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('اختر المدرب') }} <span class="text-red-500">*</span></label>
<select wire:model="headTrainerId" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('اختر المدرب...') }}</option>
@foreach($trainers as $trainer)
<option value="{{ $trainer->id }}">{{ $trainer->name_ar ?: $trainer->name }}</option>
@endforeach
</select>
@error('headTrainerId') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
@if($headTrainerId)
<div class="mt-3 p-3 bg-blue-50 border border-blue-100 rounded-lg">
<div class="flex items-center gap-2">
<div class="w-8 h-8 bg-blue-200 rounded-full flex items-center justify-center">
<svg class="w-4 h-4 text-blue-700" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"/></svg>
</div>
<span class="text-sm font-medium text-blue-800">{{ $trainers->firstWhere('id', $headTrainerId)?->name_ar ?: $trainers->firstWhere('id', $headTrainerId)?->name }}</span>
</div>
</div>
@endif
@else
<div class="p-4 bg-amber-50 border border-amber-200 rounded-lg">
<p class="text-sm text-amber-800">{{ __('سيتم تخطي تعيين المدرب الرئيسي. يمكنك تعيينه لاحقاً من إعدادات المجموعة.') }}</p>
</div>
@endif
<div class="mt-4">
<button wire:click="toggleSkipTrainer" type="button"
class="text-sm text-gray-600 hover:text-gray-800 underline underline-offset-2 transition-colors">
{{ $skipTrainer ? __('تعيين مدرب الآن') : __('تخطي - سأعين المدرب لاحقاً') }}
</button>
</div>
</div>
@endif
{{-- Step 3: Pricing --}}
{{-- ═══════════════════════════════════════════════════════════════════
Step 3: Schedule Template
═══════════════════════════════════════════════════════════════════ --}}
@if($currentStep === 3)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('التسعير') }}</h2>
<h2 class="text-lg font-semibold text-gray-800 mb-1">{{ __('جدول التدريب') }}</h2>
<p class="text-sm text-gray-500 mb-5">{{ __('حدد أيام ومواعيد التدريب الأسبوعية') }}</p>
<div class="space-y-3">
@foreach($scheduleRows as $index => $row)
<div class="flex flex-wrap items-end gap-3 p-4 bg-gray-50 border border-gray-200 rounded-lg" wire:key="schedule-{{ $index }}">
<div class="flex-1 min-w-[140px]">
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('اليوم') }}</label>
<select wire:model="scheduleRows.{{ $index }}.day_of_week"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="0">{{ __('الأحد') }}</option>
<option value="1">{{ __('الاثنين') }}</option>
<option value="2">{{ __('الثلاثاء') }}</option>
<option value="3">{{ __('الأربعاء') }}</option>
<option value="4">{{ __('الخميس') }}</option>
<option value="5">{{ __('الجمعة') }}</option>
<option value="6">{{ __('السبت') }}</option>
</select>
</div>
<div class="w-32">
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('من') }}</label>
<input type="time" wire:model="scheduleRows.{{ $index }}.start_time" dir="ltr"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
</div>
<div class="w-32">
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('إلى') }}</label>
<input type="time" wire:model="scheduleRows.{{ $index }}.end_time" dir="ltr"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
</div>
<div>
@if(count($scheduleRows) > 1)
<button wire:click="removeScheduleRow({{ $index }})" type="button"
class="p-2 text-red-500 hover:text-red-700 hover:bg-red-50 rounded-lg transition-colors">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>
</button>
@endif
</div>
</div>
@endforeach
</div>
@error('scheduleRows') <p class="mt-2 text-xs text-red-600">{{ $message }}</p> @enderror
@error('scheduleRows.*') <p class="mt-2 text-xs text-red-600">{{ $message }}</p> @enderror
<button wire:click="addScheduleRow" type="button"
class="mt-4 flex items-center gap-2 px-4 py-2 text-sm text-blue-700 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition-colors">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd"/></svg>
{{ __('إضافة يوم تدريب') }}
</button>
<div class="mt-4 p-3 bg-gray-100 rounded-lg">
<p class="text-xs text-gray-600">
{{ __('عدد الحصص في الأسبوع:') }} <span class="font-bold">{{ count($scheduleRows) }}</span>
{{ __('حصة') }}
</p>
</div>
@endif
{{-- ═══════════════════════════════════════════════════════════════════
Step 4: Initial Group
═══════════════════════════════════════════════════════════════════ --}}
@if($currentStep === 4)
<h2 class="text-lg font-semibold text-gray-800 mb-1">{{ __('المجموعة التدريبية الأولى') }}</h2>
<p class="text-sm text-gray-500 mb-5">{{ __('إنشاء مجموعة تدريب أولى للبرنامج') }}</p>
<div class="mb-5">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" wire:model.live="createInitialGroup" class="sr-only peer">
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-100 rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
<span class="ms-3 text-sm font-medium text-gray-700">{{ __('إنشاء مجموعة تدريب أولى') }}</span>
</label>
</div>
@if($createInitialGroup)
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 p-4 bg-blue-50/50 border border-blue-100 rounded-lg">
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('اسم المجموعة') }} <span class="text-red-500">*</span></label>
<input type="text" wire:model="groupName"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm bg-white">
@error('groupName') <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">{{ __('كود المجموعة') }} <span class="text-red-500">*</span></label>
<input type="text" wire:model="groupCode" dir="ltr" maxlength="10"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm bg-white font-mono uppercase">
@error('groupCode') <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">{{ __('السعة القصوى') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="groupCapacity" dir="ltr" min="1"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm bg-white">
@error('groupCapacity') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
@if($headTrainerId)
<div class="sm:col-span-2">
<div class="flex items-center gap-2 p-2 bg-green-50 border border-green-100 rounded-lg">
<svg class="w-4 h-4 text-green-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>
<span class="text-xs text-green-800">{{ __('المدرب الرئيسي سيتم تعيينه تلقائياً من الخطوة السابقة') }}</span>
</div>
</div>
@endif
</div>
@else
<div class="p-4 bg-gray-50 border border-gray-200 rounded-lg">
<p class="text-sm text-gray-600">{{ __('لن يتم إنشاء مجموعة. يمكنك إنشاء مجموعات لاحقاً من شاشة المجموعات.') }}</p>
</div>
@endif
@endif
{{-- ═══════════════════════════════════════════════════════════════════
Step 5: Pricing
═══════════════════════════════════════════════════════════════════ --}}
@if($currentStep === 5)
<h2 class="text-lg font-semibold text-gray-800 mb-1">{{ __('التسعير') }}</h2>
<p class="text-sm text-gray-500 mb-5">{{ __('حدد السعر الأساسي للاشتراك في البرنامج') }}</p>
<div class="max-w-md">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('السعر الأساسي') }} <span class="text-red-500">*</span></label>
<div class="relative">
......@@ -152,62 +304,226 @@ class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:r
<span class="absolute end-3 top-1/2 -translate-y-1/2 text-sm text-gray-500">{{ __('ج.م') }}</span>
</div>
@error('basePrice') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
<p class="mt-2 text-xs text-gray-500">{{ __('هذا هو السعر الأساسي للاشتراك الشهري. يمكن تعديل السعر لاحقاً من إعدادات التسعير.') }}</p>
</div>
@endif
{{-- Step 4: Review --}}
@if($currentStep === 4 && !$completed)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('مراجعة وتأكيد') }}</h2>
<dl class="space-y-3">
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('اسم البرنامج') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $nameAr }}</dd>
</div>
@if($activityId)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('النشاط') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $activities->firstWhere('id', $activityId)?->name_ar }}</dd>
<div class="mt-4 p-3 bg-amber-50 border border-amber-100 rounded-lg">
<p class="text-xs text-amber-800 font-medium mb-1">{{ __('هذا هو السعر الأساسي قبل أي خصومات') }}</p>
<p class="text-xs text-amber-700">{{ __('قواعد التسعير والخصومات يتم إعدادها بشكل منفصل من شاشة محرك التسعير') }}</p>
</div>
@if($basePrice && is_numeric($basePrice) && $basePrice > 0)
<div class="mt-3 p-3 bg-green-50 border border-green-100 rounded-lg">
<p class="text-xs text-green-800">
{{ __('سيتم حفظ السعر:') }}
<span class="font-bold" dir="ltr">{{ number_format((float)$basePrice, 2) }} {{ __('ج.م') }}</span>
<span class="text-green-600">({{ number_format((float)$basePrice * 100, 0) }} {{ __('قرش') }})</span>
</p>
</div>
@endif
@if($branchId)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الفرع') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $branches->firstWhere('id', $branchId)?->name_ar }}</dd>
</div>
@endif
{{-- ═══════════════════════════════════════════════════════════════════
Step 6: Facility
═══════════════════════════════════════════════════════════════════ --}}
@if($currentStep === 6)
<h2 class="text-lg font-semibold text-gray-800 mb-1">{{ __('المنشأة الرياضية') }}</h2>
<p class="text-sm text-gray-500 mb-5">{{ __('هل تريد تحديد المنشأة الرياضية لهذا البرنامج؟') }}</p>
<div class="mb-5">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" wire:model.live="assignFacility" class="sr-only peer">
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-100 rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
<span class="ms-3 text-sm font-medium text-gray-700">{{ __('تحديد منشأة للتدريب') }}</span>
</label>
</div>
@if($assignFacility)
<div class="max-w-lg">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('المنشأة') }} <span class="text-red-500">*</span></label>
<select wire:model="facilityId" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('اختر المنشأة...') }}</option>
@foreach($facilities as $facility)
<option value="{{ $facility->id }}">{{ $facility->name_ar }} ({{ $facility->type?->value }})</option>
@endforeach
</select>
@error('facilityId') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
@if($facilities->isEmpty())
<p class="mt-2 text-xs text-amber-600">{{ __('لا توجد منشآت نشطة في الفرع المختار') }}</p>
@endif
</div>
@endif
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الحصص') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $sessionsPerWeek }} {{ __('حصة/أسبوع') }} - {{ $sessionDurationMinutes }} {{ __('دقيقة') }}</dd>
@else
<div class="p-4 bg-gray-50 border border-gray-200 rounded-lg">
<p class="text-sm text-gray-600">{{ __('يمكنك تحديد المنشأة لاحقاً عند تعديل الجدول التدريبي.') }}</p>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('مدة البرنامج') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $programDurationWeeks }} {{ __('أسبوع') }}</dd>
@endif
@endif
{{-- ═══════════════════════════════════════════════════════════════════
Step 7: Review & Confirm
═══════════════════════════════════════════════════════════════════ --}}
@if($currentStep === 7 && !$completed)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('مراجعة وتأكيد') }}</h2>
<p class="text-sm text-gray-500 mb-5">{{ __('راجع جميع البيانات قبل الإنشاء') }}</p>
<div class="space-y-4">
{{-- Program Info Card --}}
<div class="p-4 bg-gray-50 border border-gray-200 rounded-lg">
<h3 class="text-sm font-semibold text-gray-700 mb-3 flex items-center gap-2">
<svg class="w-4 h-4 text-blue-600" fill="currentColor" viewBox="0 0 20 20"><path d="M9 4.804A7.968 7.968 0 005.5 4c-1.255 0-2.443.29-3.5.804v10A7.969 7.969 0 015.5 14c1.669 0 3.218.51 4.5 1.385A7.962 7.962 0 0114.5 14c1.255 0 2.443.29 3.5.804v-10A7.968 7.968 0 0014.5 4c-1.255 0-2.443.29-3.5.804V12a1 1 0 11-2 0V4.804z"/></svg>
{{ __('البرنامج التدريبي') }}
</h3>
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-2">
<div class="flex justify-between sm:justify-start sm:gap-2">
<dt class="text-xs text-gray-500">{{ __('الاسم:') }}</dt>
<dd class="text-xs font-medium text-gray-800">{{ $nameAr }}</dd>
</div>
@if($activityId)
<div class="flex justify-between sm:justify-start sm:gap-2">
<dt class="text-xs text-gray-500">{{ __('النشاط:') }}</dt>
<dd class="text-xs font-medium text-gray-800">{{ $activities->firstWhere('id', $activityId)?->name_ar }}</dd>
</div>
@endif
@if($branchId)
<div class="flex justify-between sm:justify-start sm:gap-2">
<dt class="text-xs text-gray-500">{{ __('الفرع:') }}</dt>
<dd class="text-xs font-medium text-gray-800">{{ $branches->firstWhere('id', $branchId)?->name_ar }}</dd>
</div>
@endif
<div class="flex justify-between sm:justify-start sm:gap-2">
<dt class="text-xs text-gray-500">{{ __('المدة:') }}</dt>
<dd class="text-xs font-medium text-gray-800">{{ $programDurationWeeks }} {{ __('أسبوع') }}</dd>
</div>
<div class="flex justify-between sm:justify-start sm:gap-2">
<dt class="text-xs text-gray-500">{{ __('المشتركين:') }}</dt>
<dd class="text-xs font-medium text-gray-800">{{ $minParticipants }} - {{ $maxParticipants }}</dd>
</div>
@if($gender && $gender !== 'all')
<div class="flex justify-between sm:justify-start sm:gap-2">
<dt class="text-xs text-gray-500">{{ __('الجنس:') }}</dt>
<dd class="text-xs font-medium text-gray-800">{{ $gender === 'male' ? __('ذكور') : __('إناث') }}</dd>
</div>
@endif
@if($ageMin || $ageMax)
<div class="flex justify-between sm:justify-start sm:gap-2">
<dt class="text-xs text-gray-500">{{ __('العمر:') }}</dt>
<dd class="text-xs font-medium text-gray-800">{{ $ageMin ?: '?' }} - {{ $ageMax ?: '?' }} {{ __('سنة') }}</dd>
</div>
@endif
</dl>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('المشتركين') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $minParticipants }} - {{ $maxParticipants }}</dd>
{{-- Trainer Card --}}
<div class="p-4 bg-gray-50 border border-gray-200 rounded-lg">
<h3 class="text-sm font-semibold text-gray-700 mb-3 flex items-center gap-2">
<svg class="w-4 h-4 text-green-600" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"/></svg>
{{ __('المدرب الرئيسي') }}
</h3>
@if($headTrainerId)
<p class="text-sm text-gray-800 font-medium">{{ $trainers->firstWhere('id', $headTrainerId)?->name_ar ?: $trainers->firstWhere('id', $headTrainerId)?->name }}</p>
@else
<p class="text-sm text-amber-600">{{ __('لم يتم تعيين مدرب - سيتم التعيين لاحقاً') }}</p>
@endif
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('السعر الأساسي') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ number_format((float)$basePrice, 2) }} {{ __('ج.م') }}</dd>
{{-- Schedule Card --}}
<div class="p-4 bg-gray-50 border border-gray-200 rounded-lg">
<h3 class="text-sm font-semibold text-gray-700 mb-3 flex items-center gap-2">
<svg class="w-4 h-4 text-purple-600" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd"/></svg>
{{ __('جدول التدريب') }} ({{ count($scheduleRows) }} {{ __('حصة/أسبوع') }})
</h3>
@php
$dayNames = ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'];
@endphp
<div class="space-y-1">
@foreach($scheduleRows as $row)
<div class="flex items-center gap-3 text-sm">
<span class="text-gray-700 font-medium w-20">{{ $dayNames[(int)$row['day_of_week']] ?? '' }}</span>
<span class="text-gray-600" dir="ltr">{{ $row['start_time'] }} - {{ $row['end_time'] }}</span>
</div>
@endforeach
</div>
</div>
@if($gender)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الجنس') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $gender === 'male' ? __('ذكور فقط') : __('إناث فقط') }}</dd>
{{-- Group Card --}}
@if($createInitialGroup)
<div class="p-4 bg-gray-50 border border-gray-200 rounded-lg">
<h3 class="text-sm font-semibold text-gray-700 mb-3 flex items-center gap-2">
<svg class="w-4 h-4 text-indigo-600" fill="currentColor" viewBox="0 0 20 20"><path d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v1h8v-1zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-1a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 17v1h-3zM4.75 14.094A5.973 5.973 0 004 17v1H1v-1a3 3 0 013.75-2.906z"/></svg>
{{ __('المجموعة الأولى') }}
</h3>
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-1">
<div class="flex gap-2">
<dt class="text-xs text-gray-500">{{ __('الاسم:') }}</dt>
<dd class="text-xs font-medium text-gray-800">{{ $groupName }}</dd>
</div>
<div class="flex gap-2">
<dt class="text-xs text-gray-500">{{ __('الكود:') }}</dt>
<dd class="text-xs font-medium text-gray-800 font-mono" dir="ltr">{{ $groupCode }}</dd>
</div>
<div class="flex gap-2">
<dt class="text-xs text-gray-500">{{ __('السعة:') }}</dt>
<dd class="text-xs font-medium text-gray-800">{{ $groupCapacity }} {{ __('مشترك') }}</dd>
</div>
<div class="flex gap-2">
<dt class="text-xs text-gray-500">{{ __('الحالة:') }}</dt>
<dd class="text-xs font-medium text-blue-700">{{ __('قيد التشكيل') }}</dd>
</div>
</dl>
</div>
@endif
@if($ageMin || $ageMax)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الفئة العمرية') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $ageMin ?: '?' }} - {{ $ageMax ?: '?' }} {{ __('سنة') }}</dd>
{{-- Pricing Card --}}
<div class="p-4 bg-gray-50 border border-gray-200 rounded-lg">
<h3 class="text-sm font-semibold text-gray-700 mb-3 flex items-center gap-2">
<svg class="w-4 h-4 text-emerald-600" fill="currentColor" viewBox="0 0 20 20"><path d="M8.433 7.418c.155-.103.346-.196.567-.267v1.698a2.305 2.305 0 01-.567-.267C8.07 8.34 8 8.114 8 8c0-.114.07-.34.433-.582zM11 12.849v-1.698c.22.071.412.164.567.267.364.243.433.468.433.582 0 .114-.07.34-.433.582a2.305 2.305 0 01-.567.267z"/><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-13a1 1 0 10-2 0v.092a4.535 4.535 0 00-1.676.662C6.602 6.234 6 7.009 6 8c0 .99.602 1.765 1.324 2.246.48.32 1.054.545 1.676.662v1.941c-.391-.127-.68-.317-.843-.504a1 1 0 10-1.51 1.31c.562.649 1.413 1.076 2.353 1.253V15a1 1 0 102 0v-.092a4.535 4.535 0 001.676-.662C13.398 13.766 14 12.991 14 12c0-.99-.602-1.765-1.324-2.246A4.535 4.535 0 0011 9.092V7.151c.391.127.68.317.843.504a1 1 0 101.511-1.31c-.563-.649-1.413-1.076-2.354-1.253V5z" clip-rule="evenodd"/></svg>
{{ __('السعر الأساسي') }}
</h3>
<p class="text-lg font-bold text-gray-800" dir="ltr">{{ number_format((float)$basePrice, 2) }} {{ __('ج.م') }}</p>
<p class="text-xs text-gray-500 mt-1">{{ __('شهرياً - قبل الخصومات') }}</p>
</div>
{{-- Facility Card --}}
@if($assignFacility && $facilityId)
<div class="p-4 bg-gray-50 border border-gray-200 rounded-lg">
<h3 class="text-sm font-semibold text-gray-700 mb-3 flex items-center gap-2">
<svg class="w-4 h-4 text-orange-600" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M4 4a2 2 0 012-2h8a2 2 0 012 2v12a1 1 0 110 2h-3a1 1 0 01-1-1v-2a1 1 0 00-1-1H9a1 1 0 00-1 1v2a1 1 0 01-1 1H4a1 1 0 110-2V4zm3 1h2v2H7V5zm2 4H7v2h2V9zm2-4h2v2h-2V5zm2 4h-2v2h2V9z" clip-rule="evenodd"/></svg>
{{ __('المنشأة') }}
</h3>
<p class="text-sm text-gray-800 font-medium">{{ $facilities->firstWhere('id', $facilityId)?->name_ar }}</p>
</div>
@endif
</dl>
</div>
{{-- Summary Banner --}}
<div class="mt-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<h4 class="text-sm font-semibold text-blue-800 mb-2">{{ __('سيتم إنشاء:') }}</h4>
<ul class="space-y-1 text-xs text-blue-700">
<li class="flex items-center gap-2">
<svg class="w-3.5 h-3.5 text-blue-500" 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>
{{ __('برنامج تدريبي جديد') }}
</li>
@if($createInitialGroup)
<li class="flex items-center gap-2">
<svg class="w-3.5 h-3.5 text-blue-500" 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>
{{ __('مجموعة تدريبية أولى') }}
</li>
@endif
<li class="flex items-center gap-2">
<svg class="w-3.5 h-3.5 text-blue-500" 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>
{{ count($scheduleRows) }} {{ __('جدول تدريب أسبوعي') }}
</li>
<li class="flex items-center gap-2">
<svg class="w-3.5 h-3.5 text-blue-500" 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>
{{ __('سعر أساسي للاشتراك') }}
</li>
</ul>
</div>
@endif
{{-- Success State --}}
{{-- ═══════════════════════════════════════════════════════════════════
Success State
═══════════════════════════════════════════════════════════════════ --}}
@if($completed)
<div class="text-center py-8">
<div class="mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-green-100 mb-4">
......@@ -216,7 +532,16 @@ class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:r
</svg>
</div>
<h3 class="text-lg font-semibold text-gray-800 mb-2">{{ __('تم إنشاء البرنامج التدريبي بنجاح') }}</h3>
<p class="text-sm text-gray-600 mb-6">{{ __('يمكنك الآن إنشاء مجموعات للبرنامج أو إنشاء برنامج آخر') }}</p>
<p class="text-sm text-gray-600 mb-2">{{ __('تم إنشاء البرنامج مع جميع الإعدادات المطلوبة:') }}</p>
<ul class="text-sm text-gray-600 mb-6 space-y-1">
<li>{{ __('البرنامج + السعر الأساسي') }}</li>
@if($createInitialGroup)
<li>{{ __('المجموعة الأولى + الجدول التدريبي') }}</li>
@endif
@if($headTrainerId)
<li>{{ __('المدرب الرئيسي معين') }}</li>
@endif
</ul>
<div class="flex items-center justify-center gap-3">
<a href="{{ route('programs.index') }}" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors">
{{ __('قائمة البرامج') }}
......@@ -239,16 +564,19 @@ class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:r
</button>
@endif
</div>
<div>
<div class="flex items-center gap-2">
<span class="text-xs text-gray-400">{{ $currentStep }}/{{ $totalSteps }}</span>
@if($currentStep < $totalSteps)
<button wire:click="nextStep" class="px-5 py-2.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
{{ __('التالي') }}
<button wire:click="nextStep" wire:loading.attr="disabled" wire:target="nextStep"
class="px-5 py-2.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors disabled:opacity-50">
<span wire:loading.remove wire:target="nextStep">{{ __('التالي') }}</span>
<span wire:loading wire:target="nextStep">{{ __('جارٍ التحقق...') }}</span>
</button>
@elseif($currentStep === $totalSteps)
<button wire:click="confirm" wire:loading.attr="disabled" wire:target="confirm"
class="px-5 py-2.5 bg-green-600 text-white rounded-lg text-sm font-medium hover:bg-green-700 transition-colors disabled:opacity-50">
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد') }}</span>
<span wire:loading wire:target="confirm">{{ __('جارٍ الحفظ...') }}</span>
class="px-6 py-2.5 bg-green-600 text-white rounded-lg text-sm font-medium hover:bg-green-700 transition-colors disabled:opacity-50">
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد وإنشاء') }}</span>
<span wire:loading wire:target="confirm">{{ __('جارٍ الإنشاء...') }}</span>
</button>
@endif
</div>
......
......@@ -52,14 +52,15 @@ class="inline-flex items-center gap-2 px-6 py-3 bg-gray-100 text-gray-700 rounde
@else
{{-- Step Indicator --}}
@if($currentStep < 6)
@if($currentStep < 7)
@php
$steps = [
1 => 'بيانات ولي الأمر',
2 => 'بيانات المشترك',
3 => 'اختيار البرنامج',
4 => 'مراجعة وتأكيد',
5 => 'الدفع',
2 => 'حساب ولي الأمر',
3 => 'بيانات المشترك',
4 => 'اختيار البرنامج',
5 => 'مراجعة وتأكيد',
6 => 'الدفع',
];
@endphp
......@@ -67,10 +68,10 @@ class="inline-flex items-center gap-2 px-6 py-3 bg-gray-100 text-gray-700 rounde
<div class="sm:hidden bg-white rounded-xl shadow-sm border border-gray-200 p-3 mb-4">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-bold text-blue-700">{{ __($steps[$currentStep]) }}</span>
<span class="text-xs text-gray-500" dir="ltr">{{ $currentStep }} / 5</span>
<span class="text-xs text-gray-500" dir="ltr">{{ $currentStep }} / 6</span>
</div>
<div class="w-full h-2 bg-gray-200 rounded-full overflow-hidden">
<div class="h-full bg-blue-600 rounded-full transition-all duration-300" style="width: {{ ($currentStep / 5) * 100 }}%"></div>
<div class="h-full bg-blue-600 rounded-full transition-all duration-300" style="width: {{ ($currentStep / 6) * 100 }}%"></div>
</div>
</div>
......@@ -228,8 +229,107 @@ class="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-6 py-3.
</div>
@endif
{{-- Step 2: Participant Info --}}
{{-- Step 2: Parent Account --}}
@if($currentStep === 2)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-2">{{ __('حساب ولي الأمر') }}</h2>
<p class="text-sm text-gray-500 mb-6">{{ __('إنشاء حساب لولي الأمر لمتابعة ابنه') }}</p>
{{-- Toggle: Enable Parent Portal --}}
<div class="flex items-center gap-4 mb-6 p-4 bg-blue-50 border border-blue-200 rounded-xl">
<label class="relative cursor-pointer shrink-0" dir="ltr">
<input type="checkbox" wire:model.live="createParentAccount" class="peer sr-only">
<div class="w-14 h-8 rounded-full bg-gray-300 peer-checked:bg-blue-600 transition-colors after:content-[''] after:absolute after:top-1 after:left-1 after:w-6 after:h-6 after:bg-white after:rounded-full after:transition-all peer-checked:after:translate-x-6"></div>
</label>
<div>
<span class="text-base font-medium text-gray-800">{{ __('تفعيل الوصول لبوابة أولياء الأمور') }}</span>
<p class="text-xs text-gray-500 mt-0.5">{{ __('سيتمكن ولي الأمر من متابعة الحضور والتقييمات والمصروفات') }}</p>
</div>
</div>
@if($createParentAccount)
<div class="space-y-5">
{{-- Email --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('البريد الإلكتروني') }} <span class="text-red-500">*</span></label>
<input type="email" wire:model="parentEmail" dir="ltr"
class="w-full px-4 py-3 min-h-16 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-lg"
placeholder="parent@example.com">
@error('parentEmail') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Password --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('كلمة المرور') }} <span class="text-red-500">*</span></label>
<div class="flex gap-3">
<input type="text" wire:model="parentPassword" dir="ltr"
class="flex-1 px-4 py-3 min-h-16 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-lg font-mono"
placeholder="{{ __('كلمة المرور') }}">
<button type="button" wire:click="generateParentPassword"
class="inline-flex items-center gap-2 px-4 py-3 bg-gray-100 text-gray-700 border border-gray-300 rounded-lg hover:bg-gray-200 transition-colors whitespace-nowrap text-sm font-medium">
<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="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>
{{ __('توليد عشوائي') }}
</button>
</div>
@error('parentPassword') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Send credentials checkbox --}}
<label class="flex items-center gap-3 p-4 bg-gray-50 border border-gray-200 rounded-xl cursor-pointer hover:bg-gray-100 transition-colors">
<input type="checkbox" wire:model="sendParentCredentials"
class="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-blue-500">
<div>
<span class="text-sm font-medium text-gray-700">{{ __('إرسال بيانات الدخول بالبريد الإلكتروني') }}</span>
<p class="text-xs text-gray-500 mt-0.5">{{ __('سيتم إرسال البريد وكلمة المرور للعنوان المحدد') }}</p>
</div>
</label>
{{-- Info note --}}
<div class="p-3 bg-green-50 border border-green-200 rounded-lg">
<div class="flex items-start gap-2">
<svg class="w-5 h-5 text-green-600 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<p class="text-sm text-green-700">{{ __('سيتمكن ولي الأمر من متابعة الحضور والتقييمات والمصروفات من بوابة أولياء الأمور') }}</p>
</div>
</div>
</div>
@else
{{-- Toggle OFF note --}}
<div class="p-4 bg-amber-50 border border-amber-200 rounded-xl">
<div class="flex items-start gap-3">
<svg class="w-5 h-5 text-amber-600 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<p class="text-sm text-amber-700">{{ __('يمكنك إنشاء الحساب لاحقاً من إدارة المستخدمين') }}</p>
</div>
</div>
@endif
<div class="fixed bottom-0 start-0 end-0 bg-white border-t border-gray-200 p-4 safe-bottom sm:static sm:border-0 sm:p-0 sm:mt-8 flex justify-between gap-3 z-30">
<button wire:click="previousStep"
class="inline-flex items-center justify-center gap-2 px-5 py-3.5 sm:py-3 text-gray-600 bg-gray-100 rounded-xl sm:rounded-lg hover:bg-gray-200 text-base font-medium transition-colors">
<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="M15 19l-7-7 7-7"/>
</svg>
{{ __('السابق') }}
</button>
<button wire:click="nextStep" wire:loading.attr="disabled"
class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-6 py-3.5 sm:py-3 bg-blue-600 text-white rounded-xl sm:rounded-lg hover:bg-blue-700 text-base font-medium transition-colors disabled:opacity-50">
<span wire:loading.remove wire:target="nextStep">{{ __('التالي') }}</span>
<span wire:loading wire:target="nextStep">{{ __('جارٍ...') }}</span>
<svg class="w-5 h-5 rotate-180" 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>
</div>
</div>
@endif
{{-- Step 3: Participant Info --}}
@if($currentStep === 3)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('بيانات المشترك (اللاعب)') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
......@@ -358,8 +458,8 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-6 py
</div>
@endif
{{-- Step 3: Program Selection --}}
@if($currentStep === 3)
{{-- Step 4: Program Selection --}}
@if($currentStep === 4)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('اختيار البرنامج') }}</h2>
......@@ -437,8 +537,8 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-6 py
</div>
@endif
{{-- Step 4: Review --}}
@if($currentStep === 4)
{{-- Step 5: Review --}}
@if($currentStep === 5)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('مراجعة وتأكيد') }}</h2>
......@@ -471,11 +571,43 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-6 py
</div>
</div>
{{-- Parent Account Summary --}}
<div class="p-4 {{ $createParentAccount ? 'bg-blue-50 border-blue-200' : 'bg-gray-50 border-gray-200' }} rounded-xl border">
<div class="flex items-center justify-between mb-3">
<h3 class="font-semibold text-gray-700">{{ __('حساب ولي الأمر') }}</h3>
<button wire:click="goToStep(2)" class="text-sm text-blue-600 hover:text-blue-800">{{ __('تعديل') }}</button>
</div>
<div class="text-sm">
@if($createParentAccount)
<div class="grid grid-cols-2 gap-3">
<div>
<span class="text-gray-500">{{ __('البريد الإلكتروني') }}:</span>
<span class="font-medium text-gray-800 ms-1" dir="ltr">{{ $parentEmail }}</span>
</div>
<div>
<span class="text-gray-500">{{ __('الحالة') }}:</span>
<span class="inline-flex items-center gap-1 ms-1 px-2 py-0.5 bg-green-100 text-green-700 rounded text-xs font-medium">
{{ __('سيتم إنشاء الحساب') }}
</span>
</div>
@if($sendParentCredentials)
<div class="col-span-2">
<span class="text-gray-500">{{ __('الإرسال') }}:</span>
<span class="font-medium text-gray-800 ms-1">{{ __('سيتم إرسال بيانات الدخول بالبريد') }}</span>
</div>
@endif
</div>
@else
<p class="text-gray-500">{{ __('لن يتم إنشاء حساب — يمكن إنشاؤه لاحقاً') }}</p>
@endif
</div>
</div>
{{-- Participant Summary --}}
<div class="p-4 bg-gray-50 rounded-xl border border-gray-200">
<div class="flex items-center justify-between mb-3">
<h3 class="font-semibold text-gray-700">{{ __('المشترك (اللاعب)') }}</h3>
<button wire:click="goToStep(2)" class="text-sm text-blue-600 hover:text-blue-800">{{ __('تعديل') }}</button>
<button wire:click="goToStep(3)" class="text-sm text-blue-600 hover:text-blue-800">{{ __('تعديل') }}</button>
</div>
<div class="grid grid-cols-2 gap-3 text-sm">
<div>
......@@ -524,7 +656,7 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-6 py
<div class="p-4 bg-gray-50 rounded-xl border border-gray-200">
<div class="flex items-center justify-between mb-3">
<h3 class="font-semibold text-gray-700">{{ __('البرنامج') }}</h3>
<button wire:click="goToStep(3)" class="text-sm text-blue-600 hover:text-blue-800">{{ __('تعديل') }}</button>
<button wire:click="goToStep(4)" class="text-sm text-blue-600 hover:text-blue-800">{{ __('تعديل') }}</button>
</div>
@if($this->selectedProgram)
<div class="text-sm">
......@@ -670,8 +802,8 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-6 py
</div>
@endif
{{-- Step 5: Payment --}}
@if($currentStep === 5)
{{-- Step 6: Payment --}}
@if($currentStep === 6)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('الدفع') }}</h2>
......@@ -789,8 +921,8 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py
</div>
@endif
{{-- Step 6: Success + Printable Invoice --}}
@if($currentStep === 6)
{{-- Step 7: Success + Printable Invoice --}}
@if($currentStep === 7)
<div>
{{-- Success Banner --}}
<div class="text-center py-6 mb-6">
......
<div>
<div x-data="{
roleSlug: @entangle('selectedRoleSlug'),
trainerOpen: true,
employeeOpen: true,
guardianOpen: true,
}">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-4 sm:mb-6">
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ $editing ? 'تعديل المستخدم' : 'إضافة مستخدم جديد' }}</h1>
<a href="{{ route('users.list') }}" wire:navigate class="text-sm text-gray-500 hover:text-gray-700">&larr; العودة</a>
<a href="{{ route('users.list') }}" wire:navigate class="text-sm text-gray-500 hover:text-gray-700">&larr; {{ __('العودة') }}</a>
</div>
<form wire:submit="save" class="space-y-6">
{{-- ─── User Information Section ─────────────────────────────── --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6">
<h2 class="text-base sm:text-lg font-semibold text-gray-700 mb-4">معلومات المستخدم</h2>
<h2 class="text-base sm:text-lg font-semibold text-gray-700 mb-4">{{ __('معلومات المستخدم') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">الاسم بالعربية *</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الاسم بالعربية') }} *</label>
<input type="text" wire:model="name_ar" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 @error('name_ar') border-red-500 @enderror">
@error('name_ar') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">الاسم بالإنجليزية *</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الاسم بالإنجليزية') }} *</label>
<input type="text" wire:model="name" dir="ltr" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 @error('name') border-red-500 @enderror">
@error('name') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">البريد الإلكتروني *</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('البريد الإلكتروني') }} *</label>
<input type="email" wire:model="email" dir="ltr" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 @error('email') border-red-500 @enderror">
<p class="mt-1 text-xs text-gray-500">{{ __('سيُستخدم كاسم مستخدم لتسجيل الدخول') }}</p>
@error('email') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">الدور</label>
<select wire:model="role_id" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="">بدون دور</option>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الدور') }}</label>
<select wire:model.live="role_id" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="">{{ __('بدون دور') }}</option>
@foreach($roles as $r)
<option value="{{ $r->id }}">{{ $r->name_ar }} ({{ $r->slug }})</option>
@endforeach
</select>
@error('role_id') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ $editing ? 'كلمة مرور جديدة (اتركها فارغة للإبقاء)' : 'كلمة المرور *' }}</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ $editing ? __('كلمة مرور جديدة (اتركها فارغة للإبقاء)') : __('كلمة المرور') . ' *' }}</label>
<input type="password" wire:model="password" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 @error('password') border-red-500 @enderror">
@error('password') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">تأكيد كلمة المرور</label>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تأكيد كلمة المرور') }}</label>
<input type="password" wire:model="password_confirmation" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
</div>
</div>
<div class="mt-4">
<label class="block text-sm font-medium text-gray-700 mb-1">الحالة</label>
<select wire:model="status" class="w-full max-w-xs px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="active">نشط</option>
<option value="inactive">غير نشط</option>
<option value="suspended">موقوف</option>
<option value="pending">قيد الانتظار</option>
</select>
<div class="mt-4 grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الحالة') }}</label>
<select wire:model="status" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="active">{{ __('نشط') }}</option>
<option value="inactive">{{ __('غير نشط') }}</option>
<option value="suspended">{{ __('موقوف') }}</option>
<option value="pending">{{ __('قيد الانتظار') }}</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('ربط بشخص (اختياري)') }}</label>
<select wire:model="person_id" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="">— {{ __('إنشاء سجل جديد تلقائياً') }} —</option>
@foreach($people as $p)
<option value="{{ $p['id'] }}">{{ $p['name'] }}</option>
@endforeach
</select>
<p class="mt-1 text-xs text-gray-500">{{ __('إذا لم يتم اختيار شخص، سيتم إنشاء سجل شخص تلقائياً عند الحاجة') }}</p>
</div>
</div>
</div>
{{-- ─── Trainer Setup Section ────────────────────────────────── --}}
<div x-show="roleSlug === 'trainer' || roleSlug === 'head_trainer'"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 -translate-y-2"
x-transition:enter-end="opacity-100 translate-y-0"
x-cloak
class="bg-white rounded-xl shadow-sm border border-emerald-200 p-4 sm:p-6">
{{-- Header with collapse toggle --}}
<button type="button" @click="trainerOpen = !trainerOpen" class="flex items-center justify-between w-full text-start">
<div class="flex items-center gap-2">
<span class="flex items-center justify-center w-8 h-8 bg-emerald-100 text-emerald-700 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" 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>
</span>
<h2 class="text-base sm:text-lg font-semibold text-emerald-800">{{ __('إعداد المدرب') }}</h2>
</div>
<svg :class="trainerOpen ? 'rotate-180' : ''" class="w-5 h-5 text-gray-400 transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div x-show="trainerOpen" x-transition class="mt-4">
{{-- Toggle: Create trainer record --}}
<label class="flex items-center gap-3 mb-4 cursor-pointer">
<input type="checkbox" wire:model.live="createTrainer" class="w-5 h-5 text-emerald-600 border-gray-300 rounded focus:ring-emerald-500">
<span class="text-sm font-medium text-gray-700">{{ __('إنشاء سجل مدرب؟') }}</span>
</label>
@if($createTrainer)
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4 pt-3 border-t border-gray-100">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نموذج التعويض') }} *</label>
<select wire:model="trainerCompensationModel" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 @error('trainerCompensationModel') border-red-500 @enderror">
@foreach($compensationModels as $cm)
<option value="{{ $cm->value }}">{{ $cm->label() }}</option>
@endforeach
</select>
@error('trainerCompensationModel') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('قيمة المعدل (ج.م)') }} *</label>
<input type="number" wire:model="trainerRateAmount" dir="ltr" step="0.01" min="0" placeholder="0.00"
class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 @error('trainerRateAmount') border-red-500 @enderror">
<p class="mt-1 text-xs text-gray-500">{{ __('المبلغ بالجنيه المصري') }}</p>
@error('trainerRateAmount') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الفرع') }}</label>
<select wire:model="trainerBranchId" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500">
<option value="">— {{ __('جميع الفروع') }} —</option>
@foreach($branches as $branch)
<option value="{{ $branch->id }}">{{ $branch->name_ar }}</option>
@endforeach
</select>
</div>
</div>
<div class="mt-3 p-3 bg-emerald-50 rounded-lg border border-emerald-100">
<p class="text-xs text-emerald-700">
<svg xmlns="http://www.w3.org/2000/svg" class="inline w-4 h-4 me-1" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{{ __('يمكن إضافة المؤهلات وجدول التوافر لاحقاً من صفحة المدرب') }}
</p>
</div>
@endif
</div>
</div>
{{-- ─── Employee Setup Section (Staff Roles) ─────────────────── --}}
<div x-show="roleSlug === 'receptionist' || roleSlug === 'accountant' || roleSlug === 'branch_manager' || roleSlug === 'academy_admin'"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 -translate-y-2"
x-transition:enter-end="opacity-100 translate-y-0"
x-cloak
class="bg-white rounded-xl shadow-sm border border-blue-200 p-4 sm:p-6">
{{-- Header with collapse toggle --}}
<button type="button" @click="employeeOpen = !employeeOpen" class="flex items-center justify-between w-full text-start">
<div class="flex items-center gap-2">
<span class="flex items-center justify-center w-8 h-8 bg-blue-100 text-blue-700 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</span>
<h2 class="text-base sm:text-lg font-semibold text-blue-800">{{ __('إعداد الموظف') }}</h2>
</div>
<svg :class="employeeOpen ? 'rotate-180' : ''" class="w-5 h-5 text-gray-400 transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div x-show="employeeOpen" x-transition class="mt-4">
{{-- Toggle: Create employee record --}}
<label class="flex items-center gap-3 mb-4 cursor-pointer">
<input type="checkbox" wire:model.live="createEmployee" class="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-blue-500">
<span class="text-sm font-medium text-gray-700">{{ __('إنشاء سجل موظف؟') }}</span>
</label>
@if($createEmployee)
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4 pt-3 border-t border-gray-100">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نوع التوظيف') }} *</label>
<select wire:model="employeeEmploymentType" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 @error('employeeEmploymentType') border-red-500 @enderror">
@foreach($employmentTypes as $et)
<option value="{{ $et->value }}">{{ $et->label() }}</option>
@endforeach
</select>
@error('employeeEmploymentType') <p class="mt-1 text-sm 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="employeeDepartment" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('المنصب') }}</label>
<input type="text" wire:model="employeePosition" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تاريخ البدء') }} *</label>
<input type="date" wire:model="employeeStartDate" dir="ltr" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 @error('employeeStartDate') border-red-500 @enderror">
@error('employeeStartDate') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الفرع') }}</label>
<select wire:model="employeeBranchId" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="">— {{ __('بدون فرع محدد') }} —</option>
@foreach($branches as $branch)
<option value="{{ $branch->id }}">{{ $branch->name_ar }}</option>
@endforeach
</select>
</div>
</div>
@endif
</div>
</div>
{{-- ─── Guardian Setup Section (Parent Role) ─────────────────── --}}
<div x-show="roleSlug === 'parent'"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 -translate-y-2"
x-transition:enter-end="opacity-100 translate-y-0"
x-cloak
class="bg-white rounded-xl shadow-sm border border-amber-200 p-4 sm:p-6">
{{-- Header with collapse toggle --}}
<button type="button" @click="guardianOpen = !guardianOpen" class="flex items-center justify-between w-full text-start">
<div class="flex items-center gap-2">
<span class="flex items-center justify-center w-8 h-8 bg-amber-100 text-amber-700 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" 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 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
</span>
<h2 class="text-base sm:text-lg font-semibold text-amber-800">{{ __('ربط ولي الأمر') }}</h2>
</div>
<svg :class="guardianOpen ? 'rotate-180' : ''" class="w-5 h-5 text-gray-400 transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div x-show="guardianOpen" x-transition class="mt-4">
{{-- Mode selector --}}
<div class="flex gap-4 mb-4">
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" wire:model.live="guardianMode" value="new" class="w-4 h-4 text-amber-600 border-gray-300 focus:ring-amber-500">
<span class="text-sm font-medium text-gray-700">{{ __('إنشاء سجل ولي أمر جديد') }}</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" wire:model.live="guardianMode" value="existing" class="w-4 h-4 text-amber-600 border-gray-300 focus:ring-amber-500">
<span class="text-sm font-medium text-gray-700">{{ __('ربط بولي أمر موجود') }}</span>
</label>
</div>
{{-- Existing guardian selection --}}
@if($guardianMode === 'existing')
<div class="pt-3 border-t border-gray-100">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('اختر ولي الأمر') }} *</label>
<select wire:model="existingGuardianId" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-amber-500 @error('existingGuardianId') border-red-500 @enderror">
<option value="">— {{ __('اختر') }} —</option>
@foreach($guardians ?? [] as $g)
<option value="{{ $g['id'] }}">{{ $g['name'] }} {{ $g['phone'] ? '(' . $g['phone'] . ')' : '' }}</option>
@endforeach
</select>
@error('existingGuardianId') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
@else
{{-- New guardian creation --}}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4 pt-3 border-t border-gray-100">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('صلة القرابة') }} *</label>
<select wire:model="guardianRelationshipType" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-amber-500 @error('guardianRelationshipType') border-red-500 @enderror">
<option value="father">{{ __('أب') }}</option>
<option value="mother">{{ __('أم') }}</option>
<option value="grandfather">{{ __('جد') }}</option>
<option value="grandmother">{{ __('جدة') }}</option>
<option value="uncle">{{ __('عم / خال') }}</option>
<option value="aunt">{{ __('عمة / خالة') }}</option>
<option value="sibling">{{ __('أخ / أخت') }}</option>
<option value="legal_guardian">{{ __('ولي أمر قانوني') }}</option>
<option value="other">{{ __('أخرى') }}</option>
</select>
@error('guardianRelationshipType') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('ربط باللاعبين') }}</label>
<div class="max-h-48 overflow-y-auto border border-gray-200 rounded-lg p-3 space-y-2">
@forelse($participants ?? [] as $participant)
<label class="flex items-center gap-2 cursor-pointer hover:bg-gray-50 p-1.5 rounded">
<input type="checkbox" wire:model="guardianParticipantIds" value="{{ $participant['id'] }}" class="w-4 h-4 text-amber-600 border-gray-300 rounded focus:ring-amber-500">
<span class="text-sm text-gray-700">{{ $participant['name'] }}</span>
</label>
@empty
<p class="text-sm text-gray-500 text-center py-2">{{ __('لا يوجد لاعبين مسجلين') }}</p>
@endforelse
</div>
<p class="mt-1 text-xs text-gray-500">{{ __('اختر اللاعبين المرتبطين بولي الأمر هذا') }}</p>
</div>
</div>
@endif
</div>
</div>
{{-- ─── Form Actions ─────────────────────────────────────────── --}}
<div class="flex flex-col-reverse sm:flex-row items-stretch sm:items-center justify-end gap-3">
<a href="{{ route('users.list') }}" wire:navigate class="text-center px-4 sm:px-6 py-2.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 text-sm font-medium">إلغاء</a>
<button type="submit" wire:loading.attr="disabled"
<a href="{{ route('users.list') }}" wire:navigate class="text-center px-4 sm:px-6 py-2.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 text-sm font-medium">{{ __('إلغاء') }}</a>
<button type="submit" wire:loading.attr="disabled" wire:target="save"
class="text-center px-4 sm:px-6 py-2.5 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 disabled:bg-blue-400 text-sm">
<span wire:loading.remove wire:target="save">{{ $editing ? 'حفظ التعديلات' : 'إنشاء المستخدم' }}</span>
<span wire:loading wire:target="save">جارٍ الحفظ...</span>
<span wire:loading.remove wire:target="save">{{ $editing ? __('حفظ التعديلات') : __('إنشاء المستخدم') }}</span>
<span wire:loading wire:target="save">{{ __('جارٍ الحفظ...') }}</span>
</button>
</div>
</form>
{{-- Flash messages --}}
@if(session('error'))
<div class="fixed bottom-4 start-4 end-4 sm:start-auto sm:end-4 sm:w-96 bg-red-50 border border-red-200 rounded-lg p-4 shadow-lg z-50" x-data x-init="setTimeout(() => $el.remove(), 6000)">
<p class="text-sm text-red-800">{{ session('error') }}</p>
</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