Commit 0504e7d1 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Add phone-or-email login, Egyptian NID decoder, proration engine, and receptionist wizard overhaul

- AuthService: accepts email or phone for login (identifier-based lookup)
- Login UI: updated to text input with Arabic labels
- EgyptianNidDecoder: decodes 14-digit NID → birth date, gender, governorate
- ProrationResult DTO + ProrationService: mid-month enrollment fee proration (x/30 of remaining days based on configurable renewal day)
- EnrollmentSettingsSeeder: seeds enrollment.allow_proration and enrollment.renewal_day per academy
- NewRegistrationWizard: rewritten — player-first flow, NID auto-decode locks birth/gender/governorate, foreign player toggle, guardian name deduced from player name, phone-only guardian, super admin price override, proration display
- EnrollmentService: applies proration to invoice creation
- EnrollExistingWizard: adds proratedProgramFee computed property, review step shows original vs prorated fee
- Migration: adds governorate column to people table
Co-Authored-By: 's avatarClaude Sonnet 4.6 <noreply@anthropic.com>
parent 20184e77
......@@ -12,9 +12,11 @@ class AuthService
private const MAX_FAILED_ATTEMPTS = 5;
private const LOCKOUT_MINUTES = 30;
public function attempt(string $email, string $password, string $ip, ?string $userAgent = null): AuthResult
public function attempt(string $identifier, string $password, string $ip, ?string $userAgent = null): AuthResult
{
$user = User::where('email', $email)->first();
$user = filter_var($identifier, FILTER_VALIDATE_EMAIL)
? User::where('email', $identifier)->first()
: User::where('phone', $identifier)->first();
if (!$user) {
return new AuthResult(success: false, reason: 'invalid_credentials');
......
<?php
namespace App\Domain\Identity\Services;
use Carbon\Carbon;
class EgyptianNidDecoder
{
// Governorate codes → Arabic name
private const GOVERNORATES = [
'01' => 'القاهرة',
'02' => 'الإسكندرية',
'03' => 'بور سعيد',
'04' => 'السويس',
'11' => 'دمياط',
'12' => 'الدقهلية',
'13' => 'الشرقية',
'14' => 'القليوبية',
'15' => 'كفر الشيخ',
'16' => 'الغربية',
'17' => 'المنوفية',
'18' => 'البحيرة',
'19' => 'الإسماعيلية',
'21' => 'الجيزة',
'22' => 'بني سويف',
'23' => 'الفيوم',
'24' => 'المنيا',
'25' => 'أسيوط',
'26' => 'سوهاج',
'27' => 'قنا',
'28' => 'أسوان',
'29' => 'الأقصر',
'31' => 'البحر الأحمر',
'32' => 'الوادي الجديد',
'33' => 'مطروح',
'34' => 'شمال سيناء',
'35' => 'جنوب سيناء',
'88' => 'أجنبي مقيم',
];
public function decode(string $nid): array
{
$nid = preg_replace('/\D/', '', $nid);
if (strlen($nid) !== 14) {
return ['valid' => false];
}
$century = $nid[0];
if (!in_array($century, ['2', '3'])) {
return ['valid' => false];
}
$year = ($century === '2' ? '19' : '20') . substr($nid, 1, 2);
$month = substr($nid, 3, 2);
$day = substr($nid, 5, 2);
try {
$birthDate = Carbon::createFromDate((int)$year, (int)$month, (int)$day);
if ($birthDate->isFuture()) {
return ['valid' => false];
}
} catch (\Throwable) {
return ['valid' => false];
}
$govCode = substr($nid, 7, 2);
$governorate = self::GOVERNORATES[$govCode] ?? null;
// Last digit before check digit (position 12, 0-indexed) determines gender
// Odd = male, even = female
$genderDigit = (int) $nid[12];
$gender = ($genderDigit % 2 !== 0) ? 'male' : 'female';
return [
'valid' => true,
'birth_date' => $birthDate->toDateString(),
'gender' => $gender,
'governorate_ar' => $governorate,
'governorate_code' => $govCode,
];
}
}
<?php
namespace App\Domain\Shared\DTOs;
readonly class ProrationResult
{
public function __construct(
public bool $applied,
public int $originalAmount,
public int $proratedAmount,
public int $remainingDays,
public int $renewalDay,
public string $description,
) {}
}
<?php
namespace App\Domain\Shared\Services;
use App\Domain\Shared\DTOs\ProrationResult;
use Carbon\Carbon;
class ProrationService
{
public function __construct(
private readonly SettingsService $settings,
) {}
public function isEnabled(): bool
{
return (bool) $this->settings->get('enrollment.allow_proration', false);
}
public function renewalDay(): int
{
return (int) $this->settings->get('enrollment.renewal_day', 1);
}
/**
* Calculate prorated fee for a mid-month enrollment.
* If today is on or before the renewal day, no proration applies (full price).
*/
public function calculate(int $baseAmount, ?Carbon $enrollmentDate = null): ProrationResult
{
$today = $enrollmentDate ?? now();
$renewalDay = $this->renewalDay();
$currentDay = (int) $today->day;
// No proration if enrolling on or before the renewal day
if ($currentDay <= $renewalDay) {
return new ProrationResult(
applied: false,
originalAmount: $baseAmount,
proratedAmount: $baseAmount,
remainingDays: 30,
renewalDay: $renewalDay,
description: '',
);
}
// Days remaining until next renewal: renewal_day + 30 - today
$remainingDays = $renewalDay + 30 - $currentDay;
$remainingDays = max(1, $remainingDays);
$proratedAmount = (int) ceil($baseAmount * $remainingDays / 30);
$description = "متناسب: {$remainingDays} من 30 يوم";
return new ProrationResult(
applied: true,
originalAmount: $baseAmount,
proratedAmount: $proratedAmount,
remainingDays: $remainingDays,
renewalDay: $renewalDay,
description: $description,
);
}
}
......@@ -6,6 +6,7 @@
use App\Domain\Participant\Models\Participant;
use App\Domain\Pricing\Services\PricingService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Services\ProrationService;
use App\Domain\Shared\Services\SettingsService;
use App\Domain\Attendance\Services\AttendanceGenerationService;
use App\Domain\Training\Enums\RenewalPolicy;
......@@ -28,6 +29,7 @@ public function __construct(
private readonly PricingService $pricingService,
private readonly InvoiceService $invoiceService,
private readonly SettingsService $settings,
private readonly ProrationService $prorationService,
) {}
public function enroll(Participant $participant, TrainingGroup $group, User $actor, ?array $options = []): Enrollment
......@@ -385,13 +387,22 @@ private function createEnrollmentInvoice(Enrollment $enrollment, Participant $pa
return;
}
// Apply proration if enabled
$proration = $this->prorationService->calculate($priceResult->finalAmount);
$finalAmount = $proration->proratedAmount;
$lineDescription = "اشتراك: {$program->name_ar}";
if ($proration->applied) {
$lineDescription .= " ({$proration->description})";
}
$invoice = $this->invoiceService->create([
'academy_id' => $enrollment->academy_id ?? $group->academy_id,
'branch_id' => $group->branch_id,
'billable_type' => $participant->getMorphClass(),
'billable_id' => $participant->id,
'number' => $this->invoiceService->generateNumber($group->academy_id),
'total_amount' => $priceResult->finalAmount,
'total_amount' => $finalAmount,
'subtotal_amount' => $priceResult->baseAmount,
'discount_amount' => $priceResult->totalDiscount,
'tax_amount' => 0,
......@@ -399,10 +410,10 @@ private function createEnrollmentInvoice(Enrollment $enrollment, Participant $pa
'contact_name' => $participant->person?->name_ar ?? $participant->person?->name,
], [
[
'description' => "اشتراك: {$program->name_ar}",
'description' => $lineDescription,
'quantity' => 1,
'unit_price' => $priceResult->baseAmount,
'discount_amount' => $priceResult->totalDiscount,
'unit_price' => $finalAmount,
'discount_amount' => 0,
'tax_amount' => 0,
],
], $actor);
......
......@@ -13,7 +13,7 @@
#[Title('تسجيل الدخول')]
class Login extends Component
{
public string $email = '';
public string $identifier = '';
public string $password = '';
public bool $remember = false;
public ?string $errorMessage = null;
......@@ -21,7 +21,7 @@ class Login extends Component
public function rules(): array
{
return [
'email' => 'required|email',
'identifier' => 'required|string|min:3',
'password' => 'required|min:6',
];
}
......@@ -29,8 +29,8 @@ public function rules(): array
public function messages(): array
{
return [
'email.required' => 'البريد الإلكتروني مطلوب',
'email.email' => 'صيغة البريد الإلكتروني غير صحيحة',
'identifier.required' => 'البريد الإلكتروني أو رقم الهاتف مطلوب',
'identifier.min' => 'يجب أن يكون الإدخال 3 أحرف على الأقل',
'password.required' => 'كلمة المرور مطلوبة',
'password.min' => 'كلمة المرور يجب أن تكون 6 أحرف على الأقل',
];
......@@ -42,7 +42,7 @@ public function login(AuthService $authService): void
$this->errorMessage = null;
$result = $authService->attempt(
email: $this->email,
identifier: $this->identifier,
password: $this->password,
ip: request()->ip(),
userAgent: request()->userAgent(),
......@@ -54,7 +54,7 @@ public function login(AuthService $authService): void
} elseif ($result->reason === 'inactive') {
$this->errorMessage = 'الحساب غير نشط. تواصل مع الإدارة.';
} else {
$this->errorMessage = 'البريد الإلكتروني أو كلمة المرور غير صحيحة';
$this->errorMessage = 'بيانات الدخول غير صحيحة';
}
return;
}
......
......@@ -4,7 +4,9 @@
use App\Domain\Participant\Models\Participant;
use App\Domain\Pricing\Models\BasePrice;
use App\Domain\Shared\DTOs\ProrationResult;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Services\ProrationService;
use App\Domain\Shared\Traits\UsesBranchScope;
use App\Domain\Training\Models\Activity;
use App\Domain\Training\Models\Enrollment;
......@@ -161,6 +163,24 @@ public function selectedProgramFee(): int
return $price?->amount ?? 0;
}
#[Computed]
public function proratedProgramFee(): ProrationResult
{
$baseFee = $this->selectedProgramFee;
$service = app(ProrationService::class);
if (!$service->isEnabled() || $baseFee === 0) {
return new ProrationResult(
applied: false,
originalAmount: $baseFee,
proratedAmount: $baseFee,
remainingDays: 30,
renewalDay: $service->renewalDay(),
description: '',
);
}
return $service->calculate($baseFee);
}
public function render()
{
$searchResults = collect();
......
......@@ -9,6 +9,7 @@
use App\Domain\Identity\Models\Guardian;
use App\Domain\Identity\Models\Person;
use App\Domain\Identity\Models\Role;
use App\Domain\Identity\Services\EgyptianNidDecoder;
use App\Domain\Identity\Services\PersonService;
use App\Domain\Inventory\Models\Kit;
use App\Domain\Inventory\Models\Product;
......@@ -16,8 +17,10 @@
use App\Domain\Participant\Services\DuplicateDetectionService;
use App\Domain\Participant\Services\ParticipantService;
use App\Domain\Pricing\Models\BasePrice;
use App\Domain\Shared\DTOs\ProrationResult;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Services\PlatformFeeService;
use App\Domain\Shared\Services\ProrationService;
use App\Domain\Shared\Traits\UsesBranchScope;
use App\Domain\Training\Models\Activity;
use App\Domain\Training\Models\TrainingProgram;
......@@ -42,20 +45,7 @@ class NewRegistrationWizard extends Component
public int $currentStep = 1;
public int $totalSteps = 7;
// Step 1: Guardian info
public string $guardian_name_ar = '';
public string $guardian_name = '';
public string $guardian_phone = '';
public string $guardian_national_id = '';
public string $guardian_relation = 'father';
// 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)
// Step 1: Participant (the actual player/child) — FIRST now
public string $participant_name_ar = '';
public string $participant_name = '';
public ?string $participant_date_of_birth = null;
......@@ -65,6 +55,21 @@ class NewRegistrationWizard extends Component
public string $participant_medical_notes = '';
public string $membership_type = 'non_member';
public string $membership_id = '';
public string $participant_governorate = '';
public bool $participant_is_foreign = false;
public bool $participant_nid_decoded = false;
// Step 2: Guardian info
public string $guardian_name_ar = '';
public string $guardian_name = '';
public string $guardian_phone = '';
public string $guardian_relation = 'father';
// Step 3: Parent Account
public bool $createParentAccount = true;
public string $parentEmail = '';
public string $parentPassword = '';
public bool $sendParentCredentials = true;
// Step 4: Program selection
public ?int $selected_activity_id = null;
......@@ -74,6 +79,11 @@ class NewRegistrationWizard extends Component
public bool $pay_now = false;
public string $payment_method = 'cash';
// Super-admin price override
public bool $priceOverrideEnabled = false;
public string $priceOverrideInput = '';
public string $priceOverrideReason = '';
// Guardian search (existing parent)
public bool $searchExistingGuardian = false;
public string $guardianSearchQuery = '';
......@@ -148,6 +158,69 @@ private function runPreflightChecks(): void
$this->systemErrors = $errors;
}
// --- NID decode hooks ---
public function updatedParticipantNationalId(): void
{
if ($this->participant_is_foreign) {
return;
}
$nid = trim($this->participant_national_id);
if (strlen($nid) !== 14) {
$this->participant_nid_decoded = false;
return;
}
$result = app(EgyptianNidDecoder::class)->decode($nid);
if (!$result['valid']) {
$this->participant_nid_decoded = false;
return;
}
$this->participant_date_of_birth = $result['birth_date'];
$this->participant_gender = $result['gender'];
$this->participant_governorate = $result['governorate_ar'] ?? '';
$this->participant_nid_decoded = true;
}
public function updatedParticipantIsForeign(): void
{
if ($this->participant_is_foreign) {
// Unlock fields when switching to foreign
$this->participant_nid_decoded = false;
} else {
// Re-decode if NID already filled
$this->updatedParticipantNationalId();
}
}
// --- Guardian name auto-fill from player name ---
public function updatedParticipantNameAr(): void
{
$this->autoFillGuardianName();
}
private function autoFillGuardianName(): void
{
if (empty($this->participant_name_ar) || $this->guardianSelected) {
return;
}
$parts = array_values(array_filter(explode(' ', trim($this->participant_name_ar))));
// Father name = words[1] + words[2] (the two words after the player's first name)
if (count($parts) >= 3) {
$this->guardian_name_ar = implode(' ', array_slice($parts, 1, 2));
} elseif (count($parts) === 2) {
$this->guardian_name_ar = $parts[1];
}
}
// --- Step navigation ---
public function nextStep(): void
{
$rules = $this->rulesForStep($this->currentStep);
......@@ -156,14 +229,14 @@ public function nextStep(): void
$this->validate($rules, $this->messages());
}
// Duplicate check on step 1 — if duplicates found, stay on step 1
if ($this->currentStep === 1 && !$this->duplicateCheckDone) {
// Duplicate check on step 2 (guardian) — if duplicates found, stay on step 2
if ($this->currentStep === 2 && !$this->duplicateCheckDone) {
$this->checkDuplicates();
return;
}
// Pre-fill parent email from guardian person data when moving to step 2
if ($this->currentStep === 1 && $this->duplicateCheckDone && empty($this->parentEmail)) {
// Pre-fill parent email from guardian person data when moving to step 3
if ($this->currentStep === 2 && $this->duplicateCheckDone && empty($this->parentEmail)) {
if ($this->useExistingPersonId) {
$existingPerson = Person::find($this->useExistingPersonId);
if ($existingPerson && $existingPerson->email) {
......@@ -188,25 +261,24 @@ private function rulesForStep(int $step): array
{
return match ($step) {
1 => [
'guardian_name_ar' => 'required|string|max:255',
'guardian_phone' => 'required|string|max:20',
'guardian_national_id' => 'nullable|string|max:14',
'guardian_relation' => 'required|in:father,mother,brother,sister,uncle,aunt,grandfather,grandmother,guardian,other',
],
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',
'participant_phone' => 'nullable|string|max:20',
'participant_national_id' => 'nullable|string|max:14',
'participant_national_id' => 'nullable|string|max:' . ($this->participant_is_foreign ? '30' : '14'),
'participant_medical_notes' => 'nullable|string|max:1000',
'membership_type' => 'required|in:member,non_member',
'membership_id' => 'required_if:membership_type,member|nullable|string|max:50',
],
2 => [
'guardian_name_ar' => 'required|string|max:255',
'guardian_phone' => 'required|string|max:20',
'guardian_relation' => 'required|in:father,mother,brother,sister,uncle,aunt,grandfather,grandmother,guardian,other',
],
3 => $this->createParentAccount ? [
'parentEmail' => 'required|email|max:255|unique:users,email',
'parentPassword' => 'required|string|min:6|max:100',
] : [],
4 => [
'selected_activity_id' => 'required|exists:activities,id',
'selected_program_id' => 'required|exists:training_programs,id',
......@@ -221,6 +293,14 @@ private function rulesForStep(int $step): array
public function messages(): array
{
return [
'participant_name_ar.required' => 'اسم المشترك مطلوب',
'participant_date_of_birth.required' => 'تاريخ الميلاد مطلوب',
'participant_date_of_birth.before' => 'تاريخ الميلاد يجب أن يكون في الماضي',
'participant_gender.required' => 'الجنس مطلوب',
'participant_gender.in' => 'الجنس غير صالح',
'membership_type.required' => 'نوع العضوية مطلوب',
'membership_type.in' => 'نوع العضوية غير صالح',
'membership_id.required_if' => 'رقم عضوية النادي مطلوب للأعضاء',
'guardian_name_ar.required' => 'اسم ولي الأمر مطلوب',
'guardian_phone.required' => 'رقم الهاتف مطلوب',
'guardian_relation.required' => 'صلة القرابة مطلوبة',
......@@ -230,18 +310,10 @@ public function messages(): array
'parentEmail.unique' => 'البريد الإلكتروني مستخدم بالفعل',
'parentPassword.required' => 'كلمة المرور مطلوبة',
'parentPassword.min' => 'كلمة المرور يجب أن تكون 6 أحرف على الأقل',
'participant_name_ar.required' => 'اسم المشترك مطلوب',
'participant_date_of_birth.required' => 'تاريخ الميلاد مطلوب',
'participant_date_of_birth.before' => 'تاريخ الميلاد يجب أن يكون في الماضي',
'participant_gender.required' => 'الجنس مطلوب',
'participant_gender.in' => 'الجنس غير صالح',
'selected_activity_id.required' => 'يرجى اختيار النشاط',
'selected_activity_id.exists' => 'النشاط المختار غير موجود',
'selected_program_id.required' => 'يرجى اختيار البرنامج',
'selected_program_id.exists' => 'البرنامج المختار غير موجود',
'membership_type.required' => 'نوع العضوية مطلوب',
'membership_type.in' => 'نوع العضوية غير صالح',
'membership_id.required_if' => 'رقم عضوية النادي مطلوب للأعضاء',
'payment_method.required_if' => 'يرجى اختيار طريقة الدفع',
'payment_method.in' => 'طريقة الدفع غير صالحة',
];
......@@ -325,7 +397,6 @@ public function selectExistingGuardian(int $guardianId): void
$this->guardian_name_ar = $guardian->person->name_ar ?? '';
$this->guardian_name = $guardian->person->name ?? '';
$this->guardian_phone = $guardian->person->phone ?? '';
$this->guardian_national_id = $guardian->person->national_id ?? '';
$this->guardian_relation = $guardian->relation_type ?? 'father';
$this->guardianSelected = true;
$this->guardianSearchResults = [];
......@@ -337,7 +408,6 @@ public function clearSelectedGuardian(): void
$this->guardian_name_ar = '';
$this->guardian_name = '';
$this->guardian_phone = '';
$this->guardian_national_id = '';
$this->guardian_relation = 'father';
$this->guardianSelected = false;
$this->searchExistingGuardian = false;
......@@ -383,6 +453,27 @@ public function updatedSelectedActivityId(): void
$this->selected_program_id = null;
}
// --- Price override (super admin only) ---
public function updatedPriceOverrideEnabled(): void
{
if (!$this->priceOverrideEnabled) {
$this->priceOverrideInput = '';
$this->priceOverrideReason = '';
}
}
// Returns the effective total in piasters, respecting override
#[Computed]
public function effectiveTotal(): int
{
if ($this->priceOverrideEnabled && $this->priceOverrideInput !== '') {
$overridePiasters = (int) round((float) $this->priceOverrideInput * 100);
return max(0, $overridePiasters);
}
return $this->totalWithFee;
}
// --- Hot-buy methods ---
#[Computed]
......@@ -477,6 +568,71 @@ public function hotbuyTotal(): int
return collect($this->hotbuyCart)->sum(fn ($item) => $item['price'] * $item['quantity']);
}
// --- Proration ---
#[Computed]
public function proratedProgramFee(): ProrationResult
{
$baseFee = $this->selectedProgramFee;
$service = app(ProrationService::class);
if (!$service->isEnabled() || $baseFee === 0) {
return new ProrationResult(
applied: false,
originalAmount: $baseFee,
proratedAmount: $baseFee,
remainingDays: 30,
renewalDay: $service->renewalDay(),
description: '',
);
}
return $service->calculate($baseFee);
}
#[Computed]
public function selectedProgram(): ?TrainingProgram
{
if (!$this->selected_program_id) {
return null;
}
return TrainingProgram::with('activity')->find($this->selected_program_id);
}
#[Computed]
public function selectedProgramFee(): int
{
if (!$this->selected_program_id) {
return 0;
}
$program = TrainingProgram::find($this->selected_program_id);
return $program ? $this->resolveProgramFee($program) : 0;
}
#[Computed]
public function platformFee(): int
{
$service = app(PlatformFeeService::class);
if (!$service->customerPays()) {
return 0;
}
$subtotal = $this->proratedProgramFee->proratedAmount + $this->hotbuyTotal;
return $service->calculate($subtotal);
}
#[Computed]
public function totalWithFee(): int
{
return $this->proratedProgramFee->proratedAmount + $this->hotbuyTotal + $this->platformFee;
}
#[Computed]
public function invoiceForPrint(): ?Invoice
{
if (!$this->invoiceId) {
return null;
}
return Invoice::with('items')->find($this->invoiceId);
}
public function confirm(): void
{
try {
......@@ -498,7 +654,6 @@ public function confirm(): void
'name_ar' => $this->guardian_name_ar,
'name' => $this->guardian_name ?: $this->guardian_name_ar,
'phone' => $this->guardian_phone,
'national_id' => $this->guardian_national_id ?: null,
], $actor);
}
}
......@@ -521,7 +676,6 @@ public function confirm(): void
->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,
......@@ -538,7 +692,6 @@ public function confirm(): void
$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,
......@@ -556,6 +709,7 @@ public function confirm(): void
'gender' => $this->participant_gender,
'phone' => $this->participant_phone ?: null,
'national_id' => $this->participant_national_id ?: null,
'governorate' => $this->participant_governorate ?: null,
'medical_notes' => $this->participant_medical_notes ?: null,
], $actor);
......@@ -588,25 +742,49 @@ public function confirm(): void
$actor
);
// 7. Look up the price for this program + calculate platform fee + hot-buy
$feeAmount = $this->resolveProgramFee($program);
// 7. Resolve program fee with optional proration, hot-buy, platform fee
$prorationResult = $this->proratedProgramFee;
$programFee = $prorationResult->proratedAmount;
$hotbuyTotal = $this->hotbuyTotal;
$subtotal = $feeAmount + $hotbuyTotal;
$subtotal = $programFee + $hotbuyTotal;
$platformFeeService = app(PlatformFeeService::class);
$customerPays = $platformFeeService->customerPays();
$serviceFee = $customerPays ? $platformFeeService->calculate($subtotal) : 0;
$totalWithFee = $subtotal + $serviceFee;
$computedTotal = $subtotal + $serviceFee;
// Super-admin override
$finalTotal = $computedTotal;
if ($this->priceOverrideEnabled && $this->priceOverrideInput !== '' && $actor->is_super_admin) {
$overrideAmount = (int) round((float) $this->priceOverrideInput * 100);
$finalTotal = max(0, $overrideAmount);
// Audit the override
\Log::channel('audit')->info('super_admin_price_override', [
'actor_id' => $actor->id,
'actor_name' => $actor->name,
'participant_name' => $this->participant_name_ar,
'program' => $program->name_ar,
'original_piasters' => $computedTotal,
'override_piasters' => $finalTotal,
'reason' => $this->priceOverrideReason,
]);
}
// 8. Create invoice if there's a fee or hot-buy items
$invoice = null;
if ($subtotal > 0) {
if ($subtotal > 0 || $finalTotal !== $computedTotal) {
$invoiceItems = [];
if ($feeAmount > 0) {
if ($programFee > 0) {
$description = $program->name_ar;
if ($prorationResult->applied) {
$description .= ' (' . $prorationResult->description . ')';
}
$invoiceItems[] = [
'description' => $program->name_ar,
'description' => $description,
'quantity' => 1,
'unit_price' => $feeAmount,
'unit_price' => $programFee,
'discount_amount' => 0,
'tax_amount' => 0,
];
......@@ -622,6 +800,12 @@ public function confirm(): void
];
}
// If override changed total, reflect as a discount line
$discountAmount = 0;
if ($this->priceOverrideEnabled && $actor->is_super_admin && $finalTotal < $computedTotal) {
$discountAmount = $computedTotal - $finalTotal;
}
$invoice = $invoiceService->create([
'academy_id' => app('current_academy')->id,
'number' => $invoiceService->generateNumber(app('current_academy')->id),
......@@ -631,20 +815,17 @@ public function confirm(): void
'contact_name' => $this->guardian_name_ar,
'contact_phone' => $this->guardian_phone,
'subtotal_amount' => $subtotal,
'discount_amount' => 0,
'discount_amount' => $discountAmount,
'tax_amount' => 0,
'service_fee_amount' => $serviceFee,
'total_amount' => $totalWithFee,
'total_amount' => $finalTotal,
'currency' => 'EGP',
'issue_date' => now()->toDateString(),
'due_date' => now()->addDays(7)->toDateString(),
'notes' => 'اشتراك: ' . $program->name_ar,
], $invoiceItems, $actor);
// Send the invoice (mark as sent)
$invoice->update(['status' => 'sent']);
// Link enrollment to invoice
$enrollment->update(['invoice_id' => $invoice->id]);
$this->invoice_amount = $invoice->total_amount;
......@@ -700,7 +881,6 @@ private function resolveProgramFee(TrainingProgram $program): int
->where(fn ($q) => $q->whereNull('effective_to')->orWhere('effective_to', '>=', now()))
->forBranch($this->branchId);
// Try membership-specific price first
$specificPrice = (clone $query)
->whereJsonContains('metadata->membership_type', $this->membership_type)
->orderByDesc('priority')
......@@ -710,7 +890,6 @@ private function resolveProgramFee(TrainingProgram $program): int
return $specificPrice->amount;
}
// Fall back to generic price (no membership_type in metadata)
$genericPrice = $query
->where(fn ($q) => $q
->whereNull('metadata->membership_type')
......@@ -723,53 +902,6 @@ private function resolveProgramFee(TrainingProgram $program): int
return $genericPrice?->amount ?? 0;
}
#[Computed]
public function selectedProgram(): ?TrainingProgram
{
if (!$this->selected_program_id) {
return null;
}
return TrainingProgram::with('activity')->find($this->selected_program_id);
}
#[Computed]
public function selectedProgramFee(): int
{
if (!$this->selected_program_id) {
return 0;
}
$program = TrainingProgram::find($this->selected_program_id);
return $program ? $this->resolveProgramFee($program) : 0;
}
#[Computed]
public function platformFee(): int
{
$service = app(PlatformFeeService::class);
if (!$service->customerPays()) {
return 0;
}
$subtotal = $this->selectedProgramFee + $this->hotbuyTotal;
return $service->calculate($subtotal);
}
#[Computed]
public function totalWithFee(): int
{
return $this->selectedProgramFee + $this->hotbuyTotal + $this->platformFee;
}
#[Computed]
public function invoiceForPrint(): ?Invoice
{
if (!$this->invoiceId) {
return null;
}
return Invoice::with('items')->find($this->invoiceId);
}
public function render()
{
$activities = Activity::where('is_active', true)->orderBy('name_ar')->get();
......@@ -788,6 +920,7 @@ public function render()
return view('livewire.receptionist.new-registration-wizard', [
'activities' => $activities,
'programs' => $programs,
'isSuperAdmin' => auth()->user()?->is_super_admin ?? false,
'relationOptions' => [
'father' => 'أب',
'mother' => 'أم',
......
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('people', function (Blueprint $table) {
$table->string('governorate', 100)->nullable()->after('national_id');
});
}
public function down(): void
{
Schema::table('people', function (Blueprint $table) {
$table->dropColumn('governorate');
});
}
};
......@@ -54,6 +54,7 @@ public function run(): void
$this->call(RolesAndPermissionsSeeder::class);
$this->call(PermissionSeeder::class);
$this->call(PaymentNotificationTemplateSeeder::class);
$this->call(EnrollmentSettingsSeeder::class);
// Assign academy_owner role + super_admin
$ownerRole = Role::where('academy_id', $academy->id)
......
<?php
namespace Database\Seeders;
use App\Domain\Shared\Models\SystemSetting;
use App\Domain\Identity\Models\Organization;
use Illuminate\Database\Seeder;
class EnrollmentSettingsSeeder extends Seeder
{
public function run(): void
{
$academies = Organization::all();
$defaults = [
[
'group' => 'enrollment',
'key' => 'enrollment.allow_proration',
'value' => '0',
'type' => 'boolean',
'label_ar' => 'تفعيل الدفع الجزئي (تناسبي)',
'description_ar' => 'عند تفعيله، يدفع المشترك الذي يلتحق في منتصف الشهر نسبة الأيام المتبقية فقط',
],
[
'group' => 'enrollment',
'key' => 'enrollment.renewal_day',
'value' => '1',
'type' => 'integer',
'label_ar' => 'يوم التجديد الشهري',
'description_ar' => 'اليوم من كل شهر الذي يُعتبر موعد تجديد الاشتراك (1-28)',
],
];
foreach ($academies as $academy) {
foreach ($defaults as $setting) {
SystemSetting::firstOrCreate(
['academy_id' => $academy->id, 'key' => $setting['key']],
array_merge($setting, ['academy_id' => $academy->id]),
);
}
}
}
}
......@@ -22,14 +22,14 @@
@endif
<form wire:submit="login">
<!-- Email -->
<!-- Identifier (email or phone) -->
<div class="mb-4">
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">{{ __('البريد الإلكتروني') }}</label>
<input type="email" id="email" wire:model="email"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:border-transparent text-sm sm:text-base @error('email') border-red-500 @enderror"
<label for="identifier" class="block text-sm font-medium text-gray-700 mb-1">{{ __('البريد الإلكتروني أو رقم الهاتف') }}</label>
<input type="text" id="identifier" wire:model="identifier"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:border-transparent text-sm sm:text-base @error('identifier') border-red-500 @enderror"
style="--tw-ring-color: var(--brand-primary, #2563eb);"
placeholder="admin@example.com" dir="ltr" required autofocus inputmode="email" autocomplete="email">
@error('email')
placeholder="{{ __('admin@example.com أو 01012345678') }}" dir="ltr" required autofocus autocomplete="username">
@error('identifier')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
......
......@@ -272,9 +272,16 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-emerald-600 text-whi
<span class="font-medium text-gray-800 ms-1">{{ $program->activity?->name_ar }}</span>
</div>
@if($this->selectedProgramFee > 0)
<div>
@php $proration = $this->proratedProgramFee; @endphp
<div class="col-span-2">
<span class="text-gray-500">{{ __('الرسوم') }}:</span>
<span class="font-bold text-green-700 ms-1" dir="ltr">{{ number_format($this->selectedProgramFee / 100, 2) }} {{ __('ج.م') }}</span>
@if($proration->applied)
<span class="line-through text-gray-400 ms-1" dir="ltr">{{ number_format($proration->originalAmount / 100, 2) }} {{ __('ج.م') }}</span>
<span class="font-bold text-green-700 ms-2" dir="ltr">{{ number_format($proration->proratedAmount / 100, 2) }} {{ __('ج.م') }}</span>
<span class="text-xs text-blue-600 ms-1">({{ $proration->description }})</span>
@else
<span class="font-bold text-green-700 ms-1" dir="ltr">{{ number_format($proration->proratedAmount / 100, 2) }} {{ __('ج.م') }}</span>
@endif
</div>
@endif
@endif
......@@ -284,6 +291,7 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-emerald-600 text-whi
{{-- Payment Option --}}
@if($program && $this->selectedProgramFee > 0)
@php $proration = $this->proratedProgramFee; @endphp
<div class="border-t border-gray-200 pt-6">
<div class="flex items-center gap-4 mb-4">
<label class="relative cursor-pointer" dir="ltr">
......
<div>
{{-- Header — compact on mobile --}}
{{-- Header --}}
<div class="flex items-center justify-between mb-4 sm:mb-6 print:hidden">
<div class="min-w-0">
<h1 class="text-lg sm:text-2xl font-bold text-gray-800 truncate">{{ __('تسجيل جديد') }}</h1>
......@@ -22,7 +22,7 @@ class="inline-flex items-center justify-center w-10 h-10 sm:w-auto sm:h-auto sm:
</div>
@endif
{{-- System Pre-flight Errors — Block Wizard --}}
{{-- System Pre-flight Errors --}}
@if(!empty($systemErrors))
<div class="bg-white rounded-xl shadow-sm border border-red-300 p-8 text-center">
<div class="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
......@@ -31,7 +31,6 @@ class="inline-flex items-center justify-center w-10 h-10 sm:w-auto sm:h-auto sm:
</svg>
</div>
<h2 class="text-xl font-bold text-gray-800 mb-2">{{ __('لا يمكن بدء التسجيل') }}</h2>
<p class="text-gray-500 text-sm mb-6">{{ __('يجب إصلاح المشاكل التالية قبل استخدام معالج التسجيل:') }}</p>
<ul class="text-start text-sm space-y-3 max-w-md mx-auto">
@foreach($systemErrors as $error)
<li class="flex items-start gap-3 p-3 bg-red-50 rounded-lg border border-red-100">
......@@ -55,9 +54,9 @@ class="inline-flex items-center gap-2 px-6 py-3 bg-gray-100 text-gray-700 rounde
@if($currentStep < 7)
@php
$steps = [
1 => 'بيانات ولي الأمر',
2 => 'حساب ولي الأمر',
3 => 'بيانات المشترك',
1 => 'بيانات اللاعب',
2 => 'بيانات ولي الأمر',
3 => 'حساب ولي الأمر',
4 => 'اختيار البرنامج',
5 => 'مراجعة وتأكيد',
6 => 'الدفع',
......@@ -113,17 +112,205 @@ class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'curs
{{-- Step Content --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6 pb-28 sm:pb-6">
{{-- Step 1: Guardian Info --}}
{{-- ===================== STEP 1: PLAYER DATA ===================== --}}
@if($currentStep === 1)
<div>
<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-5">
{{-- Name (Arabic) --}}
<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.live.debounce.400ms="participant_name_ar"
class="w-full px-4 py-3 min-h-[52px] border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-lg"
placeholder="{{ __('الاسم رباعي') }}">
@error('participant_name_ar') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- NID + Foreign toggle --}}
<div class="sm:col-span-2">
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-medium text-gray-700">{{ __('الرقم القومي') }}</label>
{{-- Foreign toggle --}}
<label class="flex items-center gap-2 cursor-pointer select-none">
<span class="text-xs text-gray-500">{{ __('أجنبي') }}</span>
<div class="relative" dir="ltr">
<input type="checkbox" wire:model.live="participant_is_foreign" class="peer sr-only">
<div class="w-10 h-6 rounded-full bg-gray-300 peer-checked:bg-amber-500 transition-colors
after:content-[''] after:absolute after:top-1 after:left-1 after:w-4 after:h-4
after:bg-white after:rounded-full after:transition-all peer-checked:after:translate-x-4"></div>
</div>
</label>
</div>
<div class="relative">
<input type="text" wire:model.live="participant_national_id" dir="ltr"
class="w-full px-4 py-3 min-h-[52px] border rounded-lg focus:ring-2 focus:border-blue-500 text-lg font-mono
{{ $participant_nid_decoded ? 'border-green-400 bg-green-50 focus:ring-green-400' : 'border-gray-300 focus:ring-blue-500' }}
{{ $participant_is_foreign ? 'opacity-60' : '' }}"
placeholder="{{ $participant_is_foreign ? __('رقم جواز السفر (اختياري)') : '00000000000000' }}"
maxlength="{{ $participant_is_foreign ? '30' : '14' }}">
@if($participant_nid_decoded)
<div class="absolute inset-y-0 end-3 flex items-center pointer-events-none">
<svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
</div>
@endif
</div>
@if($participant_nid_decoded)
<p class="mt-1 text-xs text-green-600 flex items-center gap-1">
<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="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
{{ __('تم استخراج البيانات من الرقم القومي') }}
</p>
@endif
@error('participant_national_id') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Date of Birth (locked when NID decoded) --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
{{ __('تاريخ الميلاد') }} <span class="text-red-500">*</span>
@if($participant_nid_decoded)
<span class="inline-flex items-center gap-1 text-xs text-green-600 ms-1 font-normal">
<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 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
{{ __('مقفل') }}
</span>
@endif
</label>
<input type="date" wire:model="participant_date_of_birth" dir="ltr"
@if($participant_nid_decoded) readonly @endif
class="w-full px-4 py-3 min-h-[52px] border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-lg
{{ $participant_nid_decoded ? 'bg-gray-50 text-gray-500 cursor-not-allowed' : '' }}">
@error('participant_date_of_birth') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Gender (locked when NID decoded) --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
{{ __('الجنس') }} <span class="text-red-500">*</span>
@if($participant_nid_decoded)
<span class="inline-flex items-center gap-1 text-xs text-green-600 ms-1 font-normal">
<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 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
{{ __('مقفل') }}
</span>
@endif
</label>
<div class="grid grid-cols-2 gap-3 {{ $participant_nid_decoded ? 'pointer-events-none opacity-70' : '' }}">
<label class="relative cursor-pointer">
<input type="radio" wire:model="participant_gender" value="male" class="peer sr-only" @if($participant_nid_decoded) disabled @endif>
<div class="px-4 py-3 min-h-[52px] flex items-center justify-center border border-gray-300 rounded-lg text-center text-base font-medium transition-all
peer-checked:border-blue-500 peer-checked:bg-blue-50 peer-checked:text-blue-700 hover:border-gray-400">
{{ __('ذكر') }}
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model="participant_gender" value="female" class="peer sr-only" @if($participant_nid_decoded) disabled @endif>
<div class="px-4 py-3 min-h-[52px] flex items-center justify-center border border-gray-300 rounded-lg text-center text-base font-medium transition-all
peer-checked:border-pink-500 peer-checked:bg-pink-50 peer-checked:text-pink-700 hover:border-gray-400">
{{ __('أنثى') }}
</div>
</label>
</div>
@error('participant_gender') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Governorate (locked when NID decoded) --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
{{ __('المحافظة') }}
@if($participant_nid_decoded)
<span class="inline-flex items-center gap-1 text-xs text-green-600 ms-1 font-normal">
<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 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
{{ __('مقفل') }}
</span>
@endif
</label>
<input type="text" wire:model="participant_governorate"
@if($participant_nid_decoded) readonly @endif
class="w-full px-4 py-3 min-h-[52px] border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-lg
{{ $participant_nid_decoded ? 'bg-gray-50 text-gray-500 cursor-not-allowed' : '' }}"
placeholder="{{ __('المحافظة') }}">
</div>
{{-- Phone (optional) --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('رقم الهاتف (اختياري)') }}</label>
<input type="tel" wire:model="participant_phone" dir="ltr"
class="w-full px-4 py-3 min-h-[52px] border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-lg"
placeholder="01xxxxxxxxx">
@error('participant_phone') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Membership Type --}}
<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>
<div class="grid grid-cols-2 gap-3">
<label class="relative cursor-pointer">
<input type="radio" wire:model.live="membership_type" value="non_member" class="peer sr-only">
<div class="px-4 py-3 min-h-[52px] flex items-center justify-center border border-gray-300 rounded-lg text-center text-base font-medium transition-all
peer-checked:border-amber-500 peer-checked:bg-amber-50 peer-checked:text-amber-700 hover:border-gray-400">
{{ __('غير عضو') }}
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model.live="membership_type" value="member" class="peer sr-only">
<div class="px-4 py-3 min-h-[52px] flex items-center justify-center border border-gray-300 rounded-lg text-center text-base font-medium transition-all
peer-checked:border-green-500 peer-checked:bg-green-50 peer-checked:text-green-700 hover:border-gray-400">
{{ __('عضو نادي') }}
</div>
</label>
</div>
@error('membership_type') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
@if($membership_type === 'member')
<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="membership_id" dir="ltr"
class="w-full px-4 py-3 min-h-[52px] border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 text-lg"
placeholder="{{ __('أدخل رقم العضوية') }}">
@error('membership_id') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
@endif
{{-- Medical notes --}}
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('ملاحظات طبية (اختياري)') }}</label>
<textarea wire:model="participant_medical_notes" rows="2"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
placeholder="{{ __('حساسية، أدوية، إصابات سابقة...') }}"></textarea>
@error('participant_medical_notes') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
</div>
<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-end z-30">
<button wire:click="nextStep" wire:loading.attr="disabled"
class="w-full sm:w-auto 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 2: GUARDIAN ===================== --}}
@if($currentStep === 2)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-1">{{ __('بيانات ولي الأمر') }}</h2>
<p class="text-sm text-gray-500 mb-5">{{ __('الهاتف هو وسيلة التواصل الرئيسية') }}</p>
{{-- Toggle: search existing or new --}}
@if(!$guardianSelected)
<div class="flex gap-3 mb-6">
<button wire:click="$set('searchExistingGuardian', false)" type="button"
class="flex-1 px-4 py-3 text-sm font-medium rounded-lg border transition-colors {{ !$searchExistingGuardian ? 'border-blue-500 bg-blue-50 text-blue-700' : 'border-gray-300 text-gray-600 hover:border-gray-400' }}">
{{ __('تسجيل ولي أمر جديد') }}
{{ __('ولي أمر جديد') }}
</button>
<button wire:click="$set('searchExistingGuardian', true)" type="button"
class="flex-1 px-4 py-3 text-sm font-medium rounded-lg border transition-colors {{ $searchExistingGuardian ? 'border-blue-500 bg-blue-50 text-blue-700' : 'border-gray-300 text-gray-600 hover:border-gray-400' }}">
......@@ -137,8 +324,7 @@ class="flex-1 px-4 py-3 text-sm font-medium rounded-lg border transition-colors
<div class="mb-6">
<input type="text" wire:model.live.debounce.300ms="guardianSearchQuery"
placeholder="{{ __('ابحث بالاسم أو رقم الهاتف...') }}"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-lg">
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 text-lg">
@if(!empty($guardianSearchResults))
<div class="mt-3 space-y-2 max-h-60 overflow-y-auto">
@foreach($guardianSearchResults as $result)
......@@ -149,7 +335,7 @@ class="w-full flex items-center justify-between p-3 bg-gray-50 rounded-lg border
<div class="flex items-center gap-3 text-sm text-gray-500 mt-0.5">
<span dir="ltr">{{ $result['phone'] }}</span>
@if($result['children_count'] > 0)
<span class="px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs">{{ $result['children_count'] }} {{ __('أبناء مسجلين') }}</span>
<span class="px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs">{{ $result['children_count'] }} {{ __('أبناء') }}</span>
@endif
</div>
</div>
......@@ -165,62 +351,53 @@ class="w-full flex items-center justify-between p-3 bg-gray-50 rounded-lg border
{{-- Selected guardian display --}}
@if($guardianSelected)
<div class="mb-6 p-4 bg-green-50 border border-green-200 rounded-xl">
<div class="flex items-center justify-between">
<div>
<p class="font-semibold text-green-800">{{ $guardian_name_ar }}</p>
<p class="text-sm text-green-600" dir="ltr">{{ $guardian_phone }}</p>
</div>
<button wire:click="clearSelectedGuardian" type="button" class="text-sm text-green-700 hover:text-green-900 font-medium underline">
{{ __('تغيير') }}
</button>
<div class="mb-6 p-4 bg-green-50 border border-green-200 rounded-xl flex items-center justify-between">
<div>
<p class="font-semibold text-green-800">{{ $guardian_name_ar }}</p>
<p class="text-sm text-green-600" dir="ltr">{{ $guardian_phone }}</p>
</div>
<button wire:click="clearSelectedGuardian" type="button" class="text-sm text-green-700 hover:text-green-900 font-medium underline">
{{ __('تغيير') }}
</button>
</div>
@endif
{{-- Guardian form (new or show data) --}}
{{-- Guardian form --}}
@if(!$searchExistingGuardian || $guardianSelected)
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5 {{ $guardianSelected ? 'opacity-60 pointer-events-none' : '' }}">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الاسم بالعربية') }} <span class="text-red-500">*</span></label>
{{-- Name (auto-filled hint) --}}
<div class="sm:col-span-2">
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-medium text-gray-700">{{ __('اسم ولي الأمر') }} <span class="text-red-500">*</span></label>
@if($guardian_name_ar && $participant_name_ar)
<span class="text-xs text-blue-500">{{ __('مأخوذ من اسم اللاعب — يمكنك التعديل') }}</span>
@endif
</div>
<input type="text" wire:model="guardian_name_ar"
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"
class="w-full px-4 py-3 min-h-[52px] border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-lg"
placeholder="{{ __('اسم ولي الأمر') }}">
@error('guardian_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>
<input type="text" wire:model="guardian_name" 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="Guardian name">
</div>
{{-- Phone (required) --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('رقم الهاتف') }} <span class="text-red-500">*</span></label>
<input type="tel" wire:model="guardian_phone" 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"
class="w-full px-4 py-3 min-h-[52px] border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-lg"
placeholder="01xxxxxxxxx">
@error('guardian_phone') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Relation --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الرقم القومي') }}</label>
<input type="text" wire:model="guardian_national_id" 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="00000000000000" maxlength="14">
@error('guardian_national_id') <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">{{ __('صلة القرابة') }} <span class="text-red-500">*</span></label>
<div class="grid grid-cols-3 sm:grid-cols-5 gap-3">
<div class="grid grid-cols-3 sm:grid-cols-5 gap-2">
@foreach($relationOptions as $value => $label)
<label class="relative cursor-pointer">
<input type="radio" wire:model="guardian_relation" value="{{ $value }}" class="peer sr-only">
<div class="px-4 py-3 min-h-16 flex items-center justify-center border border-gray-300 rounded-lg text-center text-sm font-medium transition-all
peer-checked:border-blue-500 peer-checked:bg-blue-50 peer-checked:text-blue-700
hover:border-gray-400">
<div class="px-2 py-2.5 flex items-center justify-center border border-gray-300 rounded-lg text-center text-sm font-medium transition-all
peer-checked:border-blue-500 peer-checked:bg-blue-50 peer-checked:text-blue-700 hover:border-gray-400">
{{ __($label) }}
</div>
</label>
......@@ -240,10 +417,9 @@ class="w-full px-4 py-3 min-h-16 border border-gray-300 rounded-lg focus:ring-2
</svg>
<div>
<h3 class="text-base font-bold text-amber-800">{{ __('تنبيه: تم العثور على سجلات مشابهة') }}</h3>
<p class="text-sm text-amber-700 mt-1">{{ __('قد يكون ولي الأمر مسجلاً بالفعل. يرجى التحقق من السجلات التالية:') }}</p>
<p class="text-sm text-amber-700 mt-1">{{ __('قد يكون ولي الأمر مسجلاً بالفعل:') }}</p>
</div>
</div>
<div class="space-y-3">
@foreach($potentialDuplicates as $duplicate)
<div class="flex items-center justify-between p-3 bg-white rounded-lg border border-amber-200">
......@@ -254,35 +430,37 @@ class="w-full px-4 py-3 min-h-16 border border-gray-300 rounded-lg focus:ring-2
<span dir="ltr">{{ $duplicate['phone'] }}</span>
@endif
@if($duplicate['has_participant'])
<span class="inline-flex items-center gap-1 px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs font-medium">
{{ __('مشترك') }}: {{ $duplicate['participant_number'] }}
</span>
<span class="px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs font-medium">{{ $duplicate['participant_number'] }}</span>
@endif
<span class="inline-flex items-center gap-1 px-2 py-0.5 bg-gray-100 text-gray-600 rounded text-xs">
{{ __('تطابق') }}: {{ $duplicate['confidence'] }}%
</span>
<span class="px-2 py-0.5 bg-gray-100 text-gray-600 rounded text-xs">{{ $duplicate['confidence'] }}%</span>
</div>
</div>
<button wire:click="useExistingPerson({{ $duplicate['person_id'] }})"
class="px-4 py-2 text-sm font-medium text-blue-700 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition-colors whitespace-nowrap">
{{ __('استخدام هذا السجل') }}
{{ __('استخدام') }}
</button>
</div>
@endforeach
</div>
<div class="mt-4 flex justify-end">
<button wire:click="proceedDespiteDuplicates"
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-amber-700 bg-amber-100 border border-amber-300 rounded-lg hover:bg-amber-200 transition-colors">
{{ __('تجاهل والمتابعة كتسجيل جديد') }}
class="px-4 py-2 text-sm font-medium text-amber-700 bg-amber-100 border border-amber-300 rounded-lg hover:bg-amber-200 transition-colors">
{{ __('تجاهل والمتابعة كجديد') }}
</button>
</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-end z-30">
<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="w-full sm:w-auto 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">
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">
......@@ -293,82 +471,52 @@ class="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-6 py-3.
</div>
@endif
{{-- Step 2: Parent Account --}}
@if($currentStep === 2)
{{-- ===================== STEP 3: PARENT ACCOUNT ===================== --}}
@if($currentStep === 3)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-2">{{ __('حساب ولي الأمر') }}</h2>
<p class="text-sm text-gray-500 mb-6">{{ __('إنشاء حساب لولي الأمر لمتابعة ابنه') }}</p>
<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>
<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"
class="w-full px-4 py-3 min-h-[52px] 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"
class="flex-1 px-4 py-3 min-h-[52px] 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 class="p-4 bg-amber-50 border border-amber-200 rounded-xl text-sm text-amber-700">
{{ __('يمكنك إنشاء الحساب لاحقاً من إدارة المستخدمين') }}
</div>
@endif
......@@ -392,152 +540,19 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-6 py
</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">
<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="participant_name_ar"
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="{{ __('اسم اللاعب / المشترك') }}">
@error('participant_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>
<input type="text" wire:model="participant_name" 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="Player / participant name">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تاريخ الميلاد') }} <span class="text-red-500">*</span></label>
<input type="date" wire:model="participant_date_of_birth" 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">
@error('participant_date_of_birth') <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">{{ __('الجنس') }} <span class="text-red-500">*</span></label>
<div class="grid grid-cols-2 gap-3">
<label class="relative cursor-pointer">
<input type="radio" wire:model="participant_gender" value="male" class="peer sr-only">
<div class="px-4 py-3 min-h-16 flex items-center justify-center border border-gray-300 rounded-lg text-center text-base font-medium transition-all
peer-checked:border-blue-500 peer-checked:bg-blue-50 peer-checked:text-blue-700
hover:border-gray-400">
{{ __('ذكر') }}
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model="participant_gender" value="female" class="peer sr-only">
<div class="px-4 py-3 min-h-16 flex items-center justify-center border border-gray-300 rounded-lg text-center text-base font-medium transition-all
peer-checked:border-pink-500 peer-checked:bg-pink-50 peer-checked:text-pink-700
hover:border-gray-400">
{{ __('أنثى') }}
</div>
</label>
</div>
@error('participant_gender') <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="tel" wire:model="participant_phone" 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="01xxxxxxxxx">
@error('participant_phone') <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="participant_national_id" 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="00000000000000" maxlength="14">
@error('participant_national_id') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Membership Type --}}
<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>
<div class="grid grid-cols-2 gap-3">
<label class="relative cursor-pointer">
<input type="radio" wire:model.live="membership_type" value="non_member" class="peer sr-only">
<div class="px-4 py-3 min-h-16 flex items-center justify-center border border-gray-300 rounded-lg text-center text-base font-medium transition-all
peer-checked:border-amber-500 peer-checked:bg-amber-50 peer-checked:text-amber-700
hover:border-gray-400">
{{ __('غير عضو') }}
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model.live="membership_type" value="member" class="peer sr-only">
<div class="px-4 py-3 min-h-16 flex items-center justify-center border border-gray-300 rounded-lg text-center text-base font-medium transition-all
peer-checked:border-green-500 peer-checked:bg-green-50 peer-checked:text-green-700
hover:border-gray-400">
{{ __('عضو نادي') }}
</div>
</label>
</div>
@error('membership_type') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Membership ID (only for members) --}}
@if($membership_type === 'member')
<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="membership_id" dir="ltr"
class="w-full px-4 py-3 min-h-16 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 text-lg"
placeholder="{{ __('أدخل رقم العضوية') }}">
@error('membership_id') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
@endif
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('ملاحظات طبية') }}</label>
<textarea wire:model="participant_medical_notes" rows="3"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="{{ __('أي ملاحظات طبية مهمة (حساسية، أدوية، إصابات سابقة)') }}"></textarea>
@error('participant_medical_notes') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
</div>
<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 4: Program Selection --}}
{{-- ===================== STEP 4: PROGRAM SELECTION ===================== --}}
@if($currentStep === 4)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('اختيار البرنامج') }}</h2>
{{-- Activity Selection --}}
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-3">{{ __('اختر النشاط') }}</label>
{{-- Mobile: horizontal scrollable pills --}}
<div class="flex gap-2.5 overflow-x-auto pb-2 -mx-4 px-4 sm:mx-0 sm:px-0 sm:flex-wrap sm:overflow-visible scrollbar-hide">
@foreach($activities as $activity)
<label class="relative cursor-pointer shrink-0 sm:shrink">
<input type="radio" wire:model.live="selected_activity_id" value="{{ $activity->id }}" class="peer sr-only">
<div class="px-5 py-3 sm:p-4 sm:min-h-16 flex items-center justify-center border border-gray-300 rounded-full sm:rounded-xl text-center font-medium transition-all whitespace-nowrap
peer-checked:border-blue-500 peer-checked:bg-blue-50 peer-checked:text-blue-700 peer-checked:shadow-sm
hover:border-gray-400">
<div class="px-5 py-3 sm:p-4 sm:min-h-[52px] flex items-center justify-center border border-gray-300 rounded-full sm:rounded-xl text-center font-medium transition-all whitespace-nowrap
peer-checked:border-blue-500 peer-checked:bg-blue-50 peer-checked:text-blue-700 peer-checked:shadow-sm hover:border-gray-400">
{{ $activity->name_ar }}
</div>
</label>
......@@ -546,15 +561,11 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-6 py
@error('selected_activity_id') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Program Selection --}}
@if($selected_activity_id)
<div>
<label class="block text-sm font-medium text-gray-700 mb-3">{{ __('اختر البرنامج') }}</label>
@if($programs->isEmpty())
<div class="text-center py-8 text-gray-500">
<svg class="w-12 h-12 mx-auto text-gray-300 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"/>
</svg>
<p>{{ __('لا يوجد برامج متاحة لهذا النشاط') }}</p>
</div>
@else
......@@ -562,15 +573,11 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-6 py
@foreach($programs as $program)
<label class="relative cursor-pointer">
<input type="radio" wire:model="selected_program_id" value="{{ $program->id }}" class="peer sr-only">
<div class="p-5 min-h-16 border border-gray-300 rounded-xl transition-all
peer-checked:border-blue-500 peer-checked:bg-blue-50 peer-checked:shadow-sm
hover:border-gray-400">
<div class="p-5 border border-gray-300 rounded-xl transition-all
peer-checked:border-blue-500 peer-checked:bg-blue-50 peer-checked:shadow-sm hover:border-gray-400">
<h4 class="font-bold text-gray-800">{{ $program->name_ar }}</h4>
@if($program->name)
<p class="text-xs text-gray-500 mt-0.5" dir="ltr">{{ $program->name }}</p>
@endif
@if($program->description)
<p class="text-sm text-gray-600 mt-2">{{ Str::limit($program->description, 80) }}</p>
<p class="text-sm text-gray-600 mt-1">{{ Str::limit($program->description, 80) }}</p>
@endif
</div>
</label>
......@@ -601,169 +608,95 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-6 py
</div>
@endif
{{-- Step 5: Review --}}
{{-- ===================== STEP 5: REVIEW ===================== --}}
@if($currentStep === 5)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('مراجعة وتأكيد') }}</h2>
<div class="space-y-4">
{{-- Guardian Summary --}}
{{-- Player 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>
<h3 class="font-semibold text-gray-700">{{ __('اللاعب') }}</h3>
<button wire:click="goToStep(1)" class="text-sm text-blue-600 hover:text-blue-800">{{ __('تعديل') }}</button>
</div>
<div class="grid grid-cols-2 gap-3 text-sm">
<div>
<span class="text-gray-500">{{ __('الاسم') }}:</span>
<span class="font-medium text-gray-800 ms-1">{{ $guardian_name_ar }}</span>
</div>
<div>
<span class="text-gray-500">{{ __('الهاتف') }}:</span>
<span class="font-medium text-gray-800 ms-1" dir="ltr">{{ $guardian_phone }}</span>
</div>
<div>
<span class="text-gray-500">{{ __('صلة القرابة') }}:</span>
<span class="font-medium text-gray-800 ms-1">{{ __($relationOptions[$guardian_relation] ?? $guardian_relation) }}</span>
</div>
@if($guardian_national_id)
<div>
<span class="text-gray-500">{{ __('الرقم القومي') }}:</span>
<span class="font-medium text-gray-800 ms-1" dir="ltr">{{ $guardian_national_id }}</span>
</div>
<div><span class="text-gray-500">{{ __('الاسم') }}:</span> <span class="font-medium text-gray-800 ms-1">{{ $participant_name_ar }}</span></div>
<div><span class="text-gray-500">{{ __('تاريخ الميلاد') }}:</span> <span class="font-medium text-gray-800 ms-1" dir="ltr">{{ $participant_date_of_birth }}</span></div>
<div><span class="text-gray-500">{{ __('الجنس') }}:</span> <span class="font-medium text-gray-800 ms-1">{{ $participant_gender === 'male' ? __('ذكر') : __('أنثى') }}</span></div>
@if($participant_governorate)
<div><span class="text-gray-500">{{ __('المحافظة') }}:</span> <span class="font-medium text-gray-800 ms-1">{{ $participant_governorate }}</span></div>
@endif
</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>
<span class="text-gray-500">{{ __('العضوية') }}:</span>
<span class="font-medium ms-1 {{ $membership_type === 'member' ? 'text-green-700' : 'text-amber-700' }}">
{{ $membership_type === 'member' ? __('عضو') : __('غير عضو') }}
</span>
</div>
@else
<p class="text-gray-500">{{ __('لن يتم إنشاء حساب — يمكن إنشاؤه لاحقاً') }}</p>
@endif
</div>
</div>
{{-- Participant Summary --}}
{{-- Guardian 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(3)" class="text-sm text-blue-600 hover:text-blue-800">{{ __('تعديل') }}</button>
<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="grid grid-cols-2 gap-3 text-sm">
<div>
<span class="text-gray-500">{{ __('الاسم') }}:</span>
<span class="font-medium text-gray-800 ms-1">{{ $participant_name_ar }}</span>
</div>
<div>
<span class="text-gray-500">{{ __('تاريخ الميلاد') }}:</span>
<span class="font-medium text-gray-800 ms-1" dir="ltr">{{ $participant_date_of_birth }}</span>
</div>
<div>
<span class="text-gray-500">{{ __('الجنس') }}:</span>
<span class="font-medium text-gray-800 ms-1">{{ $participant_gender === 'male' ? __('ذكر') : __('أنثى') }}</span>
</div>
@if($participant_phone)
<div>
<span class="text-gray-500">{{ __('الهاتف') }}:</span>
<span class="font-medium text-gray-800 ms-1" dir="ltr">{{ $participant_phone }}</span>
</div>
@endif
@if($participant_national_id)
<div>
<span class="text-gray-500">{{ __('الرقم القومي') }}:</span>
<span class="font-medium text-gray-800 ms-1" dir="ltr">{{ $participant_national_id }}</span>
</div>
@endif
<div>
<span class="text-gray-500">{{ __('العضوية') }}:</span>
<span class="font-medium ms-1 {{ $membership_type === 'member' ? 'text-green-700' : 'text-amber-700' }}">
{{ $membership_type === 'member' ? __('عضو نادي') : __('غير عضو') }}
</span>
@if($membership_type === 'member' && $membership_id)
<span class="text-gray-500 ms-1">({{ $membership_id }})</span>
@endif
</div>
@if($participant_medical_notes)
<div class="col-span-2">
<span class="text-gray-500">{{ __('ملاحظات طبية') }}:</span>
<span class="font-medium text-gray-800 ms-1">{{ $participant_medical_notes }}</span>
</div>
@endif
<div><span class="text-gray-500">{{ __('الاسم') }}:</span> <span class="font-medium text-gray-800 ms-1">{{ $guardian_name_ar }}</span></div>
<div><span class="text-gray-500">{{ __('الهاتف') }}:</span> <span class="font-medium text-gray-800 ms-1" dir="ltr">{{ $guardian_phone }}</span></div>
<div><span class="text-gray-500">{{ __('الصلة') }}:</span> <span class="font-medium text-gray-800 ms-1">{{ __($relationOptions[$guardian_relation] ?? $guardian_relation) }}</span></div>
</div>
</div>
{{-- Program Summary --}}
{{-- Program Summary + Fee --}}
<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(4)" class="text-sm text-blue-600 hover:text-blue-800">{{ __('تعديل') }}</button>
</div>
@if($this->selectedProgram)
<div class="text-sm">
<p class="font-medium text-gray-800">{{ $this->selectedProgram->name_ar }}</p>
@if($this->selectedProgram->activity)
<p class="text-gray-500 mt-1">{{ __('النشاط') }}: {{ $this->selectedProgram->activity->name_ar }}</p>
@endif
@if($this->selectedProgramFee > 0)
<div class="mt-3 space-y-1 text-sm">
<div class="flex items-center justify-between">
<span class="text-gray-500">{{ __('رسوم البرنامج') }}:</span>
<span class="font-medium text-gray-800" dir="ltr">{{ number_format($this->selectedProgramFee / 100, 2) }} {{ __('ج.م') }}</span>
</div>
<p class="text-sm font-medium text-gray-800">{{ $this->selectedProgram->name_ar }}</p>
@if($this->selectedProgram->activity)
<p class="text-xs text-gray-500 mt-1">{{ $this->selectedProgram->activity->name_ar }}</p>
@endif
@if($this->selectedProgramFee > 0)
<div class="mt-3 space-y-1 text-sm border-t border-gray-200 pt-3">
@if($this->proratedProgramFee->applied)
<div class="flex items-center justify-between">
<span class="text-gray-500">{{ __('السعر الأصلي') }}:</span>
<span class="text-gray-400 line-through" dir="ltr">{{ number_format($this->proratedProgramFee->originalAmount / 100, 2) }} {{ __('ج.م') }}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-blue-600 font-medium">{{ __('رسوم البرنامج') }} <span class="text-xs font-normal">({{ $this->proratedProgramFee->description }})</span>:</span>
<span class="font-medium text-blue-700" dir="ltr">{{ number_format($this->proratedProgramFee->proratedAmount / 100, 2) }} {{ __('ج.م') }}</span>
</div>
@else
<p class="mt-2 text-gray-500">{{ __('بدون رسوم') }}</p>
<div class="flex items-center justify-between">
<span class="text-gray-500">{{ __('رسوم البرنامج') }}:</span>
<span class="font-medium text-gray-800" dir="ltr">{{ number_format($this->selectedProgramFee / 100, 2) }} {{ __('ج.م') }}</span>
</div>
@endif
</div>
@endif
@endif
</div>
{{-- Hot-Buy: Quick product/kit sale --}}
<div class="p-4 bg-amber-50 rounded-xl border border-amber-200" x-data="{ showSearch: false }">
<div class="flex items-center justify-between mb-3">
<h3 class="font-semibold text-gray-700 flex items-center gap-2">
<svg class="w-5 h-5 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"/>
</svg>
{{ __('بيع سريع (اختياري)') }}
</h3>
<h3 class="font-semibold text-gray-700">{{ __('بيع سريع (اختياري)') }}</h3>
<button type="button" @click="showSearch = !showSearch"
class="text-sm text-amber-700 hover:text-amber-900 font-medium">
<span x-show="!showSearch">{{ __('إضافة منتج') }}</span>
<span x-show="showSearch" x-cloak>{{ __('إغلاق') }}</span>
</button>
</div>
{{-- Search --}}
<div x-show="showSearch" x-cloak x-transition class="mb-3">
<input type="text" wire:model.live.debounce.300ms="hotbuy_search"
class="w-full px-4 py-3 border border-amber-300 rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-amber-500 text-sm"
placeholder="{{ __('ابحث بالاسم أو الكود أو الباركود...') }}">
placeholder="{{ __('ابحث بالاسم أو الكود...') }}">
@if(count($this->hotbuyResults) > 0)
<div class="mt-2 bg-white border border-gray-200 rounded-lg shadow-lg max-h-48 overflow-y-auto">
@foreach($this->hotbuyResults as $result)
......@@ -771,10 +704,7 @@ class="w-full px-4 py-3 border border-amber-300 rounded-lg focus:ring-2 focus:ri
class="w-full flex items-center justify-between px-4 py-3 text-start hover:bg-amber-50 border-b border-gray-100 last:border-0 transition-colors">
<div>
<p class="text-sm font-medium text-gray-800">{{ $result['name_ar'] }}</p>
<p class="text-xs text-gray-500">
{{ $result['type'] === 'kit' ? __('طقم') : __('منتج') }}
@if($result['sku']) — {{ $result['sku'] }} @endif
</p>
<p class="text-xs text-gray-500">{{ $result['type'] === 'kit' ? __('طقم') : __('منتج') }}@if($result['sku']) — {{ $result['sku'] }}@endif</p>
</div>
<span class="text-sm font-bold text-green-700" dir="ltr">{{ number_format($result['price'] / 100, 2) }} {{ __('ج.م') }}</span>
</button>
......@@ -784,8 +714,6 @@ class="w-full flex items-center justify-between px-4 py-3 text-start hover:bg-am
<p class="mt-2 text-xs text-gray-500 text-center">{{ __('لا توجد نتائج') }}</p>
@endif
</div>
{{-- Cart items --}}
@if(count($hotbuyCart) > 0)
<div class="space-y-2">
@foreach($hotbuyCart as $key => $cartItem)
......@@ -811,18 +739,18 @@ class="w-8 h-8 rounded-full bg-red-100 text-red-600 flex items-center justify-ce
@endforeach
</div>
@else
<p class="text-xs text-gray-500">{{ __('يمكنك إضافة منتجات أو طقم (يونيفورم، كرة، إلخ) على نفس الفاتورة') }}</p>
<p class="text-xs text-gray-500">{{ __('يمكنك إضافة منتجات أو طقم على نفس الفاتورة') }}</p>
@endif
</div>
{{-- Grand Total Summary --}}
@if($this->selectedProgramFee > 0 || $this->hotbuyTotal > 0)
{{-- Grand Total --}}
@if($this->totalWithFee > 0)
<div class="p-4 bg-green-50 rounded-xl border border-green-200">
<div class="space-y-1.5 text-sm">
@if($this->selectedProgramFee > 0)
@if($this->proratedProgramFee->proratedAmount > 0)
<div class="flex items-center justify-between">
<span class="text-gray-600">{{ __('رسوم البرنامج') }}</span>
<span class="font-medium text-gray-800" dir="ltr">{{ number_format($this->selectedProgramFee / 100, 2) }} {{ __('ج.م') }}</span>
<span class="font-medium text-gray-800" dir="ltr">{{ number_format($this->proratedProgramFee->proratedAmount / 100, 2) }} {{ __('ج.م') }}</span>
</div>
@endif
@if($this->hotbuyTotal > 0)
......@@ -839,7 +767,45 @@ class="w-8 h-8 rounded-full bg-red-100 text-red-600 flex items-center justify-ce
@endif
<div class="flex items-center justify-between pt-2 border-t border-green-200">
<span class="font-bold text-gray-800">{{ __('الإجمالي') }}</span>
<span class="font-bold text-green-700 text-lg" dir="ltr">{{ number_format($this->totalWithFee / 100, 2) }} {{ __('ج.م') }}</span>
<span class="font-bold text-green-700 text-lg" dir="ltr">{{ number_format($this->effectiveTotal / 100, 2) }} {{ __('ج.م') }}</span>
</div>
</div>
</div>
@endif
{{-- Super Admin Price Override --}}
@if($isSuperAdmin)
<div class="border border-dashed border-red-300 rounded-xl overflow-hidden" x-data="{ open: @entangle('priceOverrideEnabled') }">
<button type="button" @click="open = !open; $wire.set('priceOverrideEnabled', open)"
class="w-full flex items-center justify-between p-4 text-start hover:bg-red-50 transition-colors">
<div class="flex items-center gap-2">
<svg class="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/>
</svg>
<span class="text-sm font-semibold text-red-700">{{ __('تجاوز السعر (مدير النظام فقط)') }}</span>
</div>
<svg class="w-4 h-4 text-red-400 transition-transform" :class="open ? 'rotate-180' : ''" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<div x-show="open" x-cloak x-transition class="p-4 pt-0 space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('المبلغ المخصص (ج.م)') }}</label>
<input type="number" wire:model.live="priceOverrideInput" dir="ltr" step="0.01" min="0"
class="w-full max-w-xs px-4 py-3 border border-red-300 rounded-lg focus:ring-2 focus:ring-red-400 focus:border-red-400 text-lg font-mono"
placeholder="0.00">
@if($priceOverrideInput !== '' && $this->effectiveTotal !== $this->totalWithFee)
<p class="mt-1 text-xs text-red-600">
{{ __('بدلاً من') }} {{ number_format($this->totalWithFee / 100, 2) }} {{ __('ج.م') }}
({{ $this->effectiveTotal < $this->totalWithFee ? '-' : '+' }}{{ number_format(abs($this->effectiveTotal - $this->totalWithFee) / 100, 2) }} {{ __('ج.م') }})
</p>
@endif
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('سبب التعديل (اختياري)') }}</label>
<input type="text" wire:model="priceOverrideReason"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-400 text-sm"
placeholder="{{ __('مثال: خصم خاص، اتفاقية، إلخ') }}">
</div>
</div>
</div>
......@@ -866,46 +832,30 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-6 py
</div>
@endif
{{-- Step 6: Payment --}}
{{-- ===================== STEP 6: PAYMENT ===================== --}}
@if($currentStep === 6)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('الدفع') }}</h2>
@if($this->totalWithFee > 0)
<div class="p-4 bg-blue-50 border border-blue-200 rounded-xl mb-6 space-y-2">
@if($this->selectedProgramFee > 0)
<div class="flex items-center justify-between text-sm">
<span class="text-blue-600">{{ __('رسوم البرنامج') }}</span>
<span class="font-medium text-blue-700" dir="ltr">{{ number_format($this->selectedProgramFee / 100, 2) }} {{ __('ج.م') }}</span>
</div>
@endif
@if($this->hotbuyTotal > 0)
<div class="flex items-center justify-between text-sm">
<span class="text-blue-600">{{ __('منتجات إضافية') }} ({{ count($hotbuyCart) }})</span>
<span class="font-medium text-blue-700" dir="ltr">{{ number_format($this->hotbuyTotal / 100, 2) }} {{ __('ج.م') }}</span>
</div>
@endif
@if($this->platformFee > 0)
<div class="flex items-center justify-between text-sm">
<span class="text-blue-600">{{ __('رسوم الخدمة') }}</span>
<span class="font-medium text-blue-700" dir="ltr">{{ number_format($this->platformFee / 100, 2) }} {{ __('ج.م') }}</span>
</div>
@endif
<div class="flex items-center justify-between pt-2 border-t border-blue-200">
@if($this->effectiveTotal > 0)
<div class="p-4 bg-blue-50 border border-blue-200 rounded-xl mb-6">
<div class="flex items-center justify-between pt-2">
<span class="text-blue-700 font-semibold">{{ __('الإجمالي المطلوب') }}</span>
<span class="text-xl font-bold text-blue-800" dir="ltr">
{{ number_format($this->totalWithFee / 100, 2) }} {{ __('ج.م') }}
{{ number_format($this->effectiveTotal / 100, 2) }} {{ __('ج.م') }}
</span>
</div>
@if($priceOverrideEnabled && $isSuperAdmin && $this->effectiveTotal !== $this->totalWithFee)
<p class="text-xs text-red-600 mt-1 text-end">{{ __('سعر مُعدَّل من المدير') }}</p>
@endif
</div>
@else
<div class="p-4 bg-gray-50 border border-gray-200 rounded-xl mb-6">
<p class="text-gray-600 text-sm">{{ __('لا يوجد سعر محدد لهذا البرنامج حالياً — سيتم التسجيل بدون فاتورة.') }}</p>
<p class="text-gray-600 text-sm">{{ __('لا يوجد سعر محدد — سيتم التسجيل بدون فاتورة.') }}</p>
</div>
@endif
<div class="space-y-6">
{{-- Pay now toggle --}}
<div class="flex items-center gap-4">
<label class="relative cursor-pointer" dir="ltr">
<input type="checkbox" wire:model.live="pay_now" class="peer sr-only">
......@@ -915,40 +865,30 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-6 py
</div>
@if($pay_now)
{{-- Payment Method --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-3">{{ __('طريقة الدفع') }}</label>
<div class="grid grid-cols-3 gap-3">
<label class="relative cursor-pointer">
<input type="radio" wire:model="payment_method" value="cash" class="peer sr-only">
<div class="p-4 min-h-16 flex flex-col items-center justify-center gap-2 border border-gray-300 rounded-xl transition-all
peer-checked:border-green-500 peer-checked:bg-green-50 peer-checked:text-green-700
hover:border-gray-400">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z"/>
</svg>
<div class="p-4 min-h-[64px] flex flex-col items-center justify-center gap-2 border border-gray-300 rounded-xl transition-all
peer-checked:border-green-500 peer-checked:bg-green-50 peer-checked:text-green-700 hover:border-gray-400">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z"/></svg>
<span class="text-sm font-medium">{{ __('نقدي') }}</span>
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model="payment_method" value="card" class="peer sr-only">
<div class="p-4 min-h-16 flex flex-col items-center justify-center gap-2 border border-gray-300 rounded-xl transition-all
peer-checked:border-blue-500 peer-checked:bg-blue-50 peer-checked:text-blue-700
hover:border-gray-400">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/>
</svg>
<div class="p-4 min-h-[64px] flex flex-col items-center justify-center gap-2 border border-gray-300 rounded-xl transition-all
peer-checked:border-blue-500 peer-checked:bg-blue-50 peer-checked:text-blue-700 hover:border-gray-400">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/></svg>
<span class="text-sm font-medium">{{ __('بطاقة') }}</span>
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model="payment_method" value="wallet" class="peer sr-only">
<div class="p-4 min-h-16 flex flex-col items-center justify-center gap-2 border border-gray-300 rounded-xl transition-all
peer-checked:border-purple-500 peer-checked:bg-purple-50 peer-checked:text-purple-700
hover:border-gray-400">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M3 6h18M3 14h18M3 18h18"/>
</svg>
<div class="p-4 min-h-[64px] flex flex-col items-center justify-center gap-2 border border-gray-300 rounded-xl transition-all
peer-checked:border-purple-500 peer-checked:bg-purple-50 peer-checked:text-purple-700 hover:border-gray-400">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M3 6h18M3 14h18M3 18h18"/></svg>
<span class="text-sm font-medium">{{ __('محفظة') }}</span>
</div>
</label>
......@@ -985,10 +925,9 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py
</div>
@endif
{{-- Step 7: Success + Printable Invoice --}}
{{-- ===================== STEP 7: SUCCESS ===================== --}}
@if($currentStep === 7)
<div>
{{-- Success Banner --}}
<div class="text-center py-6 mb-6">
<div class="w-16 h-16 mx-auto bg-green-100 rounded-full flex items-center justify-center mb-4">
<svg class="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
......@@ -1002,7 +941,6 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py
@endif
</div>
{{-- Printable Invoice --}}
@if($this->invoiceForPrint)
@php
$inv = $this->invoiceForPrint;
......@@ -1013,7 +951,6 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py
$receiptFooter = $branding->get('branding.receipt_footer_text', '');
@endphp
<div id="printable-invoice" class="bg-white border border-gray-300 rounded-xl p-4 sm:p-6 max-w-2xl mx-auto print:border-0 print:shadow-none print:rounded-none print:p-0 print:max-w-full">
{{-- Invoice Header with Branding --}}
<div class="flex items-start justify-between border-b border-gray-200 pb-4 mb-4">
<div class="flex items-center gap-3">
@if($brandLogo)
......@@ -1023,31 +960,21 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py
<h3 class="text-base sm:text-lg font-bold text-gray-800">{{ $academyName }}</h3>
@if($branch)
<p class="text-xs text-gray-500">{{ $branch->name_ar }}</p>
@if($branch->address)
<p class="text-xs text-gray-400">{{ $branch->address }}</p>
@endif
@if($branch->phone)
<p class="text-xs text-gray-400" dir="ltr">{{ $branch->phone }}</p>
@endif
@endif
</div>
</div>
<div class="text-end text-sm">
<p class="font-bold text-gray-700">{{ __('فاتورة') }}</p>
<p class="text-gray-500 mt-1 font-mono" dir="ltr">#{{ $inv->number }}</p>
<p class="text-gray-500 mt-1">{{ __('التاريخ') }}: {{ $inv->issue_date?->format('Y/m/d') ?? now()->format('Y/m/d') }}</p>
<p class="text-gray-500">{{ __('الاستحقاق') }}: {{ $inv->due_date?->format('Y/m/d') ?? now()->addDays(7)->format('Y/m/d') }}</p>
<p class="text-gray-500 mt-1">{{ $inv->issue_date?->format('Y/m/d') ?? now()->format('Y/m/d') }}</p>
</div>
</div>
{{-- Customer Info --}}
<div class="grid grid-cols-2 gap-4 text-sm mb-6">
<div>
<p class="text-gray-500 text-xs mb-1">{{ __('المشترك') }}</p>
<p class="font-semibold text-gray-800">{{ $participant_name_ar }}</p>
@if($participant_number)
<p class="text-gray-500" dir="ltr">{{ $participant_number }}</p>
@endif
@if($participant_number)<p class="text-gray-500" dir="ltr">{{ $participant_number }}</p>@endif
</div>
<div class="text-end">
<p class="text-gray-500 text-xs mb-1">{{ __('ولي الأمر') }}</p>
......@@ -1056,14 +983,12 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py
</div>
</div>
{{-- Items Table --}}
<table class="w-full text-sm mb-4">
<thead>
<tr class="border-b border-gray-200">
<th class="text-start py-2 font-medium text-gray-600">{{ __('البند') }}</th>
<th class="text-center py-2 font-medium text-gray-600 w-16">{{ __('الكمية') }}</th>
<th class="text-end py-2 font-medium text-gray-600 w-28">{{ __('السعر') }}</th>
<th class="text-end py-2 font-medium text-gray-600 w-28">{{ __('المجموع') }}</th>
<th class="text-end py-2 font-medium text-gray-600 w-28">{{ __('الإجمالي') }}</th>
</tr>
</thead>
<tbody>
......@@ -1071,35 +996,23 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py
<tr class="border-b border-gray-100">
<td class="py-3">{{ $item->description }}</td>
<td class="py-3 text-center">{{ $item->quantity }}</td>
<td class="py-3 text-end" dir="ltr">{{ number_format($item->unit_price / 100, 2) }}</td>
<td class="py-3 text-end" dir="ltr">{{ number_format($item->total_amount / 100, 2) }}</td>
</tr>
@endforeach
</tbody>
</table>
{{-- Totals --}}
<div class="border-t border-gray-200 pt-3 space-y-2 text-sm max-w-xs ms-auto">
<div class="flex justify-between">
<span class="text-gray-500">{{ __('المجموع الفرعي') }}</span>
<span class="font-medium" dir="ltr">{{ number_format($inv->subtotal_amount / 100, 2) }} {{ __('ج.م') }}</span>
</div>
@if($inv->discount_amount > 0)
<div class="flex justify-between text-green-600">
<span>{{ __('الخصم') }}</span>
<span>{{ __('خصم') }}</span>
<span dir="ltr">-{{ number_format($inv->discount_amount / 100, 2) }}</span>
</div>
@endif
@if($inv->service_fee_amount > 0)
<div class="flex justify-between">
<span class="text-gray-500">{{ __('رسوم الخدمة') }}</span>
<span class="font-medium" dir="ltr">{{ number_format($inv->service_fee_amount / 100, 2) }} {{ __('ج.م') }}</span>
</div>
@endif
@if($inv->tax_amount > 0)
<div class="flex justify-between">
<span class="text-gray-500">{{ __('الضريبة') }}</span>
<span class="font-medium" dir="ltr">{{ number_format($inv->tax_amount / 100, 2) }}</span>
<span dir="ltr">{{ number_format($inv->service_fee_amount / 100, 2) }} {{ __('ج.م') }}</span>
</div>
@endif
<div class="flex justify-between pt-2 border-t border-gray-300 text-base">
......@@ -1123,13 +1036,10 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py
@endif
</div>
{{-- Payment status badge --}}
<div class="mt-4 text-center">
@if($payment_recorded)
<span class="inline-flex items-center gap-1 px-4 py-2 bg-green-100 text-green-800 rounded-full text-sm font-bold">
<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="M5 13l4 4L19 7"/>
</svg>
<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="M5 13l4 4L19 7"/></svg>
{{ __('تم الدفع') }}
</span>
@else
......@@ -1139,7 +1049,6 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py
@endif
</div>
{{-- Footer from branding settings --}}
@if($receiptFooter)
<div class="mt-4 pt-3 border-t border-gray-200 text-center text-xs text-gray-500">
{{ $receiptFooter }}
......@@ -1147,48 +1056,38 @@ class="flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py
@endif
</div>
{{-- Actions --}}
<div class="flex flex-col sm:flex-row items-stretch sm:items-center justify-center gap-3 mt-6 print:hidden">
<a href="{{ route('invoices.print', $invoice_uuid) }}" target="_blank"
class="inline-flex items-center justify-center gap-2 px-6 py-3 min-h-[48px] bg-gray-800 text-white rounded-lg hover:bg-gray-900 active:bg-gray-950 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="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/>
</svg>
class="inline-flex items-center justify-center gap-2 px-6 py-3 min-h-[48px] bg-gray-800 text-white rounded-lg hover:bg-gray-900 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="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/></svg>
{{ __('طباعة الفاتورة') }}
</a>
<a href="{{ route('receptionist.new-registration') }}" wire:navigate
class="inline-flex items-center justify-center gap-2 px-6 py-3 min-h-[48px] bg-blue-600 text-white rounded-lg hover:bg-blue-700 active:bg-blue-800 text-base font-medium transition-colors">
class="inline-flex items-center justify-center gap-2 px-6 py-3 min-h-[48px] bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-base font-medium transition-colors">
{{ __('تسجيل آخر') }}
</a>
<a href="{{ route('receptionist.dashboard') }}" wire:navigate
class="inline-flex items-center justify-center gap-2 px-6 py-3 min-h-[48px] bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 active:bg-gray-300 text-base font-medium transition-colors">
class="inline-flex items-center justify-center gap-2 px-6 py-3 min-h-[48px] bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 text-base font-medium transition-colors">
{{ __('العودة للاستقبال') }}
</a>
</div>
{{-- Navigation Links --}}
<div class="flex items-center justify-center gap-3 mt-4 print:hidden">
@if($participant_uuid)
<a href="{{ route('participants.show', $participant_uuid) }}" wire:navigate
class="inline-flex items-center gap-2 px-4 py-2.5 min-h-[44px] border border-blue-300 rounded-lg text-sm font-medium text-blue-700 hover:bg-blue-50 active:bg-blue-100 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</svg>
class="inline-flex items-center gap-2 px-4 py-2.5 min-h-[44px] border border-blue-300 rounded-lg text-sm font-medium text-blue-700 hover:bg-blue-50 transition-colors">
{{ __('عرض المشترك') }}
</a>
@endif
@if($invoice_uuid)
<a href="{{ route('invoices.show', $invoice_uuid) }}" wire:navigate
class="inline-flex items-center gap-2 px-4 py-2.5 min-h-[44px] border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 active:bg-gray-100 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
class="inline-flex items-center gap-2 px-4 py-2.5 min-h-[44px] border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors">
{{ __('عرض الفاتورة') }}
</a>
@endif
</div>
@else
{{-- No invoice (free program) --}}
<div class="flex items-center justify-center gap-4 mt-6">
<a href="{{ route('receptionist.new-registration') }}" wire:navigate
class="inline-flex items-center gap-2 px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-base font-medium transition-colors">
......@@ -1199,15 +1098,10 @@ class="inline-flex items-center gap-2 px-6 py-3 bg-gray-100 text-gray-700 rounde
{{ __('العودة للاستقبال') }}
</a>
</div>
{{-- Navigation Links --}}
@if($participant_uuid)
<div class="flex items-center justify-center gap-3 mt-4">
<a href="{{ route('participants.show', $participant_uuid) }}" wire:navigate
class="inline-flex items-center gap-2 px-4 py-2.5 min-h-[44px] border border-blue-300 rounded-lg text-sm font-medium text-blue-700 hover:bg-blue-50 active:bg-blue-100 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</svg>
class="inline-flex items-center gap-2 px-4 py-2.5 min-h-[44px] border border-blue-300 rounded-lg text-sm font-medium text-blue-700 hover:bg-blue-50 transition-colors">
{{ __('عرض المشترك') }}
</a>
</div>
......@@ -1215,6 +1109,7 @@ class="inline-flex items-center gap-2 px-4 py-2.5 min-h-[44px] border border-blu
@endif
</div>
@endif
</div>
@endif {{-- end systemErrors else block --}}
......
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