Commit 0c8f5bdb authored by Mahmoud Aglan's avatar Mahmoud Aglan

Add document management system + 10 feature wizards

Document system: polymorphic documents table, upload/approve/reject workflow,
medical certificate tracking with expiry, system settings toggles, daily expire job.

Feature wizards: Employee, Trainer, Program, Group, Facility, Invoice, Product,
PricingRule, TransferParticipant, StockAdjustment — all with Arabic UI, step-by-step
guided creation, and delegation to existing services.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent aeca9efa
<?php
namespace App\Console\Commands;
use App\Domain\Document\Services\DocumentService;
use Illuminate\Console\Command;
class ExpireDocuments extends Command
{
protected $signature = 'documents:expire';
protected $description = 'Mark approved documents past their expiry date as expired';
public function handle(DocumentService $service): int
{
$count = $service->checkAndExpireDocuments();
$this->info("Expired {$count} document(s).");
return self::SUCCESS;
}
}
<?php
namespace App\Domain\Document;
final class DocumentSettings
{
public const REQUIRE_DOCUMENTS_UPLOAD = 'require_documents_upload';
public const REQUIRE_MEDICAL_CERTIFICATE = 'require_medical_certificate';
public const MEDICAL_CERTIFICATE_EXPIRY_REQUIRED = 'medical_certificate_expiry_required';
public const MEDICAL_CERTIFICATE_MAX_AGE_MONTHS = 'medical_certificate_max_age_months';
public const BLOCK_ATTENDANCE_WITHOUT_MEDICAL = 'block_attendance_without_medical';
public const ALLOWED_DOCUMENT_TYPES = 'allowed_document_types';
public const MAX_DOCUMENT_SIZE_MB = 'max_document_size_mb';
public const GROUP = 'enrollment';
public const DEFAULTS = [
self::REQUIRE_DOCUMENTS_UPLOAD => '0',
self::REQUIRE_MEDICAL_CERTIFICATE => '0',
self::MEDICAL_CERTIFICATE_EXPIRY_REQUIRED => '1',
self::MEDICAL_CERTIFICATE_MAX_AGE_MONTHS => '12',
self::BLOCK_ATTENDANCE_WITHOUT_MEDICAL => '0',
self::ALLOWED_DOCUMENT_TYPES => '["birth_certificate","national_id","medical_certificate","passport","photo","contract","qualification","insurance","other"]',
self::MAX_DOCUMENT_SIZE_MB => '10',
];
}
<?php
namespace App\Domain\Document\Enums;
enum DocumentStatus: string
{
case Pending = 'pending';
case Approved = 'approved';
case Rejected = 'rejected';
case Expired = 'expired';
public function label(): string
{
return match ($this) {
self::Pending => 'في الانتظار',
self::Approved => 'معتمد',
self::Rejected => 'مرفوض',
self::Expired => 'منتهي الصلاحية',
};
}
public function badgeColor(): string
{
return match ($this) {
self::Pending => 'yellow',
self::Approved => 'green',
self::Rejected => 'red',
self::Expired => 'gray',
};
}
}
<?php
namespace App\Domain\Document\Enums;
enum DocumentType: string
{
case BirthCertificate = 'birth_certificate';
case NationalId = 'national_id';
case MedicalCertificate = 'medical_certificate';
case Passport = 'passport';
case Photo = 'photo';
case Contract = 'contract';
case Qualification = 'qualification';
case Insurance = 'insurance';
case Other = 'other';
public function label(): string
{
return match ($this) {
self::BirthCertificate => 'شهادة الميلاد',
self::NationalId => 'بطاقة الرقم القومي',
self::MedicalCertificate => 'الشهادة الطبية',
self::Passport => 'جواز السفر',
self::Photo => 'صورة شخصية',
self::Contract => 'عقد',
self::Qualification => 'مؤهل / شهادة',
self::Insurance => 'تأمين',
self::Other => 'أخرى',
};
}
public function requiresExpiry(): bool
{
return match ($this) {
self::MedicalCertificate, self::Insurance => true,
default => false,
};
}
public function requiresApproval(): bool
{
return match ($this) {
self::MedicalCertificate => true,
default => false,
};
}
}
<?php
namespace App\Domain\Document\Models;
use App\Domain\Document\Enums\DocumentStatus;
use App\Domain\Document\Enums\DocumentType;
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class Document extends Model
{
use BelongsToAcademy, HasUuid, SoftDeletes;
protected $fillable = [
'academy_id',
'documentable_type',
'documentable_id',
'document_type',
'file_path',
'original_filename',
'mime_type',
'file_size',
'status',
'expires_at',
'approved_by',
'approved_at',
'rejection_reason',
'notes',
'uploaded_by',
'created_by',
];
protected function casts(): array
{
return [
'document_type' => DocumentType::class,
'status' => DocumentStatus::class,
'file_size' => 'integer',
'expires_at' => 'date',
'approved_at' => 'datetime',
];
}
public function documentable(): MorphTo
{
return $this->morphTo();
}
public function approver(): BelongsTo
{
return $this->belongsTo(User::class, 'approved_by');
}
public function uploader(): BelongsTo
{
return $this->belongsTo(User::class, 'uploaded_by');
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
public function scopePending(Builder $query): Builder
{
return $query->where('status', DocumentStatus::Pending);
}
public function scopeApproved(Builder $query): Builder
{
return $query->where('status', DocumentStatus::Approved);
}
public function scopeExpired(Builder $query): Builder
{
return $query->where('status', DocumentStatus::Expired);
}
public function scopeMedicalCertificates(Builder $query): Builder
{
return $query->where('document_type', DocumentType::MedicalCertificate);
}
public function scopeOfType(Builder $query, DocumentType $type): Builder
{
return $query->where('document_type', $type);
}
public function isExpired(): bool
{
return $this->expires_at && $this->expires_at->isPast();
}
public function isPending(): bool
{
return $this->status === DocumentStatus::Pending;
}
public function isApproved(): bool
{
return $this->status === DocumentStatus::Approved;
}
}
<?php
namespace App\Domain\Document\Services;
use App\Domain\Document\DocumentSettings;
use App\Domain\Document\Enums\DocumentStatus;
use App\Domain\Document\Enums\DocumentType;
use App\Domain\Document\Models\Document;
use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Models\SystemSetting;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class DocumentService
{
public function upload(
Model $documentable,
UploadedFile $file,
DocumentType $type,
?string $notes,
User $actor
): Document {
return DB::transaction(function () use ($documentable, $file, $type, $notes, $actor) {
$uuid = (string) Str::uuid();
$extension = $file->getClientOriginalExtension();
$path = $this->buildStoragePath($documentable, $uuid, $extension);
Storage::disk('local')->put($path, file_get_contents($file->getRealPath()));
$status = $type->requiresApproval()
? DocumentStatus::Pending
: DocumentStatus::Approved;
return Document::create([
'academy_id' => app('current_academy')->id,
'documentable_type' => get_class($documentable),
'documentable_id' => $documentable->id,
'document_type' => $type->value,
'file_path' => $path,
'original_filename' => $file->getClientOriginalName(),
'mime_type' => $file->getMimeType(),
'file_size' => $file->getSize(),
'status' => $status->value,
'notes' => $notes,
'uploaded_by' => $actor->id,
'created_by' => $actor->id,
]);
});
}
public function approve(Document $document, ?string $expiresAt, User $approver): Document
{
return DB::transaction(function () use ($document, $expiresAt, $approver) {
$document->update([
'status' => DocumentStatus::Approved->value,
'expires_at' => $expiresAt,
'approved_by' => $approver->id,
'approved_at' => now(),
'rejection_reason' => null,
]);
return $document->fresh();
});
}
public function reject(Document $document, string $reason, User $actor): Document
{
return DB::transaction(function () use ($document, $reason, $actor) {
$document->update([
'status' => DocumentStatus::Rejected->value,
'rejection_reason' => $reason,
'approved_by' => $actor->id,
'approved_at' => now(),
]);
return $document->fresh();
});
}
public function markExpired(Document $document): Document
{
$document->update(['status' => DocumentStatus::Expired->value]);
return $document->fresh();
}
public function checkAndExpireDocuments(): int
{
return Document::where('status', DocumentStatus::Approved)
->whereNotNull('expires_at')
->where('expires_at', '<', now()->toDateString())
->update(['status' => DocumentStatus::Expired->value]);
}
public function participantHasValidMedical(Participant $participant): bool
{
return Document::where('documentable_type', Participant::class)
->where('documentable_id', $participant->id)
->where('document_type', DocumentType::MedicalCertificate)
->where('status', DocumentStatus::Approved)
->where(function ($q) {
$q->whereNull('expires_at')
->orWhere('expires_at', '>=', now()->toDateString());
})
->exists();
}
public function getMissingRequiredDocuments(Participant $participant): array
{
$missing = [];
if (SystemSetting::get(DocumentSettings::REQUIRE_MEDICAL_CERTIFICATE, false)) {
if (!$this->participantHasValidMedical($participant)) {
$missing[] = DocumentType::MedicalCertificate;
}
}
return $missing;
}
public function getMaxFileSizeBytes(): int
{
$mb = (int) SystemSetting::get(DocumentSettings::MAX_DOCUMENT_SIZE_MB, 10);
return $mb * 1024 * 1024;
}
public function getAllowedTypes(): array
{
$json = SystemSetting::get(DocumentSettings::ALLOWED_DOCUMENT_TYPES, DocumentSettings::DEFAULTS[DocumentSettings::ALLOWED_DOCUMENT_TYPES]);
if (is_string($json)) {
$json = json_decode($json, true) ?? [];
}
return array_filter(
DocumentType::cases(),
fn (DocumentType $type) => in_array($type->value, $json)
);
}
private function buildStoragePath(Model $documentable, string $uuid, string $extension): string
{
$academyId = app('current_academy')->id;
$type = class_basename($documentable);
$entityId = $documentable->id;
return "documents/{$academyId}/{$type}/{$entityId}/{$uuid}.{$extension}";
}
}
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
class Employee extends Model class Employee extends Model
...@@ -94,6 +95,11 @@ public function updater(): BelongsTo ...@@ -94,6 +95,11 @@ public function updater(): BelongsTo
return $this->belongsTo(\App\Models\User::class, 'updated_by'); return $this->belongsTo(\App\Models\User::class, 'updated_by');
} }
public function documents(): MorphMany
{
return $this->morphMany(\App\Domain\Document\Models\Document::class, 'documentable');
}
// --- Scopes --- // --- Scopes ---
public function scopeActive(Builder $query): Builder public function scopeActive(Builder $query): Builder
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
class Trainer extends Model class Trainer extends Model
{ {
...@@ -81,6 +82,11 @@ public function assignments() ...@@ -81,6 +82,11 @@ public function assignments()
); );
} }
public function documents(): MorphMany
{
return $this->morphMany(\App\Domain\Document\Models\Document::class, 'documentable');
}
// --- Scopes --- // --- Scopes ---
public function scopeActive(Builder $query): Builder public function scopeActive(Builder $query): Builder
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
class Person extends Model class Person extends Model
{ {
...@@ -51,6 +52,11 @@ public function participant(): HasOne ...@@ -51,6 +52,11 @@ public function participant(): HasOne
return $this->hasOne(\App\Domain\Participant\Models\Participant::class); return $this->hasOne(\App\Domain\Participant\Models\Participant::class);
} }
public function documents(): MorphMany
{
return $this->morphMany(\App\Domain\Document\Models\Document::class, 'documentable');
}
public function getAgeAttribute(): ?int public function getAgeAttribute(): ?int
{ {
return $this->date_of_birth?->age; return $this->date_of_birth?->age;
......
...@@ -157,6 +157,11 @@ public function attendanceRecords(): MorphMany ...@@ -157,6 +157,11 @@ public function attendanceRecords(): MorphMany
return $this->morphMany(\App\Domain\Attendance\Models\AttendanceRecord::class, 'subject'); return $this->morphMany(\App\Domain\Attendance\Models\AttendanceRecord::class, 'subject');
} }
public function documents(): MorphMany
{
return $this->morphMany(\App\Domain\Document\Models\Document::class, 'documentable');
}
public function posTransactions(): HasMany public function posTransactions(): HasMany
{ {
return $this->hasMany(\App\Domain\POS\Models\POSTransaction::class); return $this->hasMany(\App\Domain\POS\Models\POSTransaction::class);
......
<?php
namespace App\Http\Controllers;
use App\Domain\Document\Models\Document;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;
class DocumentController extends Controller
{
public function show(Document $document): StreamedResponse
{
$this->authorize('documents.view');
$this->ensureSameAcademy($document);
return $this->streamFile($document, 'inline');
}
public function download(Document $document): StreamedResponse
{
$this->authorize('documents.view');
$this->ensureSameAcademy($document);
return $this->streamFile($document, 'attachment');
}
public function preview(Document $document): StreamedResponse
{
$this->authorize('documents.view');
$this->ensureSameAcademy($document);
return $this->streamFile($document, 'inline');
}
private function streamFile(Document $document, string $disposition): StreamedResponse
{
$disk = Storage::disk('local');
abort_unless($disk->exists($document->file_path), 404);
return $disk->download(
$document->file_path,
$document->original_filename,
[
'Content-Type' => $document->mime_type,
'Content-Disposition' => "{$disposition}; filename=\"{$document->original_filename}\"",
]
);
}
private function ensureSameAcademy(Document $document): void
{
abort_unless(
$document->academy_id === app('current_academy')?->id,
403
);
}
}
...@@ -85,6 +85,12 @@ class SystemSettings extends Component ...@@ -85,6 +85,12 @@ class SystemSettings extends Component
'freeze_max_days' => ['type' => 'number', 'label' => 'أقصى مدة تجميد (أيام)', 'step' => '1', 'min' => 1], 'freeze_max_days' => ['type' => 'number', 'label' => 'أقصى مدة تجميد (أيام)', 'step' => '1', 'min' => 1],
'freeze_max_times' => ['type' => 'number', 'label' => 'أقصى عدد مرات التجميد', 'step' => '1', 'min' => 1], 'freeze_max_times' => ['type' => 'number', 'label' => 'أقصى عدد مرات التجميد', 'step' => '1', 'min' => 1],
'enrollment_expiry_days' => ['type' => 'number', 'label' => 'مدة صلاحية الاشتراك (أيام)', 'step' => '1', 'min' => 1], 'enrollment_expiry_days' => ['type' => 'number', 'label' => 'مدة صلاحية الاشتراك (أيام)', 'step' => '1', 'min' => 1],
'require_documents_upload' => ['type' => 'boolean', 'label' => 'تفعيل نظام رفع المستندات'],
'require_medical_certificate' => ['type' => 'boolean', 'label' => 'إلزام رفع شهادة طبية سارية'],
'medical_certificate_expiry_required' => ['type' => 'boolean', 'label' => 'إلزام تاريخ انتهاء الشهادة عند الموافقة'],
'medical_certificate_max_age_months' => ['type' => 'number', 'label' => 'أقصى مدة صلاحية الشهادة الطبية (شهور)', 'step' => '1', 'min' => 1, 'max' => 60],
'block_attendance_without_medical' => ['type' => 'boolean', 'label' => 'منع الحضور بدون شهادة طبية سارية'],
'max_document_size_mb' => ['type' => 'number', 'label' => 'الحد الأقصى لحجم الملف (ميجابايت)', 'step' => '1', 'min' => 1, 'max' => 50],
], ],
]; ];
...@@ -152,6 +158,13 @@ class SystemSettings extends Component ...@@ -152,6 +158,13 @@ class SystemSettings extends Component
'freeze_max_days' => '30', 'freeze_max_days' => '30',
'freeze_max_times' => '2', 'freeze_max_times' => '2',
'enrollment_expiry_days' => '30', 'enrollment_expiry_days' => '30',
// documents
'require_documents_upload' => '0',
'require_medical_certificate' => '0',
'medical_certificate_expiry_required' => '1',
'medical_certificate_max_age_months' => '12',
'block_attendance_without_medical' => '0',
'max_document_size_mb' => '10',
]; ];
public function mount(): void public function mount(): void
......
<?php
namespace App\Livewire\Documents;
use App\Domain\Document\DocumentSettings;
use App\Domain\Document\Enums\DocumentStatus;
use App\Domain\Document\Models\Document;
use App\Domain\Document\Services\DocumentService;
use App\Domain\Shared\Models\SystemSetting;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('components.layouts.app')]
#[Title('مراجعة مستند')]
class DocumentApproval extends Component
{
public Document $document;
public string $expiresAt = '';
public string $rejectionReason = '';
public function mount(Document $document): void
{
$this->authorize('documents.approve');
$this->document = $document;
}
public function approve(DocumentService $service): void
{
$expiryRequired = $this->document->document_type->requiresExpiry()
&& SystemSetting::get(DocumentSettings::MEDICAL_CERTIFICATE_EXPIRY_REQUIRED, true);
$rules = [];
if ($expiryRequired) {
$rules['expiresAt'] = 'required|date|after:today';
} else {
$rules['expiresAt'] = 'nullable|date|after:today';
}
$this->validate($rules);
$service->approve(
$this->document,
$this->expiresAt ?: null,
auth()->user()
);
session()->flash('success', __('تم اعتماد المستند بنجاح'));
$this->redirectRoute('documents.approvals', navigate: true);
}
public function reject(DocumentService $service): void
{
$this->validate([
'rejectionReason' => 'required|string|min:5|max:500',
]);
$service->reject(
$this->document,
$this->rejectionReason,
auth()->user()
);
session()->flash('success', __('تم رفض المستند'));
$this->redirectRoute('documents.approvals', navigate: true);
}
public function render()
{
return view('livewire.documents.document-approval', [
'expiryRequired' => $this->document->document_type->requiresExpiry()
&& SystemSetting::get(DocumentSettings::MEDICAL_CERTIFICATE_EXPIRY_REQUIRED, true),
]);
}
}
<?php
namespace App\Livewire\Documents;
use App\Domain\Document\Enums\DocumentStatus;
use App\Domain\Document\Enums\DocumentType;
use App\Domain\Document\Models\Document;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
#[Layout('components.layouts.app')]
#[Title('اعتماد المستندات')]
class DocumentApprovalList extends Component
{
use WithPagination;
#[Url]
public string $search = '';
#[Url]
public string $status = 'pending';
#[Url]
public string $documentType = '';
public function updatedSearch(): void
{
$this->resetPage();
}
public function updatedStatus(): void
{
$this->resetPage();
}
public function updatedDocumentType(): void
{
$this->resetPage();
}
public function render()
{
$this->authorize('documents.approve');
$query = Document::with(['documentable', 'uploader'])
->latest();
if ($this->status) {
$query->where('status', $this->status);
}
if ($this->documentType) {
$query->where('document_type', $this->documentType);
}
if ($this->search) {
$search = $this->search;
$query->where(function ($q) use ($search) {
$q->where('original_filename', 'ilike', "%{$search}%")
->orWhereHasMorph('documentable', '*', function ($mq) use ($search) {
$mq->where('name_ar', 'ilike', "%{$search}%")
->orWhere('name', 'ilike', "%{$search}%");
});
});
}
return view('livewire.documents.document-approval-list', [
'documents' => $query->paginate(20),
'statuses' => DocumentStatus::cases(),
'documentTypes' => DocumentType::cases(),
]);
}
}
<?php
namespace App\Livewire\Documents;
use App\Domain\Document\DocumentSettings;
use App\Domain\Document\Enums\DocumentType;
use App\Domain\Document\Services\DocumentService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Models\SystemSetting;
use Illuminate\Database\Eloquent\Model;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
use Livewire\WithFileUploads;
#[Layout('components.layouts.app')]
#[Title('رفع مستند')]
class DocumentUploadWizard extends Component
{
use WithFileUploads;
public int $currentStep = 1;
public int $totalSteps = 4;
public bool $completed = false;
public string $documentableType = '';
public int $documentableId = 0;
public string $documentableLabel = '';
public string $documentType = '';
public $file = null;
public string $notes = '';
public ?array $createdDocument = null;
public function mount(string $documentableType, int $documentableId): void
{
$this->authorize('documents.upload');
$this->documentableType = $documentableType;
$this->documentableId = $documentableId;
$model = $this->resolveDocumentable();
abort_unless($model, 404);
$this->documentableLabel = $model->name_ar ?? $model->name ?? "#{$model->id}";
}
public function nextStep(): void
{
$this->validate($this->rulesForStep($this->currentStep));
$this->currentStep = min($this->currentStep + 1, $this->totalSteps);
}
public function previousStep(): void
{
$this->currentStep = max($this->currentStep - 1, 1);
}
public function goToStep(int $step): void
{
if ($step < $this->currentStep) {
$this->currentStep = $step;
}
}
public function confirm(DocumentService $service): void
{
try {
$model = $this->resolveDocumentable();
abort_unless($model, 404);
$type = DocumentType::from($this->documentType);
$document = $service->upload(
$model,
$this->file,
$type,
$this->notes ?: null,
auth()->user()
);
$this->createdDocument = [
'uuid' => $document->uuid,
'type_label' => $type->label(),
'status_label' => $document->status->label(),
'original_filename' => $document->original_filename,
];
$this->completed = true;
$this->currentStep = $this->totalSteps;
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
public function render()
{
return view('livewire.documents.document-upload-wizard', [
'allowedTypes' => app(DocumentService::class)->getAllowedTypes(),
'maxSizeMb' => (int) SystemSetting::get(DocumentSettings::MAX_DOCUMENT_SIZE_MB, 10),
'stepLabels' => $this->getStepLabels(),
]);
}
private function rulesForStep(int $step): array
{
$maxSize = (int) SystemSetting::get(DocumentSettings::MAX_DOCUMENT_SIZE_MB, 10);
return match ($step) {
1 => [
'documentType' => 'required|in:' . implode(',', array_column(DocumentType::cases(), 'value')),
],
2 => [
'file' => "required|file|max:" . ($maxSize * 1024),
],
3 => [
'notes' => 'nullable|string|max:1000',
],
default => [],
};
}
private function getStepLabels(): array
{
return [
1 => 'نوع المستند',
2 => 'رفع الملف',
3 => 'ملاحظات',
4 => 'تأكيد',
];
}
private function resolveDocumentable(): ?Model
{
$map = [
'participant' => \App\Domain\Participant\Models\Participant::class,
'person' => \App\Domain\Identity\Models\Person::class,
'employee' => \App\Domain\HR\Models\Employee::class,
'trainer' => \App\Domain\HR\Models\Trainer::class,
];
$class = $map[$this->documentableType] ?? null;
if (!$class) {
return null;
}
return $class::find($this->documentableId);
}
}
<?php
namespace App\Livewire\Documents;
use App\Domain\Document\DocumentSettings;
use App\Domain\Document\Services\DocumentService;
use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Models\SystemSetting;
use Livewire\Component;
class MedicalCertificateAlert extends Component
{
public Participant $participant;
public bool $show = false;
public bool $isBlocking = false;
public bool $hasValid = false;
public function mount(Participant $participant): void
{
$this->participant = $participant;
$requireMedical = SystemSetting::get(DocumentSettings::REQUIRE_MEDICAL_CERTIFICATE, false);
if (!$requireMedical) {
return;
}
$service = app(DocumentService::class);
$this->hasValid = $service->participantHasValidMedical($participant);
$this->isBlocking = (bool) SystemSetting::get(DocumentSettings::BLOCK_ATTENDANCE_WITHOUT_MEDICAL, false);
$this->show = !$this->hasValid;
}
public function render()
{
return view('livewire.documents.medical-certificate-alert');
}
}
<?php
namespace App\Livewire\Documents;
use App\Domain\Document\Enums\DocumentType;
use App\Domain\Document\Models\Document;
use App\Domain\Document\Services\DocumentService;
use App\Domain\Participant\Models\Participant;
use Livewire\Component;
class ParticipantDocuments extends Component
{
public Participant $participant;
public function mount(Participant $participant): void
{
$this->participant = $participant;
}
public function delete(string $uuid, DocumentService $service): void
{
$this->authorize('documents.delete');
$document = Document::where('uuid', $uuid)->firstOrFail();
$document->delete();
session()->flash('success', __('تم حذف المستند'));
}
public function render()
{
$documents = Document::where('documentable_type', Participant::class)
->where('documentable_id', $this->participant->id)
->with('uploader')
->latest()
->get();
return view('livewire.documents.participant-documents', [
'documents' => $documents,
'documentTypes' => DocumentType::cases(),
'hasValidMedical' => app(DocumentService::class)->participantHasValidMedical($this->participant),
]);
}
}
<?php
namespace App\Livewire\Enrollments;
use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Training\Models\Enrollment;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Services\EnrollmentService;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('نقل مشترك')]
class TransferParticipantWizard extends Component
{
public int $currentStep = 1;
public int $totalSteps = 4;
public bool $completed = false;
// Step 1: Select Participant & Enrollment
public string $participantSearch = '';
public ?int $participantId = null;
public ?int $enrollmentId = null;
// Step 2: Destination
public ?int $destinationGroupId = null;
// Step 3: Impact & Reason
public string $reason = '';
// Cached data for display
public ?array $selectedParticipant = null;
public array $activeEnrollments = [];
public ?array $selectedEnrollment = null;
public ?array $sourceGroup = null;
public ?array $destinationGroup = null;
public function mount(): void
{
$this->authorize('enrollments.transfer');
}
public function getStepLabels(): array
{
return [
1 => 'اختيار المشترك',
2 => 'الوجهة',
3 => 'التأثير',
4 => 'تأكيد النقل',
];
}
public function selectParticipant(int $id): void
{
$participant = Participant::with('person')->findOrFail($id);
$this->participantId = $id;
$this->selectedParticipant = [
'id' => $participant->id,
'name' => $participant->person?->name_ar ?? $participant->person?->name ?? '-',
'participant_number' => $participant->participant_number,
];
$this->loadActiveEnrollments();
}
public function updatedParticipantId(): void
{
if ($this->participantId) {
$this->loadActiveEnrollments();
} else {
$this->activeEnrollments = [];
}
$this->enrollmentId = null;
$this->selectedEnrollment = null;
$this->sourceGroup = null;
}
private function loadActiveEnrollments(): void
{
$enrollments = Enrollment::where('participant_id', $this->participantId)
->where('status', 'active')
->with(['group', 'program'])
->get();
$this->activeEnrollments = $enrollments->map(fn ($e) => [
'id' => $e->id,
'group_name' => $e->group?->name_ar ?? '-',
'program_name' => $e->program?->name_ar ?? '-',
'enrollment_date' => $e->enrollment_date?->format('Y-m-d'),
'group_id' => $e->training_group_id,
'program_id' => $e->training_program_id,
])->toArray();
}
public function selectEnrollment(int $id): void
{
$enrollment = Enrollment::with(['group', 'program'])->findOrFail($id);
$this->enrollmentId = $id;
$this->selectedEnrollment = [
'id' => $enrollment->id,
'group_name' => $enrollment->group?->name_ar ?? '-',
'program_name' => $enrollment->program?->name_ar ?? '-',
'group_id' => $enrollment->training_group_id,
'program_id' => $enrollment->training_program_id,
];
$this->sourceGroup = [
'id' => $enrollment->group?->id,
'name_ar' => $enrollment->group?->name_ar,
'current_count' => $enrollment->group?->current_count,
'max_capacity' => $enrollment->group?->max_capacity,
];
}
public function selectDestinationGroup(int $id): void
{
$group = TrainingGroup::findOrFail($id);
$this->destinationGroupId = $id;
$this->destinationGroup = [
'id' => $group->id,
'name_ar' => $group->name_ar,
'current_count' => $group->current_count,
'max_capacity' => $group->max_capacity,
];
}
public function nextStep(): void
{
$this->validate($this->rulesForStep($this->currentStep));
if ($this->currentStep < $this->totalSteps) {
$this->currentStep++;
}
}
public function previousStep(): void
{
if ($this->currentStep > 1) {
$this->currentStep--;
}
}
public function goToStep(int $step): void
{
if ($step < $this->currentStep) {
$this->currentStep = $step;
}
}
public function confirm(): void
{
try {
$enrollment = Enrollment::findOrFail($this->enrollmentId);
$toGroup = TrainingGroup::findOrFail($this->destinationGroupId);
app(EnrollmentService::class)->transfer($enrollment, $toGroup, auth()->user());
$this->completed = true;
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
private function rulesForStep(int $step): array
{
return match ($step) {
1 => [
'participantId' => 'required|integer|exists:participants,id',
'enrollmentId' => 'required|integer|exists:enrollments,id',
],
2 => [
'destinationGroupId' => 'required|integer|exists:training_groups,id',
],
3 => [
'reason' => 'required|string|min:5|max:500',
],
default => [],
};
}
public function render()
{
$searchResults = collect();
if (strlen($this->participantSearch) >= 2) {
$searchResults = Participant::with('person')
->whereHas('person', function ($q) {
$q->where('name_ar', 'ilike', "%{$this->participantSearch}%")
->orWhere('name', 'ilike', "%{$this->participantSearch}%")
->orWhere('phone', 'ilike', "%{$this->participantSearch}%")
->orWhere('national_id', 'ilike', "%{$this->participantSearch}%");
})
->orWhere('participant_number', 'ilike', "%{$this->participantSearch}%")
->where('status', 'active')
->limit(10)
->get();
}
$availableGroups = collect();
if ($this->selectedEnrollment && $this->currentStep >= 2) {
$availableGroups = TrainingGroup::where('training_program_id', $this->selectedEnrollment['program_id'])
->where('id', '!=', $this->selectedEnrollment['group_id'])
->whereIn('status', ['forming', 'active'])
->orderBy('name_ar')
->get();
}
return view('livewire.enrollments.transfer-participant-wizard', [
'searchResults' => $searchResults,
'availableGroups' => $availableGroups,
]);
}
}
<?php
namespace App\Livewire\Facilities;
use App\Domain\Facility\Services\FacilityService;
use App\Domain\Identity\Models\Branch;
use App\Domain\Shared\Exceptions\DomainException;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('إنشاء منشأة جديدة')]
class CreateFacilityWizard extends Component
{
public int $currentStep = 1;
public int $totalSteps = 4;
public bool $completed = false;
// Step 1: Basic Info
public string $nameAr = '';
public string $name = '';
public string $type = '';
public ?int $branchId = null;
public ?int $capacity = null;
public string $descriptionAr = '';
// Step 2: Specs
public string $surfaceType = '';
public ?string $areaSqm = null;
public ?string $lengthM = null;
public ?string $widthM = null;
public bool $isIndoor = false;
public bool $hasLighting = false;
public bool $hasAc = false;
// Step 3: Operating Hours
public string $operatingStart = '';
public string $operatingEnd = '';
public string $rentalCostPerHour = '';
public string $address = '';
// Data
public array $branches = [];
public function mount(): void
{
$this->authorize('facilities.create');
$this->branches = Branch::orderBy('name_ar')->get(['id', 'name_ar'])->toArray();
}
public function getStepLabels(): array
{
return [
1 => 'المعلومات الأساسية',
2 => 'المواصفات',
3 => 'ساعات التشغيل',
4 => 'مراجعة',
];
}
public function rulesForStep(): array
{
return match ($this->currentStep) {
1 => [
'nameAr' => 'required|string|max:255',
'name' => 'nullable|string|max:255',
'type' => 'required|in:field,court,pool,gym,track,hall,room,outdoor,other',
'branchId' => 'required|exists:branches,id',
'capacity' => 'nullable|integer|min:1',
'descriptionAr' => 'nullable|string|max:1000',
],
2 => [
'surfaceType' => 'nullable|string|max:100',
'areaSqm' => 'nullable|numeric|min:0',
'lengthM' => 'nullable|numeric|min:0',
'widthM' => 'nullable|numeric|min:0',
'isIndoor' => 'boolean',
'hasLighting' => 'boolean',
'hasAc' => 'boolean',
],
3 => [
'operatingStart' => 'nullable|date_format:H:i',
'operatingEnd' => 'nullable|date_format:H:i|after:operatingStart',
'rentalCostPerHour' => 'nullable|numeric|min:0',
'address' => 'nullable|string|max:500',
],
default => [],
};
}
public function messages(): array
{
return [
'nameAr.required' => 'اسم المنشأة بالعربية مطلوب',
'type.required' => 'نوع المنشأة مطلوب',
'type.in' => 'نوع المنشأة غير صالح',
'branchId.required' => 'الفرع مطلوب',
'branchId.exists' => 'الفرع المختار غير موجود',
'capacity.integer' => 'السعة يجب أن تكون رقمًا صحيحًا',
'capacity.min' => 'السعة يجب أن تكون 1 على الأقل',
'areaSqm.numeric' => 'المساحة يجب أن تكون رقمًا',
'lengthM.numeric' => 'الطول يجب أن يكون رقمًا',
'widthM.numeric' => 'العرض يجب أن يكون رقمًا',
'operatingEnd.after' => 'وقت الإغلاق يجب أن يكون بعد وقت الافتتاح',
'rentalCostPerHour.numeric' => 'تكلفة الإيجار يجب أن تكون رقمًا',
];
}
public function nextStep(): void
{
$this->validate($this->rulesForStep(), $this->messages());
$this->currentStep = min($this->currentStep + 1, $this->totalSteps);
}
public function previousStep(): void
{
$this->currentStep = max($this->currentStep - 1, 1);
}
public function goToStep(int $step): void
{
if ($step < $this->currentStep) {
$this->currentStep = $step;
}
}
public function confirm(): void
{
try {
$data = [
'name_ar' => $this->nameAr,
'name' => $this->name ?: null,
'type' => $this->type,
'branch_id' => $this->branchId,
'capacity' => $this->capacity,
'description_ar' => $this->descriptionAr ?: null,
'surface_type' => $this->surfaceType ?: null,
'area_sqm' => $this->areaSqm ? (float) $this->areaSqm : null,
'length_m' => $this->lengthM ? (float) $this->lengthM : null,
'width_m' => $this->widthM ? (float) $this->widthM : null,
'is_indoor' => $this->isIndoor,
'has_lighting' => $this->hasLighting,
'has_ac' => $this->hasAc,
'operating_start' => $this->operatingStart ?: null,
'operating_end' => $this->operatingEnd ?: null,
'rental_cost_per_hour' => $this->rentalCostPerHour ? (int) round((float) $this->rentalCostPerHour * 100) : null,
'address' => $this->address ?: null,
'status' => 'active',
];
app(FacilityService::class)->create($data, auth()->user());
$this->completed = true;
session()->flash('success', 'تم إنشاء المنشأة بنجاح');
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
public function render()
{
return view('livewire.facilities.create-facility-wizard');
}
}
<?php
namespace App\Livewire\Groups;
use App\Domain\HR\Models\Trainer;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Training\Models\Activity;
use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Services\TrainingGroupService;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('إنشاء مجموعة تدريبية')]
class CreateGroupWizard extends Component
{
public int $currentStep = 1;
public int $totalSteps = 4;
public bool $completed = false;
// Step 1: Program Selection
public ?int $programId = null;
public ?int $activityFilter = null;
// Step 2: Group Info
public string $nameAr = '';
public string $name = '';
public string $code = '';
public string $season = '';
public string $startDate = '';
public string $endDate = '';
public string $notes = '';
// Step 3: Capacity & Trainer
public string $maxCapacity = '';
public ?int $headTrainerId = null;
public function mount(): void
{
$this->authorize('groups.create');
$this->startDate = now()->toDateString();
}
public function getStepLabels(): array
{
return [
1 => 'البرنامج',
2 => 'بيانات المجموعة',
3 => 'السعة والمدرب',
4 => 'مراجعة',
];
}
public function updatedActivityFilter(): void
{
$this->programId = null;
}
public function rulesForStep(int $step): array
{
return match ($step) {
1 => [
'programId' => 'required|exists:training_programs,id',
],
2 => [
'nameAr' => 'required|string|max:255',
'code' => 'required|string|max:30',
'startDate' => 'required|date',
'endDate' => 'nullable|date|after:startDate',
],
3 => [
'maxCapacity' => 'required|integer|min:1|max:500',
],
default => [],
};
}
public function messagesForStep(int $step): array
{
return match ($step) {
1 => [
'programId.required' => 'يجب اختيار البرنامج التدريبي',
'programId.exists' => 'البرنامج المختار غير موجود',
],
2 => [
'nameAr.required' => 'اسم المجموعة بالعربي مطلوب',
'code.required' => 'كود المجموعة مطلوب',
'code.max' => 'كود المجموعة لا يتجاوز 30 حرف',
'startDate.required' => 'تاريخ البدء مطلوب',
'startDate.date' => 'تاريخ البدء غير صالح',
'endDate.after' => 'تاريخ الانتهاء يجب أن يكون بعد تاريخ البدء',
],
3 => [
'maxCapacity.required' => 'السعة القصوى مطلوبة',
'maxCapacity.integer' => 'السعة القصوى يجب أن تكون رقم صحيح',
'maxCapacity.min' => 'السعة القصوى يجب أن تكون 1 على الأقل',
],
default => [],
};
}
public function nextStep(): void
{
$this->validate(
$this->rulesForStep($this->currentStep),
$this->messagesForStep($this->currentStep)
);
if ($this->currentStep < $this->totalSteps) {
$this->currentStep++;
}
}
public function previousStep(): void
{
if ($this->currentStep > 1) {
$this->currentStep--;
}
}
public function goToStep(int $step): void
{
if ($step < $this->currentStep) {
$this->currentStep = $step;
}
}
public function confirm(): void
{
try {
DB::transaction(function () {
$program = TrainingProgram::findOrFail($this->programId);
$data = [
'academy_id' => app('current_academy')->id,
'training_program_id' => $this->programId,
'branch_id' => $program->branch_id,
'name_ar' => $this->nameAr,
'name' => $this->name ?: null,
'code' => $this->code,
'season' => $this->season ?: null,
'start_date' => $this->startDate,
'end_date' => $this->endDate ?: null,
'notes' => $this->notes ?: null,
'max_capacity' => (int) $this->maxCapacity,
'head_trainer_id' => $this->headTrainerId,
];
app(TrainingGroupService::class)->create($data, auth()->user());
});
$this->completed = true;
session()->flash('success', 'تم إنشاء المجموعة التدريبية بنجاح');
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
public function render()
{
$programs = TrainingProgram::query()
->when($this->activityFilter, fn ($q) => $q->where('activity_id', $this->activityFilter))
->whereIn('status', ['draft', 'active'])
->orderBy('name_ar')
->get(['id', 'name_ar', 'name', 'activity_id', 'max_participants']);
$trainers = Trainer::with('employee.person')
->where('status', 'active')
->get();
return view('livewire.groups.create-group-wizard', [
'stepLabels' => $this->getStepLabels(),
'activities' => Activity::orderBy('name_ar')->get(['id', 'name_ar']),
'programs' => $programs,
'trainers' => $trainers,
'selectedProgram' => $this->programId ? TrainingProgram::with('activity', 'branch')->find($this->programId) : null,
]);
}
}
<?php
namespace App\Livewire\HR;
use App\Domain\HR\Enums\EmploymentType;
use App\Domain\HR\Enums\SalaryFrequency;
use App\Domain\HR\Services\EmployeeService;
use App\Domain\Identity\Models\Branch;
use App\Domain\Identity\Models\Person;
use App\Domain\Shared\Exceptions\DomainException;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('إضافة موظف جديد')]
class CreateEmployeeWizard extends Component
{
public int $currentStep = 1;
public int $totalSteps = 5;
public bool $completed = false;
// Step 1: Person
public ?int $personId = null;
public string $personSearch = '';
public bool $createNewPerson = false;
public string $personNameAr = '';
public string $personName = '';
public string $personPhone = '';
public string $personNationalId = '';
public string $personDateOfBirth = '';
public string $personGender = '';
// Step 2: Employment
public string $department = '';
public string $position = '';
public string $employmentType = '';
public string $startDate = '';
public string $endDate = '';
// Step 3: Branch & Manager
public ?int $branchId = null;
public ?int $managerId = null;
// Step 4: Salary
public string $salaryAmount = '';
public string $salaryFrequency = '';
public string $workingHoursPerWeek = '';
public function mount(): void
{
$this->authorize('employees.create');
$this->startDate = now()->toDateString();
}
public function getStepLabels(): array
{
return [
1 => 'بيانات الشخص',
2 => 'بيانات التوظيف',
3 => 'الفرع والمسؤول',
4 => 'الراتب',
5 => 'مراجعة',
];
}
public function getPersonResultsProperty(): \Illuminate\Support\Collection
{
if (strlen($this->personSearch) < 2) {
return collect();
}
return Person::where(function ($q) {
$q->where('name_ar', 'ilike', "%{$this->personSearch}%")
->orWhere('name', 'ilike', "%{$this->personSearch}%")
->orWhere('phone', 'like', "%{$this->personSearch}%")
->orWhere('national_id', 'like', "%{$this->personSearch}%");
})->limit(10)->get(['id', 'name', 'name_ar', 'phone', 'national_id']);
}
public function selectPerson(int $id): void
{
$person = Person::findOrFail($id);
$this->personId = $person->id;
$this->personNameAr = $person->name_ar ?? '';
$this->personName = $person->name ?? '';
$this->personPhone = $person->phone ?? '';
$this->personNationalId = $person->national_id ?? '';
$this->personDateOfBirth = $person->date_of_birth?->toDateString() ?? '';
$this->personGender = $person->gender ?? '';
$this->createNewPerson = false;
$this->personSearch = '';
}
public function toggleCreateNew(): void
{
$this->createNewPerson = !$this->createNewPerson;
if ($this->createNewPerson) {
$this->personId = null;
}
}
public function rulesForStep(int $step): array
{
return match ($step) {
1 => $this->createNewPerson ? [
'personNameAr' => 'required|string|max:255',
'personPhone' => 'required|string|max:20',
'personGender' => 'required|in:male,female',
] : [
'personId' => 'required|exists:people,id',
],
2 => [
'department' => 'required|string|max:100',
'position' => 'required|string|max:100',
'employmentType' => 'required|in:' . implode(',', array_column(EmploymentType::cases(), 'value')),
'startDate' => 'required|date',
'endDate' => 'nullable|date|after:startDate',
],
3 => [
'branchId' => 'required|exists:branches,id',
],
4 => [
'salaryAmount' => 'required|numeric|min:0',
'salaryFrequency' => 'required|in:' . implode(',', array_column(SalaryFrequency::cases(), 'value')),
'workingHoursPerWeek' => 'nullable|numeric|min:1|max:168',
],
default => [],
};
}
public function messagesForStep(int $step): array
{
return match ($step) {
1 => [
'personId.required' => 'يجب اختيار شخص من القائمة',
'personId.exists' => 'الشخص المختار غير موجود',
'personNameAr.required' => 'الاسم بالعربي مطلوب',
'personPhone.required' => 'رقم الهاتف مطلوب',
'personGender.required' => 'الجنس مطلوب',
'personGender.in' => 'قيمة الجنس غير صالحة',
],
2 => [
'department.required' => 'القسم مطلوب',
'position.required' => 'المسمى الوظيفي مطلوب',
'employmentType.required' => 'نوع التوظيف مطلوب',
'employmentType.in' => 'نوع التوظيف غير صالح',
'startDate.required' => 'تاريخ البدء مطلوب',
'startDate.date' => 'تاريخ البدء غير صالح',
'endDate.after' => 'تاريخ الانتهاء يجب أن يكون بعد تاريخ البدء',
],
3 => [
'branchId.required' => 'يجب اختيار الفرع',
'branchId.exists' => 'الفرع غير موجود',
],
4 => [
'salaryAmount.required' => 'مبلغ الراتب مطلوب',
'salaryAmount.numeric' => 'مبلغ الراتب يجب أن يكون رقم',
'salaryAmount.min' => 'مبلغ الراتب لا يمكن أن يكون سالب',
'salaryFrequency.required' => 'دورية الراتب مطلوبة',
'salaryFrequency.in' => 'دورية الراتب غير صالحة',
'workingHoursPerWeek.numeric' => 'ساعات العمل يجب أن تكون رقم',
'workingHoursPerWeek.min' => 'ساعات العمل يجب أن تكون أكثر من صفر',
'workingHoursPerWeek.max' => 'ساعات العمل لا يمكن أن تتجاوز 168 ساعة',
],
default => [],
};
}
public function nextStep(): void
{
$this->validate(
$this->rulesForStep($this->currentStep),
$this->messagesForStep($this->currentStep)
);
if ($this->currentStep < $this->totalSteps) {
$this->currentStep++;
}
}
public function previousStep(): void
{
if ($this->currentStep > 1) {
$this->currentStep--;
}
}
public function goToStep(int $step): void
{
if ($step < $this->currentStep) {
$this->currentStep = $step;
}
}
public function confirm(): void
{
try {
DB::transaction(function () {
$person = $this->resolveOrCreatePerson();
$data = [
'academy_id' => app('current_academy')->id,
'person_id' => $person->id,
'department' => $this->department,
'position' => $this->position,
'employment_type' => $this->employmentType,
'start_date' => $this->startDate,
'end_date' => $this->endDate ?: null,
'branch_id' => $this->branchId,
'manager_id' => $this->managerId,
'salary_amount' => (int) round((float) $this->salaryAmount * 100),
'salary_frequency' => $this->salaryFrequency,
'working_hours_per_week' => $this->workingHoursPerWeek ?: null,
];
app(EmployeeService::class)->create($data, auth()->user());
});
$this->completed = true;
session()->flash('success', 'تم إضافة الموظف بنجاح');
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
private function resolveOrCreatePerson(): Person
{
if ($this->personId) {
return Person::findOrFail($this->personId);
}
return Person::create([
'academy_id' => app('current_academy')->id,
'name_ar' => $this->personNameAr,
'name' => $this->personName ?: null,
'phone' => $this->personPhone,
'national_id' => $this->personNationalId ?: null,
'date_of_birth' => $this->personDateOfBirth ?: null,
'gender' => $this->personGender,
'created_by' => auth()->id(),
]);
}
public function render()
{
return view('livewire.hr.create-employee-wizard', [
'stepLabels' => $this->getStepLabels(),
'branches' => Branch::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar']),
'employees' => $this->branchId
? \App\Domain\HR\Models\Employee::where('branch_id', $this->branchId)->with('person')->get()
: collect(),
'employmentTypes' => EmploymentType::cases(),
'salaryFrequencies' => SalaryFrequency::cases(),
]);
}
}
<?php
namespace App\Livewire\HR;
use App\Domain\HR\Enums\AvailabilityType;
use App\Domain\HR\Enums\CompensationModel;
use App\Domain\HR\Enums\EmploymentType;
use App\Domain\HR\Services\EmployeeService;
use App\Domain\HR\Services\TrainerService;
use App\Domain\Identity\Models\Branch;
use App\Domain\Identity\Models\Person;
use App\Domain\Shared\Exceptions\DomainException;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('إضافة مدرب جديد')]
class CreateTrainerWizard extends Component
{
public int $currentStep = 1;
public int $totalSteps = 6;
public bool $completed = false;
// Step 1: Person
public ?int $personId = null;
public string $personSearch = '';
public bool $createNewPerson = false;
public string $personNameAr = '';
public string $personName = '';
public string $personPhone = '';
public string $personNationalId = '';
public string $personDateOfBirth = '';
public string $personGender = '';
// Step 2: Employment
public string $department = '';
public string $position = '';
public string $employmentType = '';
public string $startDate = '';
public ?int $branchId = null;
// Step 3: Compensation
public string $compensationModel = '';
public string $hourlyRate = '';
public string $sessionRate = '';
public string $groupRate = '';
public string $playerRate = '';
public string $revenueSharePercent = '';
// Step 4: Availability
public array $availabilities = [];
// Step 5: Qualifications
public array $qualifications = [];
public function mount(): void
{
$this->authorize('trainers.create');
$this->startDate = now()->toDateString();
$this->position = 'مدرب';
$this->department = 'التدريب';
// Initialize 7 days availability
$days = ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'];
foreach ($days as $index => $day) {
$this->availabilities[$index] = [
'day_of_week' => $index,
'day_name' => $day,
'enabled' => false,
'start_time' => '08:00',
'end_time' => '17:00',
'type' => 'available',
];
}
// Initialize empty qualification
$this->qualifications = [
['name' => '', 'issuer' => '', 'issue_date' => '', 'expiry_date' => ''],
];
}
public function getStepLabels(): array
{
return [
1 => 'بيانات الشخص',
2 => 'بيانات التوظيف',
3 => 'التعويض',
4 => 'التوفر',
5 => 'المؤهلات',
6 => 'مراجعة',
];
}
public function getPersonResultsProperty(): \Illuminate\Support\Collection
{
if (strlen($this->personSearch) < 2) {
return collect();
}
return Person::where(function ($q) {
$q->where('name_ar', 'ilike', "%{$this->personSearch}%")
->orWhere('name', 'ilike', "%{$this->personSearch}%")
->orWhere('phone', 'like', "%{$this->personSearch}%")
->orWhere('national_id', 'like', "%{$this->personSearch}%");
})->limit(10)->get(['id', 'name', 'name_ar', 'phone', 'national_id']);
}
public function selectPerson(int $id): void
{
$person = Person::findOrFail($id);
$this->personId = $person->id;
$this->personNameAr = $person->name_ar ?? '';
$this->personName = $person->name ?? '';
$this->personPhone = $person->phone ?? '';
$this->personNationalId = $person->national_id ?? '';
$this->personDateOfBirth = $person->date_of_birth?->toDateString() ?? '';
$this->personGender = $person->gender ?? '';
$this->createNewPerson = false;
$this->personSearch = '';
}
public function toggleCreateNew(): void
{
$this->createNewPerson = !$this->createNewPerson;
if ($this->createNewPerson) {
$this->personId = null;
}
}
public function addQualification(): void
{
$this->qualifications[] = ['name' => '', 'issuer' => '', 'issue_date' => '', 'expiry_date' => ''];
}
public function removeQualification(int $index): void
{
unset($this->qualifications[$index]);
$this->qualifications = array_values($this->qualifications);
}
public function rulesForStep(int $step): array
{
return match ($step) {
1 => $this->createNewPerson ? [
'personNameAr' => 'required|string|max:255',
'personPhone' => 'required|string|max:20',
'personGender' => 'required|in:male,female',
] : [
'personId' => 'required|exists:people,id',
],
2 => [
'department' => 'required|string|max:100',
'position' => 'required|string|max:100',
'employmentType' => 'required|in:' . implode(',', array_column(EmploymentType::cases(), 'value')),
'startDate' => 'required|date',
'branchId' => 'required|exists:branches,id',
],
3 => [
'compensationModel' => 'required|in:' . implode(',', array_column(CompensationModel::cases(), 'value')),
],
4 => [],
5 => [],
default => [],
};
}
public function messagesForStep(int $step): array
{
return match ($step) {
1 => [
'personId.required' => 'يجب اختيار شخص من القائمة',
'personId.exists' => 'الشخص المختار غير موجود',
'personNameAr.required' => 'الاسم بالعربي مطلوب',
'personPhone.required' => 'رقم الهاتف مطلوب',
'personGender.required' => 'الجنس مطلوب',
'personGender.in' => 'قيمة الجنس غير صالحة',
],
2 => [
'department.required' => 'القسم مطلوب',
'position.required' => 'المسمى الوظيفي مطلوب',
'employmentType.required' => 'نوع التوظيف مطلوب',
'employmentType.in' => 'نوع التوظيف غير صالح',
'startDate.required' => 'تاريخ البدء مطلوب',
'branchId.required' => 'يجب اختيار الفرع',
'branchId.exists' => 'الفرع غير موجود',
],
3 => [
'compensationModel.required' => 'نموذج التعويض مطلوب',
'compensationModel.in' => 'نموذج التعويض غير صالح',
],
default => [],
};
}
public function nextStep(): void
{
$this->validate(
$this->rulesForStep($this->currentStep),
$this->messagesForStep($this->currentStep)
);
if ($this->currentStep < $this->totalSteps) {
$this->currentStep++;
}
}
public function previousStep(): void
{
if ($this->currentStep > 1) {
$this->currentStep--;
}
}
public function goToStep(int $step): void
{
if ($step < $this->currentStep) {
$this->currentStep = $step;
}
}
public function confirm(): void
{
try {
DB::transaction(function () {
$person = $this->resolveOrCreatePerson();
$academyId = app('current_academy')->id;
// Create Employee
$employeeData = [
'academy_id' => $academyId,
'person_id' => $person->id,
'department' => $this->department,
'position' => $this->position,
'employment_type' => $this->employmentType,
'start_date' => $this->startDate,
'branch_id' => $this->branchId,
];
$employee = app(EmployeeService::class)->create($employeeData, auth()->user());
// Create Trainer
$trainerData = [
'compensation_model' => $this->compensationModel,
'hourly_rate' => $this->hourlyRate ? (int) round((float) $this->hourlyRate * 100) : null,
'session_rate' => $this->sessionRate ? (int) round((float) $this->sessionRate * 100) : null,
'group_rate' => $this->groupRate ? (int) round((float) $this->groupRate * 100) : null,
'player_rate' => $this->playerRate ? (int) round((float) $this->playerRate * 100) : null,
'revenue_share_percent' => $this->revenueSharePercent ?: null,
];
$trainerService = app(TrainerService::class);
$trainer = $trainerService->create($employee, $trainerData, auth()->user());
// Set Availability
$slots = collect($this->availabilities)
->filter(fn ($a) => $a['enabled'])
->map(fn ($a) => [
'day_of_week' => $a['day_of_week'],
'start_time' => $a['start_time'],
'end_time' => $a['end_time'],
'type' => $a['type'],
])
->values()
->toArray();
if (!empty($slots)) {
$trainerService->setAvailability($trainer, $slots);
}
// Add Qualifications
$validQualifications = collect($this->qualifications)
->filter(fn ($q) => !empty($q['name']));
foreach ($validQualifications as $qual) {
$trainerService->addQualification($trainer, [
'name' => $qual['name'],
'issuer' => $qual['issuer'] ?: null,
'issue_date' => $qual['issue_date'] ?: null,
'expiry_date' => $qual['expiry_date'] ?: null,
]);
}
});
$this->completed = true;
session()->flash('success', 'تم إضافة المدرب بنجاح');
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
private function resolveOrCreatePerson(): Person
{
if ($this->personId) {
return Person::findOrFail($this->personId);
}
return Person::create([
'academy_id' => app('current_academy')->id,
'name_ar' => $this->personNameAr,
'name' => $this->personName ?: null,
'phone' => $this->personPhone,
'national_id' => $this->personNationalId ?: null,
'date_of_birth' => $this->personDateOfBirth ?: null,
'gender' => $this->personGender,
'created_by' => auth()->id(),
]);
}
public function render()
{
return view('livewire.hr.create-trainer-wizard', [
'stepLabels' => $this->getStepLabels(),
'branches' => Branch::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar']),
'employmentTypes' => EmploymentType::cases(),
'compensationModels' => CompensationModel::cases(),
'availabilityTypes' => AvailabilityType::cases(),
]);
}
}
<?php
namespace App\Livewire\Inventory;
use App\Domain\Inventory\Models\Product;
use App\Domain\Inventory\Models\ProductCategory;
use App\Domain\Shared\Exceptions\DomainException;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('إنشاء منتج جديد')]
class CreateProductWizard extends Component
{
public int $currentStep = 1;
public int $totalSteps = 4;
public bool $completed = false;
// Step 1: Basic Info
public string $nameAr = '';
public string $name = '';
public string $sku = '';
public string $barcode = '';
public ?int $categoryId = null;
public string $type = 'physical';
public string $descriptionAr = '';
// Step 2: Pricing
public string $sellingPrice = '';
public string $costPrice = '';
public string $taxRate = '0';
// Step 3: Inventory Settings
public bool $trackInventory = true;
public ?int $minStockLevel = null;
public ?int $maxStockLevel = null;
public bool $isActive = true;
// Data
public array $categories = [];
public function mount(): void
{
$this->authorize('products.create');
$this->categories = ProductCategory::active()
->orderBy('name_ar')
->get(['id', 'name_ar', 'name'])
->toArray();
}
public function getStepLabels(): array
{
return [
1 => 'المعلومات الأساسية',
2 => 'التسعير',
3 => 'إعدادات المخزون',
4 => 'مراجعة',
];
}
public function rulesForStep(): array
{
return match ($this->currentStep) {
1 => [
'nameAr' => 'required|string|max:255',
'name' => 'nullable|string|max:255',
'sku' => 'nullable|string|max:50',
'barcode' => 'nullable|string|max:50',
'categoryId' => 'nullable|exists:product_categories,id',
'type' => 'required|in:physical,digital,service',
'descriptionAr' => 'nullable|string|max:1000',
],
2 => [
'sellingPrice' => 'required|numeric|min:0.01',
'costPrice' => 'nullable|numeric|min:0',
'taxRate' => 'nullable|numeric|min:0|max:100',
],
3 => [
'trackInventory' => 'boolean',
'minStockLevel' => 'nullable|integer|min:0',
'maxStockLevel' => 'nullable|integer|min:0',
'isActive' => 'boolean',
],
default => [],
};
}
public function messages(): array
{
return [
'nameAr.required' => 'اسم المنتج بالعربية مطلوب',
'type.required' => 'نوع المنتج مطلوب',
'type.in' => 'نوع المنتج غير صالح',
'categoryId.exists' => 'التصنيف المختار غير موجود',
'sellingPrice.required' => 'سعر البيع مطلوب',
'sellingPrice.min' => 'سعر البيع يجب أن يكون أكبر من صفر',
'costPrice.numeric' => 'سعر التكلفة يجب أن يكون رقمًا',
'taxRate.numeric' => 'نسبة الضريبة يجب أن تكون رقمًا',
'taxRate.max' => 'نسبة الضريبة لا يمكن أن تتجاوز 100%',
'minStockLevel.integer' => 'الحد الأدنى يجب أن يكون رقمًا صحيحًا',
'maxStockLevel.integer' => 'الحد الأقصى يجب أن يكون رقمًا صحيحًا',
];
}
public function nextStep(): void
{
$this->validate($this->rulesForStep(), $this->messages());
$this->currentStep = min($this->currentStep + 1, $this->totalSteps);
}
public function previousStep(): void
{
$this->currentStep = max($this->currentStep - 1, 1);
}
public function goToStep(int $step): void
{
if ($step < $this->currentStep) {
$this->currentStep = $step;
}
}
public function confirm(): void
{
try {
Product::create([
'name_ar' => $this->nameAr,
'name' => $this->name ?: null,
'sku' => $this->sku ?: null,
'barcode' => $this->barcode ?: null,
'category_id' => $this->categoryId,
'type' => $this->type,
'description_ar' => $this->descriptionAr ?: null,
'selling_price' => (int) round((float) $this->sellingPrice * 100),
'cost_price' => $this->costPrice ? (int) round((float) $this->costPrice * 100) : null,
'tax_rate' => $this->taxRate ? (int) $this->taxRate : 0,
'track_inventory' => $this->trackInventory,
'min_stock_level' => $this->minStockLevel,
'max_stock_level' => $this->maxStockLevel,
'is_active' => $this->isActive,
'created_by' => auth()->id(),
]);
$this->completed = true;
session()->flash('success', 'تم إنشاء المنتج بنجاح');
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
public function render()
{
return view('livewire.inventory.create-product-wizard');
}
}
<?php
namespace App\Livewire\Inventory;
use App\Domain\Inventory\Enums\MovementType;
use App\Domain\Inventory\Models\InventoryLevel;
use App\Domain\Inventory\Models\Product;
use App\Domain\Inventory\Models\Warehouse;
use App\Domain\Inventory\Services\InventoryService;
use App\Domain\Shared\Exceptions\DomainException;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('تسوية المخزون')]
class StockAdjustmentWizard extends Component
{
public int $currentStep = 1;
public int $totalSteps = 5;
public bool $completed = false;
// Step 1: Warehouse
public ?int $warehouseId = null;
// Step 2: Product
public ?int $productId = null;
// Step 3: Quantity & Reason
public string $adjustmentDirection = 'up';
public ?int $quantity = null;
public string $reason = '';
public ?string $referenceNumber = null;
// Cached display data
public ?array $selectedWarehouse = null;
public ?array $selectedProduct = null;
public ?int $currentStock = null;
public function mount(): void
{
$this->authorize('inventory.adjust');
}
public function getStepLabels(): array
{
return [
1 => 'المستودع',
2 => 'المنتج',
3 => 'الكمية والسبب',
4 => 'مراجعة',
5 => 'تأكيد',
];
}
public function selectWarehouse(int $id): void
{
$warehouse = Warehouse::findOrFail($id);
$this->warehouseId = $id;
$this->selectedWarehouse = [
'id' => $warehouse->id,
'name_ar' => $warehouse->name_ar,
'name' => $warehouse->name,
];
// Reset product selection when warehouse changes
$this->productId = null;
$this->selectedProduct = null;
$this->currentStock = null;
}
public function selectProduct(int $id): void
{
$product = Product::findOrFail($id);
$this->productId = $id;
$this->selectedProduct = [
'id' => $product->id,
'name_ar' => $product->name_ar,
'name' => $product->name,
'sku' => $product->sku,
];
$this->loadCurrentStock();
}
public function updatedWarehouseId(): void
{
if ($this->warehouseId && $this->productId) {
$this->loadCurrentStock();
}
}
public function updatedProductId(): void
{
if ($this->warehouseId && $this->productId) {
$this->loadCurrentStock();
}
}
private function loadCurrentStock(): void
{
if (!$this->warehouseId || !$this->productId) {
$this->currentStock = null;
return;
}
$level = InventoryLevel::where('product_id', $this->productId)
->where('warehouse_id', $this->warehouseId)
->first();
$this->currentStock = $level?->quantity_on_hand ?? 0;
}
public function getExpectedAfterProperty(): ?int
{
if ($this->currentStock === null || !$this->quantity) {
return null;
}
if ($this->adjustmentDirection === 'up') {
return $this->currentStock + $this->quantity;
}
return $this->currentStock - $this->quantity;
}
public function nextStep(): void
{
$this->validate($this->rulesForStep($this->currentStep));
if ($this->currentStep < $this->totalSteps) {
$this->currentStep++;
}
}
public function previousStep(): void
{
if ($this->currentStep > 1) {
$this->currentStep--;
}
}
public function goToStep(int $step): void
{
if ($step < $this->currentStep) {
$this->currentStep = $step;
}
}
public function confirm(): void
{
try {
$product = Product::findOrFail($this->productId);
$warehouse = Warehouse::findOrFail($this->warehouseId);
$movementType = $this->adjustmentDirection === 'up'
? MovementType::CountAdjustmentUp
: MovementType::CountAdjustmentDown;
app(InventoryService::class)->createMovement(
product: $product,
warehouse: $warehouse,
type: $movementType,
quantity: $this->quantity,
actor: auth()->user(),
reason: $this->reason,
);
$this->completed = true;
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
private function rulesForStep(int $step): array
{
return match ($step) {
1 => [
'warehouseId' => 'required|integer|exists:warehouses,id',
],
2 => [
'productId' => 'required|integer|exists:products,id',
],
3 => [
'adjustmentDirection' => 'required|in:up,down',
'quantity' => 'required|integer|min:1',
'reason' => 'required|string|min:5|max:500',
'referenceNumber' => 'nullable|string|max:100',
],
default => [],
};
}
public function render()
{
$warehouses = Warehouse::active()->orderBy('name_ar')->get(['id', 'name_ar', 'name', 'code']);
$products = collect();
if ($this->warehouseId) {
$products = Product::active()->tracked()->orderBy('name_ar')->get(['id', 'name_ar', 'name', 'sku']);
}
return view('livewire.inventory.stock-adjustment-wizard', [
'warehouses' => $warehouses,
'products' => $products,
]);
}
}
<?php
namespace App\Livewire\Invoices;
use App\Domain\Financial\Services\InvoiceService;
use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Exceptions\DomainException;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('إنشاء فاتورة جديدة')]
class CreateInvoiceWizard extends Component
{
public int $currentStep = 1;
public int $totalSteps = 5;
public bool $completed = false;
// Step 1: Client
public string $participantSearch = '';
public ?int $participantId = null;
public ?string $participantName = null;
public string $contactName = '';
public string $contactPhone = '';
// Step 2: Items (repeatable)
public array $items = [];
// Step 3: Discount & Tax
public string $discountAmount = '0';
public string $taxAmount = '0';
// Step 4: Dates
public string $issueDate = '';
public string $dueDate = '';
public string $notes = '';
public function mount(): void
{
$this->authorize('invoices.create');
$this->issueDate = now()->format('Y-m-d');
$this->dueDate = now()->addDays(30)->format('Y-m-d');
$this->addItem();
}
public function getStepLabels(): array
{
return [
1 => 'العميل',
2 => 'البنود',
3 => 'الخصم والضريبة',
4 => 'تاريخ الاستحقاق',
5 => 'مراجعة',
];
}
public function rulesForStep(): array
{
return match ($this->currentStep) {
1 => [
'participantId' => 'required|exists:participants,id',
'contactName' => 'nullable|string|max:255',
'contactPhone' => 'nullable|string|max:20',
],
2 => [
'items' => 'required|array|min:1',
'items.*.description' => 'required|string|max:255',
'items.*.quantity' => 'required|integer|min:1',
'items.*.unit_price' => 'required|numeric|min:0.01',
],
3 => [
'discountAmount' => 'nullable|numeric|min:0',
'taxAmount' => 'nullable|numeric|min:0',
],
4 => [
'issueDate' => 'required|date',
'dueDate' => 'required|date|after_or_equal:issueDate',
'notes' => 'nullable|string|max:1000',
],
default => [],
};
}
public function messages(): array
{
return [
'participantId.required' => 'يرجى اختيار العميل',
'participantId.exists' => 'العميل المختار غير موجود',
'items.required' => 'يجب إضافة بند واحد على الأقل',
'items.min' => 'يجب إضافة بند واحد على الأقل',
'items.*.description.required' => 'وصف البند مطلوب',
'items.*.quantity.required' => 'الكمية مطلوبة',
'items.*.quantity.min' => 'الكمية يجب أن تكون 1 على الأقل',
'items.*.unit_price.required' => 'سعر الوحدة مطلوب',
'items.*.unit_price.min' => 'سعر الوحدة يجب أن يكون أكبر من صفر',
'issueDate.required' => 'تاريخ الإصدار مطلوب',
'dueDate.required' => 'تاريخ الاستحقاق مطلوب',
'dueDate.after_or_equal' => 'تاريخ الاستحقاق يجب أن يكون بعد أو يساوي تاريخ الإصدار',
];
}
public function addItem(): void
{
$this->items[] = [
'description' => '',
'quantity' => 1,
'unit_price' => '',
];
}
public function removeItem(int $index): void
{
if (count($this->items) > 1) {
unset($this->items[$index]);
$this->items = array_values($this->items);
}
}
public function selectParticipant(int $id): void
{
$participant = Participant::with('person')->find($id);
if ($participant) {
$this->participantId = $participant->id;
$this->participantName = $participant->person?->name_ar ?? $participant->person?->name;
$this->contactName = $participant->person?->name_ar ?? '';
$this->contactPhone = $participant->person?->phone ?? '';
}
}
public function getSubtotalProperty(): int
{
$subtotal = 0;
foreach ($this->items as $item) {
$price = (float) ($item['unit_price'] ?? 0);
$qty = (int) ($item['quantity'] ?? 0);
$subtotal += (int) round($price * 100) * $qty;
}
return $subtotal;
}
public function getTotalProperty(): int
{
$subtotal = $this->getSubtotalProperty();
$discount = (int) round((float) ($this->discountAmount ?? 0) * 100);
$tax = (int) round((float) ($this->taxAmount ?? 0) * 100);
return $subtotal - $discount + $tax;
}
public function nextStep(): void
{
$this->validate($this->rulesForStep(), $this->messages());
$this->currentStep = min($this->currentStep + 1, $this->totalSteps);
}
public function previousStep(): void
{
$this->currentStep = max($this->currentStep - 1, 1);
}
public function goToStep(int $step): void
{
if ($step < $this->currentStep) {
$this->currentStep = $step;
}
}
public function confirm(): void
{
try {
$subtotal = $this->getSubtotalProperty();
$discount = (int) round((float) ($this->discountAmount ?? 0) * 100);
$tax = (int) round((float) ($this->taxAmount ?? 0) * 100);
$total = $subtotal - $discount + $tax;
$data = [
'billable_type' => \App\Domain\Participant\Models\Participant::class,
'billable_id' => $this->participantId,
'contact_name' => $this->contactName ?: null,
'contact_phone' => $this->contactPhone ?: null,
'subtotal_amount' => $subtotal,
'discount_amount' => $discount,
'tax_amount' => $tax,
'total_amount' => $total,
'issue_date' => $this->issueDate,
'due_date' => $this->dueDate,
'notes' => $this->notes ?: null,
'currency' => 'EGP',
];
$items = collect($this->items)->map(function ($item) {
$unitPrice = (int) round((float) $item['unit_price'] * 100);
$quantity = (int) $item['quantity'];
return [
'description' => $item['description'],
'quantity' => $quantity,
'unit_price' => $unitPrice,
'discount_amount' => 0,
'tax_amount' => 0,
];
})->toArray();
app(InvoiceService::class)->create($data, $items, auth()->user());
$this->completed = true;
session()->flash('success', 'تم إنشاء الفاتورة بنجاح');
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
public function render()
{
$searchResults = collect();
if (strlen($this->participantSearch) >= 2 && !$this->participantId) {
$searchResults = Participant::query()
->with('person')
->where(function ($q) {
$search = $this->participantSearch;
$q->where('participant_number', 'ilike', "%{$search}%")
->orWhereHas('person', function ($pq) use ($search) {
$pq->where('name_ar', 'ilike', "%{$search}%")
->orWhere('name', 'ilike', "%{$search}%")
->orWhere('phone', 'like', "%{$search}%");
});
})
->limit(10)
->get();
}
return view('livewire.invoices.create-invoice-wizard', [
'searchResults' => $searchResults,
]);
}
}
<?php
namespace App\Livewire\Pricing;
use App\Domain\Pricing\Enums\AdjustmentType;
use App\Domain\Pricing\Enums\PricingRuleType;
use App\Domain\Pricing\Models\PricingRule;
use App\Domain\Shared\Exceptions\DomainException;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('إنشاء قاعدة تسعير')]
class CreatePricingRuleWizard extends Component
{
public int $currentStep = 1;
public int $totalSteps = 5;
public bool $completed = false;
// Step 1: Rule Type
public string $ruleType = '';
// Step 2: Conditions (dynamic based on rule_type)
public ?int $minAge = null;
public ?int $maxAge = null;
public ?int $minMonths = null;
public ?int $minChildren = null;
public ?int $orderNumber = null;
public string $targetGender = '';
public ?int $targetBranchId = null;
public string $targetClassification = '';
public string $genericConditions = '';
// Step 3: Adjustment
public string $adjustmentType = '';
public ?int $adjustmentValue = null;
public ?int $maxDiscountPercent = null;
// Step 4: Scope & Priority
public string $nameAr = '';
public string $name = '';
public ?string $targetType = null;
public ?int $targetId = null;
public ?int $branchId = null;
public int $priority = 10;
public bool $isStackable = true;
public string $effectiveFrom = '';
public ?string $effectiveTo = null;
public bool $isActive = true;
public ?int $usageLimit = null;
public function mount(): void
{
$this->authorize('pricing.create');
$this->effectiveFrom = now()->toDateString();
}
public function getStepLabels(): array
{
return [
1 => 'نوع القاعدة',
2 => 'الشروط',
3 => 'التعديل',
4 => 'النطاق والأولوية',
5 => 'مراجعة',
];
}
public function updatedRuleType(): void
{
$this->minAge = null;
$this->maxAge = null;
$this->minMonths = null;
$this->minChildren = null;
$this->orderNumber = null;
$this->targetGender = '';
$this->targetBranchId = null;
$this->targetClassification = '';
$this->genericConditions = '';
}
public function nextStep(): void
{
$this->validate($this->rulesForStep($this->currentStep));
if ($this->currentStep < $this->totalSteps) {
$this->currentStep++;
}
}
public function previousStep(): void
{
if ($this->currentStep > 1) {
$this->currentStep--;
}
}
public function goToStep(int $step): void
{
if ($step < $this->currentStep) {
$this->currentStep = $step;
}
}
public function confirm(): void
{
try {
DB::transaction(function () {
PricingRule::create([
'academy_id' => app('current_academy')->id,
'name_ar' => $this->nameAr,
'name' => $this->name ?: null,
'rule_type' => $this->ruleType,
'adjustment_type' => $this->adjustmentType,
'adjustment_value' => $this->adjustmentValue,
'conditions' => $this->buildConditions(),
'target_type' => $this->targetType ?: null,
'target_id' => $this->targetId ?: null,
'branch_id' => $this->branchId ?: null,
'priority' => $this->priority,
'is_stackable' => $this->isStackable,
'max_discount_percent' => $this->maxDiscountPercent,
'effective_from' => $this->effectiveFrom,
'effective_to' => $this->effectiveTo ?: null,
'is_active' => $this->isActive,
'usage_limit' => $this->usageLimit,
'usage_count' => 0,
'created_by' => auth()->id(),
]);
});
$this->completed = true;
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
private function buildConditions(): array
{
return match ($this->ruleType) {
'age' => ['min_age' => $this->minAge, 'max_age' => $this->maxAge],
'membership_duration' => ['min_months' => $this->minMonths],
'family_size' => ['min_children' => $this->minChildren],
'sibling_order' => ['order_number' => $this->orderNumber],
'gender' => ['target_gender' => $this->targetGender],
'branch' => ['target_branch_id' => $this->targetBranchId],
'classification' => ['target_classification' => $this->targetClassification],
default => json_decode($this->genericConditions ?: '{}', true) ?: [],
};
}
private function rulesForStep(int $step): array
{
return match ($step) {
1 => [
'ruleType' => 'required|in:' . implode(',', array_column(PricingRuleType::cases(), 'value')),
],
2 => $this->conditionRulesForType(),
3 => [
'adjustmentType' => 'required|in:' . implode(',', array_column(AdjustmentType::cases(), 'value')),
'adjustmentValue' => 'required|integer|min:1',
'maxDiscountPercent' => 'nullable|integer|min:1|max:100',
],
4 => [
'nameAr' => 'required|string|min:3|max:255',
'name' => 'nullable|string|max:255',
'priority' => 'required|integer|min:1|max:100',
'effectiveFrom' => 'required|date',
'effectiveTo' => 'nullable|date|after:effectiveFrom',
'usageLimit' => 'nullable|integer|min:1',
],
default => [],
};
}
private function conditionRulesForType(): array
{
return match ($this->ruleType) {
'age' => [
'minAge' => 'nullable|integer|min:1|max:100',
'maxAge' => 'nullable|integer|min:1|max:100',
],
'membership_duration' => [
'minMonths' => 'required|integer|min:1',
],
'family_size' => [
'minChildren' => 'required|integer|min:2',
],
'sibling_order' => [
'orderNumber' => 'required|integer|min:1',
],
'gender' => [
'targetGender' => 'required|in:male,female',
],
'branch' => [
'targetBranchId' => 'required|integer|exists:branches,id',
],
'classification' => [
'targetClassification' => 'required|in:regular,vip,scholarship,staff_child,trial',
],
default => [],
};
}
public function getRuleTypeLabelProperty(): string
{
if (!$this->ruleType) {
return '';
}
return PricingRuleType::from($this->ruleType)->label();
}
public function getAdjustmentTypeLabelProperty(): string
{
if (!$this->adjustmentType) {
return '';
}
return AdjustmentType::from($this->adjustmentType)->label();
}
public function render()
{
return view('livewire.pricing.create-pricing-rule-wizard', [
'ruleTypes' => PricingRuleType::cases(),
'adjustmentTypes' => AdjustmentType::cases(),
'branches' => \App\Domain\Identity\Models\Branch::orderBy('name_ar')->get(['id', 'name_ar', 'name']),
]);
}
}
<?php
namespace App\Livewire\Programs;
use App\Domain\Identity\Models\Branch;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Training\Models\Activity;
use App\Domain\Training\Services\TrainingProgramService;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('إنشاء برنامج تدريبي')]
class CreateProgramWizard extends Component
{
public int $currentStep = 1;
public int $totalSteps = 4;
public bool $completed = false;
// Step 1: Basic Info
public string $nameAr = '';
public string $name = '';
public ?int $activityId = null;
public ?int $branchId = null;
public string $skillLevel = '';
public string $ageMin = '';
public string $ageMax = '';
public string $gender = '';
public string $descriptionAr = '';
// Step 2: Schedule & Duration
public string $sessionsPerWeek = '';
public string $sessionDurationMinutes = '';
public string $programDurationWeeks = '';
public string $minParticipants = '';
public string $maxParticipants = '';
// Step 3: Pricing
public string $basePrice = '';
public function mount(): void
{
$this->authorize('programs.create');
}
public function getStepLabels(): array
{
return [
1 => 'المعلومات الأساسية',
2 => 'الجدول والمدة',
3 => 'التسعير',
4 => 'مراجعة',
];
}
public function rulesForStep(int $step): array
{
return match ($step) {
1 => [
'nameAr' => 'required|string|max:255',
'activityId' => 'required|exists:activities,id',
'branchId' => 'required|exists:branches,id',
'skillLevel' => 'nullable|string|max:50',
'ageMin' => 'nullable|integer|min:2|max:99',
'ageMax' => 'nullable|integer|min:2|max:99|gte:ageMin',
'gender' => 'nullable|in:male,female',
],
2 => [
'sessionsPerWeek' => 'required|integer|min:1|max:14',
'sessionDurationMinutes' => 'required|integer|min:15|max:300',
'programDurationWeeks' => 'required|integer|min:1|max:104',
'minParticipants' => 'required|integer|min:1',
'maxParticipants' => 'required|integer|min:1|gte:minParticipants',
],
3 => [
'basePrice' => 'required|numeric|min:0',
],
default => [],
};
}
public function messagesForStep(int $step): array
{
return match ($step) {
1 => [
'nameAr.required' => 'اسم البرنامج بالعربي مطلوب',
'activityId.required' => 'يجب اختيار النشاط',
'activityId.exists' => 'النشاط المختار غير موجود',
'branchId.required' => 'يجب اختيار الفرع',
'branchId.exists' => 'الفرع غير موجود',
'ageMin.integer' => 'الحد الأدنى للعمر يجب أن يكون رقم صحيح',
'ageMax.gte' => 'الحد الأقصى للعمر يجب أن يكون أكبر من أو يساوي الحد الأدنى',
'gender.in' => 'قيمة الجنس غير صالحة',
],
2 => [
'sessionsPerWeek.required' => 'عدد الحصص في الأسبوع مطلوب',
'sessionsPerWeek.integer' => 'عدد الحصص يجب أن يكون رقم صحيح',
'sessionsPerWeek.min' => 'عدد الحصص يجب أن يكون حصة واحدة على الأقل',
'sessionDurationMinutes.required' => 'مدة الحصة مطلوبة',
'sessionDurationMinutes.integer' => 'مدة الحصة يجب أن تكون رقم صحيح',
'sessionDurationMinutes.min' => 'مدة الحصة يجب أن تكون 15 دقيقة على الأقل',
'programDurationWeeks.required' => 'مدة البرنامج مطلوبة',
'programDurationWeeks.integer' => 'مدة البرنامج يجب أن تكون رقم صحيح',
'minParticipants.required' => 'الحد الأدنى للمشتركين مطلوب',
'maxParticipants.required' => 'الحد الأقصى للمشتركين مطلوب',
'maxParticipants.gte' => 'الحد الأقصى يجب أن يكون أكبر من أو يساوي الحد الأدنى',
],
3 => [
'basePrice.required' => 'السعر الأساسي مطلوب',
'basePrice.numeric' => 'السعر يجب أن يكون رقم',
'basePrice.min' => 'السعر لا يمكن أن يكون سالب',
],
default => [],
};
}
public function nextStep(): void
{
$this->validate(
$this->rulesForStep($this->currentStep),
$this->messagesForStep($this->currentStep)
);
if ($this->currentStep < $this->totalSteps) {
$this->currentStep++;
}
}
public function previousStep(): void
{
if ($this->currentStep > 1) {
$this->currentStep--;
}
}
public function goToStep(int $step): void
{
if ($step < $this->currentStep) {
$this->currentStep = $step;
}
}
public function confirm(): void
{
try {
DB::transaction(function () {
$data = [
'academy_id' => app('current_academy')->id,
'name_ar' => $this->nameAr,
'name' => $this->name ?: null,
'activity_id' => $this->activityId,
'branch_id' => $this->branchId,
'skill_level' => $this->skillLevel ?: null,
'age_min' => $this->ageMin ?: null,
'age_max' => $this->ageMax ?: null,
'gender' => $this->gender ?: null,
'description_ar' => $this->descriptionAr ?: null,
'sessions_per_week' => (int) $this->sessionsPerWeek,
'session_duration_minutes' => (int) $this->sessionDurationMinutes,
'program_duration_weeks' => (int) $this->programDurationWeeks,
'min_participants' => (int) $this->minParticipants,
'max_participants' => (int) $this->maxParticipants,
'status' => 'draft',
];
app(TrainingProgramService::class)->create($data, auth()->user());
});
$this->completed = true;
session()->flash('success', 'تم إنشاء البرنامج التدريبي بنجاح');
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
public function render()
{
return view('livewire.programs.create-program-wizard', [
'stepLabels' => $this->getStepLabels(),
'activities' => Activity::orderBy('name_ar')->get(['id', 'name_ar', 'name']),
'branches' => Branch::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar']),
]);
}
}
<?php
use App\Domain\Document\Enums\DocumentStatus;
use App\Domain\Document\Enums\DocumentType;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('documents', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->foreignId('academy_id')->constrained('academies');
$table->string('documentable_type', 100);
$table->unsignedBigInteger('documentable_id');
$table->string('document_type', 30);
$table->string('file_path', 500);
$table->string('original_filename', 255);
$table->string('mime_type', 100);
$table->bigInteger('file_size');
$table->string('status', 20)->default('pending');
$table->date('expires_at')->nullable();
$table->foreignId('approved_by')->nullable()->constrained('users')->nullOnDelete();
$table->dateTime('approved_at')->nullable();
$table->text('rejection_reason')->nullable();
$table->text('notes')->nullable();
$table->foreignId('uploaded_by')->constrained('users');
$table->foreignId('created_by')->constrained('users');
$table->timestamps();
$table->softDeletes();
$table->index(['academy_id', 'documentable_type', 'documentable_id'], 'documents_polymorphic_idx');
$table->index(['academy_id', 'status']);
$table->index(['academy_id', 'document_type', 'status']);
$table->index('expires_at');
});
$types = implode("','", array_column(DocumentType::cases(), 'value'));
DB::statement("ALTER TABLE documents ADD CONSTRAINT documents_document_type_check CHECK (document_type IN ('{$types}'))");
$statuses = implode("','", array_column(DocumentStatus::cases(), 'value'));
DB::statement("ALTER TABLE documents ADD CONSTRAINT documents_status_check CHECK (status IN ('{$statuses}'))");
}
public function down(): void
{
Schema::dropIfExists('documents');
}
};
...@@ -183,6 +183,9 @@ private function getPermissionsList(): array ...@@ -183,6 +183,9 @@ private function getPermissionsList(): array
'evaluations.list', 'evaluations.create', 'evaluations.update', 'evaluations.manage', 'evaluations.list', 'evaluations.create', 'evaluations.update', 'evaluations.manage',
'evaluations.approve', 'evaluations.share', 'evaluations.approve', 'evaluations.share',
// Documents
'documents.list', 'documents.view', 'documents.upload', 'documents.approve', 'documents.delete',
// Audit // Audit
'audit.list', 'audit.view', 'audit.export', 'audit.list', 'audit.view', 'audit.export',
......
...@@ -86,6 +86,15 @@ public function run(): void ...@@ -86,6 +86,15 @@ public function run(): void
['group' => 'enrollment', 'key' => 'freeze_max_days', 'value' => '30', 'type' => 'integer', 'description_ar' => 'أقصى مدة تجميد الاشتراك (بالأيام)'], ['group' => 'enrollment', 'key' => 'freeze_max_days', 'value' => '30', 'type' => 'integer', 'description_ar' => 'أقصى مدة تجميد الاشتراك (بالأيام)'],
['group' => 'enrollment', 'key' => 'freeze_max_times', 'value' => '2', 'type' => 'integer', 'description_ar' => 'أقصى عدد مرات التجميد في الاشتراك الواحد'], ['group' => 'enrollment', 'key' => 'freeze_max_times', 'value' => '2', 'type' => 'integer', 'description_ar' => 'أقصى عدد مرات التجميد في الاشتراك الواحد'],
['group' => 'enrollment', 'key' => 'enrollment_expiry_days', 'value' => '30', 'type' => 'integer', 'description_ar' => 'مدة صلاحية الاشتراك الافتراضية (بالأيام)'], ['group' => 'enrollment', 'key' => 'enrollment_expiry_days', 'value' => '30', 'type' => 'integer', 'description_ar' => 'مدة صلاحية الاشتراك الافتراضية (بالأيام)'],
// Documents
['group' => 'enrollment', 'key' => 'require_documents_upload', 'value' => '0', 'type' => 'boolean', 'description_ar' => 'تفعيل نظام رفع المستندات (يسمح للمشتركين والموظفين برفع مستندات)'],
['group' => 'enrollment', 'key' => 'require_medical_certificate', 'value' => '0', 'type' => 'boolean', 'description_ar' => 'إلزام رفع شهادة طبية سارية'],
['group' => 'enrollment', 'key' => 'medical_certificate_expiry_required', 'value' => '1', 'type' => 'boolean', 'description_ar' => 'إلزام إدخال تاريخ انتهاء الشهادة الطبية عند الموافقة'],
['group' => 'enrollment', 'key' => 'medical_certificate_max_age_months', 'value' => '12', 'type' => 'integer', 'description_ar' => 'أقصى مدة صلاحية الشهادة الطبية بالأشهر'],
['group' => 'enrollment', 'key' => 'block_attendance_without_medical', 'value' => '0', 'type' => 'boolean', 'description_ar' => 'منع تسجيل الحضور بدون شهادة طبية سارية'],
['group' => 'enrollment', 'key' => 'allowed_document_types', 'value' => '["birth_certificate","national_id","medical_certificate","passport","photo","contract","qualification","insurance","other"]', 'type' => 'json', 'description_ar' => 'أنواع المستندات المسموح رفعها'],
['group' => 'enrollment', 'key' => 'max_document_size_mb', 'value' => '10', 'type' => 'integer', 'description_ar' => 'الحد الأقصى لحجم الملف المرفوع (ميجابايت)'],
]; ];
foreach ($settings as $setting) { foreach ($settings as $setting) {
......
<div>
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-6">
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('اعتماد المستندات') }}</h1>
</div>
{{-- Filters --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-4">
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
<input type="text" wire:model.live.debounce.300ms="search" placeholder="{{ __('بحث بالاسم أو الملف...') }}"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<select wire:model.live="status" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('كل الحالات') }}</option>
@foreach($statuses as $s)
<option value="{{ $s->value }}">{{ $s->label() }}</option>
@endforeach
</select>
<select wire:model.live="documentType" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('كل الأنواع') }}</option>
@foreach($documentTypes as $dt)
<option value="{{ $dt->value }}">{{ $dt->label() }}</option>
@endforeach
</select>
</div>
</div>
{{-- Table --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div wire:loading.class="opacity-50 pointer-events-none">
<table class="w-full text-sm">
<thead class="bg-gray-50 border-b border-gray-200">
<tr>
<th class="px-4 py-3 text-start font-medium text-gray-600">{{ __('المستند') }}</th>
<th class="px-4 py-3 text-start font-medium text-gray-600">{{ __('النوع') }}</th>
<th class="px-4 py-3 text-start font-medium text-gray-600">{{ __('صاحب المستند') }}</th>
<th class="px-4 py-3 text-start font-medium text-gray-600">{{ __('رافع المستند') }}</th>
<th class="px-4 py-3 text-start font-medium text-gray-600">{{ __('الحالة') }}</th>
<th class="px-4 py-3 text-start font-medium text-gray-600">{{ __('التاريخ') }}</th>
<th class="px-4 py-3 text-end font-medium text-gray-600">{{ __('إجراء') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
@forelse($documents as $doc)
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 text-gray-800" dir="ltr">{{ Str::limit($doc->original_filename, 30) }}</td>
<td class="px-4 py-3 text-gray-600">{{ $doc->document_type->label() }}</td>
<td class="px-4 py-3 text-gray-600">{{ $doc->documentable?->name_ar ?? '-' }}</td>
<td class="px-4 py-3 text-gray-600">{{ $doc->uploader?->name_ar ?? '-' }}</td>
<td class="px-4 py-3">
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium
{{ match($doc->status->badgeColor()) { 'yellow' => 'bg-yellow-100 text-yellow-700', 'green' => 'bg-green-100 text-green-700', 'red' => 'bg-red-100 text-red-700', default => 'bg-gray-100 text-gray-700' } }}">
{{ $doc->status->label() }}
</span>
</td>
<td class="px-4 py-3 text-gray-500 text-xs" dir="ltr">{{ $doc->created_at->format('Y-m-d') }}</td>
<td class="px-4 py-3 text-end">
<a href="{{ route('documents.review', $doc) }}" wire:navigate
class="text-blue-600 hover:text-blue-800 text-xs font-medium">{{ __('مراجعة') }}</a>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-12 text-center text-gray-400">{{ __('لا توجد مستندات') }}</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($documents->hasPages())
<div class="px-4 py-3 border-t border-gray-200">{{ $documents->links() }}</div>
@endif
</div>
</div>
<div>
<div class="flex items-center justify-between mb-6">
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('مراجعة مستند') }}</h1>
<a href="{{ route('documents.approvals') }}" wire:navigate
class="inline-flex items-center gap-2 px-4 py-2.5 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 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="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>
{{ __('رجوع') }}
</a>
</div>
@if(session('success'))
<div class="mb-4 p-3 rounded-lg bg-green-50 border border-green-200 text-green-700 text-sm">{{ session('success') }}</div>
@endif
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
{{-- Document Info --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('معلومات المستند') }}</h2>
<dl class="space-y-3">
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('النوع') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $document->document_type->label() }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('اسم الملف') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ $document->original_filename }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الحجم') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ number_format($document->file_size / 1024, 0) }} KB</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('صاحب المستند') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $document->documentable?->name_ar ?? '-' }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('رافع المستند') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $document->uploader?->name_ar ?? '-' }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('تاريخ الرفع') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ $document->created_at->format('Y-m-d H:i') }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الحالة') }}</dt>
<dd>
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium
{{ match($document->status->badgeColor()) { 'yellow' => 'bg-yellow-100 text-yellow-700', 'green' => 'bg-green-100 text-green-700', 'red' => 'bg-red-100 text-red-700', default => 'bg-gray-100 text-gray-700' } }}">
{{ $document->status->label() }}
</span>
</dd>
</div>
@if($document->notes)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('ملاحظات') }}</dt>
<dd class="text-sm text-gray-800">{{ $document->notes }}</dd>
</div>
@endif
</dl>
{{-- Preview/Download --}}
<div class="mt-4 flex gap-3">
<a href="{{ route('documents.view', $document) }}" target="_blank"
class="inline-flex items-center gap-2 px-4 py-2 bg-blue-50 text-blue-700 rounded-lg hover:bg-blue-100 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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
{{ __('معاينة') }}
</a>
<a href="{{ route('documents.download', $document) }}"
class="inline-flex items-center gap-2 px-4 py-2 bg-gray-50 text-gray-700 rounded-lg hover:bg-gray-100 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="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 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>
{{ __('تحميل') }}
</a>
</div>
</div>
{{-- Actions --}}
@if($document->isPending())
<div class="space-y-4">
{{-- Approve --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h2 class="text-lg font-semibold text-green-700 mb-4">{{ __('اعتماد المستند') }}</h2>
@if($expiryRequired)
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تاريخ الانتهاء') }} <span class="text-red-500">*</span></label>
<input type="date" wire:model="expiresAt" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 text-sm" dir="ltr">
@error('expiresAt') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
@else
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تاريخ الانتهاء (اختياري)') }}</label>
<input type="date" wire:model="expiresAt" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 text-sm" dir="ltr">
</div>
@endif
<button wire:click="approve" wire:loading.attr="disabled" wire:target="approve"
class="w-full px-4 py-2.5 bg-green-600 text-white rounded-lg hover:bg-green-700 text-sm font-medium disabled:opacity-50">
<span wire:loading.remove wire:target="approve">{{ __('اعتماد') }}</span>
<span wire:loading wire:target="approve">{{ __('جارٍ الاعتماد...') }}</span>
</button>
</div>
{{-- Reject --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h2 class="text-lg font-semibold text-red-700 mb-4">{{ __('رفض المستند') }}</h2>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('سبب الرفض') }} <span class="text-red-500">*</span></label>
<textarea wire:model="rejectionReason" rows="3" placeholder="{{ __('اكتب سبب رفض هذا المستند...') }}"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-red-500 text-sm"></textarea>
@error('rejectionReason') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<button wire:click="reject" wire:loading.attr="disabled" wire:target="reject"
class="w-full px-4 py-2.5 bg-red-600 text-white rounded-lg hover:bg-red-700 text-sm font-medium disabled:opacity-50">
<span wire:loading.remove wire:target="reject">{{ __('رفض') }}</span>
<span wire:loading wire:target="reject">{{ __('جارٍ الرفض...') }}</span>
</button>
</div>
</div>
@endif
</div>
</div>
<div>
<div class="flex items-center justify-between mb-6">
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('رفع مستند') }} — {{ $documentableLabel }}</h1>
</div>
{{-- Step Indicator --}}
<div class="flex items-center justify-center gap-2 mb-8">
@foreach($stepLabels as $num => $label)
<button wire:click="goToStep({{ $num }})"
class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<span class="flex items-center justify-center w-8 h-8 rounded-full text-sm font-bold
{{ $num < $currentStep ? 'bg-green-500 text-white' : ($num === $currentStep ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-500') }}">
@if($num < $currentStep)
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
@else
{{ $num }}
@endif
</span>
<span class="hidden sm:inline text-sm {{ $num === $currentStep ? 'text-blue-700 font-medium' : 'text-gray-500' }}">{{ $label }}</span>
</button>
@if(!$loop->last)
<div class="w-8 h-0.5 {{ $num < $currentStep ? 'bg-green-400' : 'bg-gray-200' }}"></div>
@endif
@endforeach
</div>
@if(session('error'))
<div class="mb-4 p-3 rounded-lg bg-red-50 border border-red-200 text-red-700 text-sm">{{ session('error') }}</div>
@endif
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
{{-- Step 1: Document Type --}}
@if($currentStep === 1)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('اختر نوع المستند') }}</h2>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3">
@foreach($allowedTypes as $type)
<label class="relative flex flex-col items-center p-4 rounded-lg border-2 cursor-pointer transition-all
{{ $documentType === $type->value ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-gray-300' }}">
<input type="radio" wire:model="documentType" value="{{ $type->value }}" class="sr-only">
<span class="text-sm font-medium text-center {{ $documentType === $type->value ? 'text-blue-700' : 'text-gray-700' }}">{{ $type->label() }}</span>
</label>
@endforeach
</div>
@error('documentType') <p class="mt-2 text-xs text-red-600">{{ $message }}</p> @enderror
@endif
{{-- Step 2: File Upload --}}
@if($currentStep === 2)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('رفع الملف') }}</h2>
<div class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center">
<input type="file" wire:model="file" class="hidden" id="file-upload">
<label for="file-upload" class="cursor-pointer">
<svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48">
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<p class="mt-2 text-sm text-gray-600">{{ __('اضغط لاختيار ملف أو اسحبه هنا') }}</p>
<p class="mt-1 text-xs text-gray-400">{{ __('الحد الأقصى:') }} {{ $maxSizeMb }} MB</p>
</label>
</div>
@if($file)
<div class="mt-3 p-3 bg-green-50 border border-green-200 rounded-lg flex items-center gap-3">
<svg class="w-5 h-5 text-green-600" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>
<span class="text-sm text-green-700">{{ $file->getClientOriginalName() }} ({{ number_format($file->getSize() / 1024, 0) }} KB)</span>
</div>
@endif
<div wire:loading wire:target="file" class="mt-3 text-sm text-blue-600">{{ __('جارٍ رفع الملف...') }}</div>
@error('file') <p class="mt-2 text-xs text-red-600">{{ $message }}</p> @enderror
@endif
{{-- Step 3: Notes --}}
@if($currentStep === 3)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('ملاحظات (اختياري)') }}</h2>
<textarea wire:model="notes" rows="4" placeholder="{{ __('أي ملاحظات إضافية حول هذا المستند...') }}"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"></textarea>
@error('notes') <p class="mt-2 text-xs text-red-600">{{ $message }}</p> @enderror
@endif
{{-- Step 4: Review/Confirm --}}
@if($currentStep === 4 && !$completed)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('مراجعة وتأكيد') }}</h2>
<dl class="space-y-3">
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('نوع المستند') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ \App\Domain\Document\Enums\DocumentType::from($documentType)->label() }}</dd>
</div>
@if($file)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('اسم الملف') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ $file->getClientOriginalName() }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('حجم الملف') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ number_format($file->getSize() / 1024, 0) }} KB</dd>
</div>
@endif
@if($notes)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('ملاحظات') }}</dt>
<dd class="text-sm text-gray-800">{{ $notes }}</dd>
</div>
@endif
</dl>
@endif
{{-- Success --}}
@if($completed && $createdDocument)
<div class="text-center py-8">
<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="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
</div>
<h2 class="text-xl font-bold text-gray-800 mb-2">{{ __('تم رفع المستند بنجاح') }}</h2>
<p class="text-sm text-gray-500 mb-1">{{ $createdDocument['type_label'] }} — {{ $createdDocument['original_filename'] }}</p>
<p class="text-sm text-gray-500">{{ __('الحالة:') }} {{ $createdDocument['status_label'] }}</p>
</div>
@endif
</div>
{{-- Navigation Buttons --}}
@if(!$completed)
<div class="flex items-center justify-between mt-6">
<button wire:click="previousStep" @if($currentStep === 1) disabled @endif
class="px-5 py-2.5 text-sm font-medium rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed">
{{ __('السابق') }}
</button>
@if($currentStep < $totalSteps)
<button wire:click="nextStep"
class="px-5 py-2.5 text-sm font-medium rounded-lg bg-blue-600 text-white hover:bg-blue-700">
{{ __('التالي') }}
</button>
@else
<button wire:click="confirm" wire:loading.attr="disabled" wire:target="confirm"
class="px-5 py-2.5 text-sm font-medium rounded-lg bg-green-600 text-white hover:bg-green-700 disabled:opacity-50">
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد الرفع') }}</span>
<span wire:loading wire:target="confirm">{{ __('جارٍ الرفع...') }}</span>
</button>
@endif
</div>
@endif
</div>
<div>
@if($show)
<div class="rounded-lg p-3 flex items-center gap-3 {{ $isBlocking ? 'bg-red-50 border border-red-200' : 'bg-yellow-50 border border-yellow-200' }}">
<svg class="w-5 h-5 flex-shrink-0 {{ $isBlocking ? 'text-red-500' : 'text-yellow-500' }}" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
</svg>
<div>
<p class="text-sm font-medium {{ $isBlocking ? 'text-red-800' : 'text-yellow-800' }}">
{{ $isBlocking ? __('مطلوب شهادة طبية سارية — لا يمكن تسجيل الحضور') : __('لم يتم رفع شهادة طبية سارية') }}
</p>
<p class="text-xs {{ $isBlocking ? 'text-red-600' : 'text-yellow-600' }} mt-0.5">
{{ __('يرجى رفع شهادة طبية صالحة للمشترك') }}
</p>
</div>
</div>
@endif
</div>
<div>
<div class="flex items-center justify-between mb-4">
<h3 class="text-base font-semibold text-gray-800">{{ __('المستندات') }}</h3>
@can('documents.upload')
<a href="{{ route('documents.upload', ['documentableType' => 'participant', 'documentableId' => $participant->id]) }}" wire:navigate
class="inline-flex items-center gap-1.5 px-3 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-xs font-medium">
<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="M12 4v16m8-8H4"/></svg>
{{ __('رفع مستند') }}
</a>
@endcan
</div>
@if($documents->isEmpty())
<div class="text-center py-8 text-gray-400 text-sm">{{ __('لا توجد مستندات مرفوعة') }}</div>
@else
<div class="space-y-2">
@foreach($documents as $doc)
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg border border-gray-100">
<div class="flex items-center gap-3 min-w-0">
<div class="flex-shrink-0 w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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>
</div>
<div class="min-w-0">
<p class="text-sm font-medium text-gray-800 truncate">{{ $doc->document_type->label() }}</p>
<p class="text-xs text-gray-500 truncate" dir="ltr">{{ $doc->original_filename }}</p>
</div>
</div>
<div class="flex items-center gap-2">
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-medium
{{ match($doc->status->badgeColor()) { 'yellow' => 'bg-yellow-100 text-yellow-700', 'green' => 'bg-green-100 text-green-700', 'red' => 'bg-red-100 text-red-700', default => 'bg-gray-100 text-gray-700' } }}">
{{ $doc->status->label() }}
</span>
@if($doc->expires_at)
<span class="text-[10px] text-gray-400" dir="ltr">{{ $doc->expires_at->format('Y-m-d') }}</span>
@endif
<a href="{{ route('documents.view', $doc) }}" target="_blank" class="text-blue-600 hover:text-blue-800">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
</a>
@can('documents.delete')
<button wire:click="delete('{{ $doc->uuid }}')" wire:confirm="{{ __('هل أنت متأكد من حذف هذا المستند؟') }}"
class="text-red-500 hover:text-red-700">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</button>
@endcan
</div>
</div>
@endforeach
</div>
@endif
</div>
<div>
{{-- Header --}}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('نقل مشترك') }}</h1>
<p class="text-sm text-gray-500 mt-1">{{ __('نقل مشترك من مجموعة إلى أخرى في نفس البرنامج') }}</p>
</div>
</div>
{{-- Flash Messages --}}
@if(session('error'))
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-xl text-red-700 text-sm">
{{ session('error') }}
</div>
@endif
{{-- Success State --}}
@if($completed)
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-8 text-center">
<div class="w-16 h-16 mx-auto mb-4 bg-green-100 rounded-full flex items-center justify-center">
<svg class="w-8 h-8 text-green-600" 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>
<h2 class="text-xl font-bold text-gray-800 mb-2">{{ __('تم النقل بنجاح') }}</h2>
<p class="text-gray-500 mb-6">{{ __('تم نقل المشترك إلى المجموعة الجديدة بنجاح') }}</p>
<div class="flex items-center justify-center gap-4">
<a href="{{ route('enrollments.index') }}" wire:navigate
class="inline-flex items-center gap-2 px-6 py-3 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 font-medium transition-colors">
{{ __('العودة للتسجيلات') }}
</a>
</div>
</div>
@else
{{-- Step Indicator --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6">
<div class="flex items-center justify-between">
@foreach($this->getStepLabels() as $num => $label)
<div class="flex items-center {{ !$loop->last ? 'flex-1' : '' }}">
<button wire:click="goToStep({{ $num }})"
@if($num >= $currentStep) disabled @endif
class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold transition-colors
{{ $num === $currentStep ? 'bg-emerald-600 text-white' : '' }}
{{ $num < $currentStep ? 'bg-green-500 text-white' : '' }}
{{ $num > $currentStep ? 'bg-gray-200 text-gray-500' : '' }}">
@if($num < $currentStep)
<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>
@else
{{ $num }}
@endif
</div>
<span class="text-xs font-medium hidden sm:inline
{{ $num === $currentStep ? 'text-emerald-600' : '' }}
{{ $num < $currentStep ? 'text-green-600' : '' }}
{{ $num > $currentStep ? 'text-gray-400' : '' }}">
{{ __($label) }}
</span>
</button>
@if(!$loop->last)
<div class="flex-1 h-0.5 mx-3 {{ $num < $currentStep ? 'bg-green-500' : 'bg-gray-200' }}"></div>
@endif
</div>
@endforeach
</div>
</div>
{{-- Step Content --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6">
{{-- Step 1: Select Participant & Enrollment --}}
@if($currentStep === 1)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('اختيار المشترك والتسجيل') }}</h2>
{{-- Search --}}
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('بحث عن المشترك') }}</label>
<input type="text" wire:model.live.debounce.300ms="participantSearch"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 text-lg"
placeholder="{{ __('بحث بالاسم، الهاتف، الرقم القومي، أو رقم المشترك...') }}">
</div>
{{-- Selected Participant --}}
@if($selectedParticipant)
<div class="mb-4 p-4 bg-emerald-50 border border-emerald-200 rounded-xl flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-emerald-200 flex items-center justify-center">
<svg class="w-5 h-5 text-emerald-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</svg>
</div>
<div>
<span class="font-medium text-emerald-800">{{ $selectedParticipant['name'] }}</span>
@if($selectedParticipant['participant_number'])
<span class="text-xs text-emerald-600 ms-2">#{{ $selectedParticipant['participant_number'] }}</span>
@endif
</div>
</div>
<button wire:click="$set('participantId', null)" class="text-emerald-600 hover:text-emerald-800 text-sm">
{{ __('تغيير') }}
</button>
</div>
{{-- Active Enrollments --}}
@if(count($activeEnrollments) > 0)
<div class="mb-4">
<h3 class="text-sm font-medium text-gray-700 mb-3">{{ __('التسجيلات النشطة') }}</h3>
<div class="space-y-2">
@foreach($activeEnrollments as $enrollment)
<label class="relative cursor-pointer block">
<input type="radio" wire:click="selectEnrollment({{ $enrollment['id'] }})" name="enrollment"
{{ $enrollmentId === $enrollment['id'] ? 'checked' : '' }} class="peer sr-only">
<div class="p-4 border-2 rounded-xl transition-all
peer-checked:border-emerald-500 peer-checked:bg-emerald-50
border-gray-200 hover:border-gray-300">
<div class="flex items-center justify-between">
<div>
<div class="font-medium text-gray-800">{{ $enrollment['group_name'] }}</div>
<div class="text-sm text-gray-500">{{ $enrollment['program_name'] }}</div>
</div>
<div class="text-xs text-gray-400">{{ $enrollment['enrollment_date'] }}</div>
</div>
</div>
</label>
@endforeach
</div>
</div>
@else
<div class="p-4 bg-amber-50 border border-amber-200 rounded-xl text-amber-700 text-sm">
{{ __('لا توجد تسجيلات نشطة لهذا المشترك') }}
</div>
@endif
@endif
{{-- Search Results --}}
@if(strlen($participantSearch) >= 2 && !$selectedParticipant)
<div class="space-y-2">
@forelse($searchResults as $participant)
<button wire:click="selectParticipant({{ $participant->id }})"
class="w-full text-start p-4 border border-gray-200 rounded-xl hover:border-emerald-300 hover:bg-emerald-50 transition-colors">
<div class="flex items-center justify-between">
<div>
<div class="font-medium text-gray-800">{{ $participant->person?->name_ar ?? $participant->person?->name ?? '-' }}</div>
<div class="text-sm text-gray-500">{{ $participant->participant_number }}</div>
</div>
<span class="px-2 py-1 text-xs rounded-full
{{ $participant->status->value === 'active' ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-600' }}">
{{ $participant->status->value === 'active' ? __('نشط') : $participant->status->value }}
</span>
</div>
</button>
@empty
<div class="p-4 text-center text-gray-500 text-sm">
{{ __('لا توجد نتائج') }}
</div>
@endforelse
</div>
@endif
@error('participantId') <p class="text-red-500 text-xs mt-2">{{ $message }}</p> @enderror
@error('enrollmentId') <p class="text-red-500 text-xs mt-2">{{ $message }}</p> @enderror
</div>
@endif
{{-- Step 2: Destination Group --}}
@if($currentStep === 2)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-2">{{ __('اختيار المجموعة الوجهة') }}</h2>
<p class="text-sm text-gray-500 mb-6">{{ __('اختر المجموعة التي سيتم نقل المشترك إليها') }}</p>
@if($availableGroups->count() > 0)
<div class="space-y-3">
@foreach($availableGroups as $group)
<label class="relative cursor-pointer block">
<input type="radio" wire:click="selectDestinationGroup({{ $group->id }})" name="destination"
{{ $destinationGroupId === $group->id ? 'checked' : '' }} class="peer sr-only">
<div class="p-4 border-2 rounded-xl transition-all
peer-checked:border-emerald-500 peer-checked:bg-emerald-50
border-gray-200 hover:border-gray-300
{{ $group->isFull() ? 'opacity-50' : '' }}">
<div class="flex items-center justify-between">
<div>
<div class="font-medium text-gray-800">{{ $group->name_ar }}</div>
@if($group->name)
<div class="text-xs text-gray-400">{{ $group->name }}</div>
@endif
</div>
<div class="flex items-center gap-3">
<span class="px-3 py-1 text-sm rounded-full font-medium
{{ $group->hasAvailableSpots() ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700' }}">
{{ $group->current_count }}/{{ $group->max_capacity }}
</span>
@if($group->hasAvailableSpots())
<span class="text-xs text-green-600">{{ __('متاح') }}: {{ $group->max_capacity - $group->current_count }}</span>
@else
<span class="text-xs text-red-600">{{ __('ممتلئة') }}</span>
@endif
</div>
</div>
</div>
</label>
@endforeach
</div>
@else
<div class="p-6 text-center text-gray-500">
<svg class="w-12 h-12 mx-auto mb-3 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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>
@endif
@error('destinationGroupId') <p class="text-red-500 text-xs mt-2">{{ $message }}</p> @enderror
</div>
@endif
{{-- Step 3: Impact --}}
@if($currentStep === 3)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('تأثير النقل') }}</h2>
{{-- Transfer Visual --}}
<div class="flex items-center justify-center gap-4 mb-6 p-6 bg-gray-50 rounded-xl">
<div class="text-center p-4 bg-white rounded-lg border border-gray-200 flex-1">
<div class="text-xs text-gray-500 mb-1">{{ __('من') }}</div>
<div class="font-medium text-gray-800">{{ $sourceGroup['name_ar'] ?? '-' }}</div>
<div class="text-sm text-gray-500 mt-1" dir="ltr">
{{ ($sourceGroup['current_count'] ?? 0) }}/{{ $sourceGroup['max_capacity'] ?? 0 }}
</div>
<div class="text-xs text-green-600 mt-1">
{{ __('بعد النقل:') }} {{ (($sourceGroup['current_count'] ?? 0) - 1) }}/{{ $sourceGroup['max_capacity'] ?? 0 }}
</div>
</div>
<div class="text-gray-400">
<svg class="w-8 h-8 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"/>
</svg>
</div>
<div class="text-center p-4 bg-white rounded-lg border border-gray-200 flex-1">
<div class="text-xs text-gray-500 mb-1">{{ __('إلى') }}</div>
<div class="font-medium text-gray-800">{{ $destinationGroup['name_ar'] ?? '-' }}</div>
<div class="text-sm text-gray-500 mt-1" dir="ltr">
{{ ($destinationGroup['current_count'] ?? 0) }}/{{ $destinationGroup['max_capacity'] ?? 0 }}
</div>
<div class="text-xs text-amber-600 mt-1">
{{ __('بعد النقل:') }} {{ (($destinationGroup['current_count'] ?? 0) + 1) }}/{{ $destinationGroup['max_capacity'] ?? 0 }}
</div>
</div>
</div>
{{-- Reason --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('سبب النقل') }} <span class="text-red-500">*</span></label>
<textarea wire:model="reason" rows="3"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
placeholder="{{ __('اذكر سبب نقل المشترك...') }}"></textarea>
@error('reason') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
</div>
@endif
{{-- Step 4: Confirm --}}
@if($currentStep === 4)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('تأكيد النقل') }}</h2>
<div class="space-y-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('المشترك') }}</div>
<div class="font-medium text-gray-800">{{ $selectedParticipant['name'] ?? '-' }}</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('البرنامج') }}</div>
<div class="font-medium text-gray-800">{{ $selectedEnrollment['program_name'] ?? '-' }}</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('المجموعة الحالية') }}</div>
<div class="font-medium text-gray-800">{{ $sourceGroup['name_ar'] ?? '-' }}</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('المجموعة الجديدة') }}</div>
<div class="font-medium text-gray-800">{{ $destinationGroup['name_ar'] ?? '-' }}</div>
</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('سبب النقل') }}</div>
<div class="text-gray-800">{{ $reason }}</div>
</div>
<div class="p-4 bg-amber-50 border border-amber-200 rounded-lg">
<div class="flex items-start gap-2">
<svg class="w-5 h-5 text-amber-600 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
<div class="text-sm text-amber-700">
{{ __('سيتم إلغاء التسجيل الحالي وإنشاء تسجيل جديد في المجموعة الوجهة. هذا الإجراء لا يمكن التراجع عنه.') }}
</div>
</div>
</div>
</div>
</div>
@endif
{{-- Navigation Buttons --}}
<div class="flex items-center justify-between mt-8 pt-6 border-t border-gray-200">
<div>
@if($currentStep > 1)
<button wire:click="previousStep"
class="px-5 py-2.5 text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 font-medium transition-colors">
{{ __('السابق') }}
</button>
@endif
</div>
<div>
@if($currentStep < $totalSteps)
<button wire:click="nextStep" wire:loading.attr="disabled" wire:target="nextStep"
class="px-6 py-2.5 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 font-medium transition-colors disabled:opacity-50">
<span wire:loading.remove wire:target="nextStep">{{ __('التالي') }}</span>
<span wire:loading wire:target="nextStep">{{ __('جارٍ التحقق...') }}</span>
</button>
@else
<button wire:click="confirm" wire:loading.attr="disabled" wire:target="confirm"
class="px-6 py-3 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 font-medium disabled:opacity-50">
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد النقل') }}</span>
<span wire:loading wire:target="confirm">{{ __('جارٍ النقل...') }}</span>
</button>
@endif
</div>
</div>
</div>
@endif
</div>
<div>
{{-- Header --}}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('إنشاء منشأة جديدة') }}</h1>
<p class="text-sm text-gray-500 mt-1">{{ __('إضافة منشأة رياضية جديدة للنظام') }}</p>
</div>
</div>
{{-- Flash Messages --}}
@if(session('error'))
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-xl text-red-700 text-sm">
{{ session('error') }}
</div>
@endif
{{-- Step Indicator --}}
@if(!$completed)
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6">
<div class="flex items-center justify-between">
@php $steps = $this->getStepLabels(); @endphp
@foreach($steps as $num => $label)
<div class="flex items-center {{ !$loop->last ? 'flex-1' : '' }}">
<button wire:click="goToStep({{ $num }})"
@if($num >= $currentStep) disabled @endif
class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold transition-colors
{{ $num === $currentStep ? 'bg-blue-600 text-white' : '' }}
{{ $num < $currentStep ? 'bg-green-500 text-white' : '' }}
{{ $num > $currentStep ? 'bg-gray-200 text-gray-500' : '' }}">
@if($num < $currentStep)
<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>
@else
{{ $num }}
@endif
</div>
<span class="text-xs font-medium hidden sm:inline
{{ $num === $currentStep ? 'text-blue-600' : '' }}
{{ $num < $currentStep ? 'text-green-600' : '' }}
{{ $num > $currentStep ? 'text-gray-400' : '' }}">
{{ __($label) }}
</span>
</button>
@if(!$loop->last)
<div class="flex-1 h-0.5 mx-3 {{ $num < $currentStep ? 'bg-green-500' : 'bg-gray-200' }}"></div>
@endif
</div>
@endforeach
</div>
</div>
@endif
{{-- Step Content --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6">
{{-- Step 1: Basic Info --}}
@if($currentStep === 1)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('المعلومات الأساسية') }}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{{-- Name Arabic --}}
<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="nameAr"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="{{ __('مثال: ملعب 1') }}">
@error('nameAr') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Name English --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('اسم المنشأة بالإنجليزية') }}</label>
<input type="text" wire:model="name" dir="ltr"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Field 1">
@error('name') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Type --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نوع المنشأة') }} <span class="text-red-500">*</span></label>
<select wire:model="type"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="">{{ __('-- اختر النوع --') }}</option>
<option value="field">{{ __('ملعب') }}</option>
<option value="court">{{ __('كورت') }}</option>
<option value="pool">{{ __('حمام سباحة') }}</option>
<option value="gym">{{ __('صالة رياضية') }}</option>
<option value="track">{{ __('مضمار') }}</option>
<option value="hall">{{ __('قاعة') }}</option>
<option value="room">{{ __('غرفة') }}</option>
<option value="outdoor">{{ __('مساحة خارجية') }}</option>
<option value="other">{{ __('أخرى') }}</option>
</select>
@error('type') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Branch --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الفرع') }} <span class="text-red-500">*</span></label>
<select wire:model="branchId"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="">{{ __('-- اختر الفرع --') }}</option>
@foreach($branches as $branch)
<option value="{{ $branch['id'] }}">{{ $branch['name_ar'] }}</option>
@endforeach
</select>
@error('branchId') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Capacity --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('السعة') }}</label>
<input type="number" wire:model="capacity" dir="ltr" min="1"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="50">
@error('capacity') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Description --}}
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الوصف') }}</label>
<textarea wire:model="descriptionAr" rows="3"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="{{ __('وصف مختصر للمنشأة...') }}"></textarea>
@error('descriptionAr') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
</div>
</div>
@endif
{{-- Step 2: Specs --}}
@if($currentStep === 2)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('المواصفات') }}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{{-- Surface Type --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نوع السطح') }}</label>
<input type="text" wire:model="surfaceType"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="{{ __('مثال: نجيل صناعي، خشب، تارتان') }}">
@error('surfaceType') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Area --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('المساحة (م²)') }}</label>
<input type="number" wire:model="areaSqm" dir="ltr" step="0.01" min="0"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="500">
@error('areaSqm') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Length --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الطول (م)') }}</label>
<input type="number" wire:model="lengthM" dir="ltr" step="0.01" min="0"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="100">
@error('lengthM') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Width --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('العرض (م)') }}</label>
<input type="number" wire:model="widthM" dir="ltr" step="0.01" min="0"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="50">
@error('widthM') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Boolean Options --}}
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-3">{{ __('الخصائص') }}</label>
<div class="flex flex-wrap gap-6">
<label class="inline-flex items-center gap-2 cursor-pointer">
<input type="checkbox" wire:model="isIndoor"
class="w-5 h-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<span class="text-sm text-gray-700">{{ __('مغطى (داخلي)') }}</span>
</label>
<label class="inline-flex items-center gap-2 cursor-pointer">
<input type="checkbox" wire:model="hasLighting"
class="w-5 h-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<span class="text-sm text-gray-700">{{ __('إضاءة') }}</span>
</label>
<label class="inline-flex items-center gap-2 cursor-pointer">
<input type="checkbox" wire:model="hasAc"
class="w-5 h-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<span class="text-sm text-gray-700">{{ __('تكييف') }}</span>
</label>
</div>
</div>
</div>
</div>
@endif
{{-- Step 3: Operating Hours --}}
@if($currentStep === 3)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('ساعات التشغيل') }}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{{-- Operating Start --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('وقت الافتتاح') }}</label>
<input type="time" wire:model="operatingStart" dir="ltr"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
@error('operatingStart') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Operating End --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('وقت الإغلاق') }}</label>
<input type="time" wire:model="operatingEnd" dir="ltr"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
@error('operatingEnd') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Rental Cost --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تكلفة الإيجار بالساعة') }}</label>
<div class="relative">
<input type="number" wire:model="rentalCostPerHour" dir="ltr" step="0.01" min="0"
class="w-full px-4 py-2.5 pe-12 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="0.00">
<span class="absolute end-3 top-1/2 -translate-y-1/2 text-sm text-gray-500">{{ __('ج.م') }}</span>
</div>
@error('rentalCostPerHour') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Address --}}
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('العنوان') }}</label>
<textarea wire:model="address" rows="2"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="{{ __('العنوان التفصيلي للمنشأة...') }}"></textarea>
@error('address') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
</div>
</div>
@endif
{{-- Step 4: Review --}}
@if($currentStep === 4 && !$completed)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('مراجعة البيانات') }}</h2>
<div class="space-y-4">
{{-- Basic Info Section --}}
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="text-sm font-semibold text-gray-600 mb-3">{{ __('المعلومات الأساسية') }}</h3>
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
<div>
<dt class="text-gray-500">{{ __('الاسم بالعربية') }}</dt>
<dd class="font-medium text-gray-800">{{ $nameAr }}</dd>
</div>
@if($name)
<div>
<dt class="text-gray-500">{{ __('الاسم بالإنجليزية') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ $name }}</dd>
</div>
@endif
<div>
<dt class="text-gray-500">{{ __('النوع') }}</dt>
<dd class="font-medium text-gray-800">
@php
$typeLabels = ['field'=>'ملعب','court'=>'كورت','pool'=>'حمام سباحة','gym'=>'صالة رياضية','track'=>'مضمار','hall'=>'قاعة','room'=>'غرفة','outdoor'=>'مساحة خارجية','other'=>'أخرى'];
@endphp
{{ $typeLabels[$type] ?? $type }}
</dd>
</div>
<div>
<dt class="text-gray-500">{{ __('الفرع') }}</dt>
<dd class="font-medium text-gray-800">{{ collect($branches)->firstWhere('id', $branchId)['name_ar'] ?? '-' }}</dd>
</div>
@if($capacity)
<div>
<dt class="text-gray-500">{{ __('السعة') }}</dt>
<dd class="font-medium text-gray-800">{{ $capacity }}</dd>
</div>
@endif
</dl>
</div>
{{-- Specs Section --}}
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="text-sm font-semibold text-gray-600 mb-3">{{ __('المواصفات') }}</h3>
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
@if($surfaceType)
<div>
<dt class="text-gray-500">{{ __('نوع السطح') }}</dt>
<dd class="font-medium text-gray-800">{{ $surfaceType }}</dd>
</div>
@endif
@if($areaSqm)
<div>
<dt class="text-gray-500">{{ __('المساحة') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ $areaSqm }} {{ __('م²') }}</dd>
</div>
@endif
@if($lengthM)
<div>
<dt class="text-gray-500">{{ __('الطول') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ $lengthM }} {{ __('م') }}</dd>
</div>
@endif
@if($widthM)
<div>
<dt class="text-gray-500">{{ __('العرض') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ $widthM }} {{ __('م') }}</dd>
</div>
@endif
<div class="sm:col-span-2">
<dt class="text-gray-500 mb-1">{{ __('الخصائص') }}</dt>
<dd class="flex flex-wrap gap-2">
@if($isIndoor) <span class="px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs">{{ __('مغطى') }}</span> @endif
@if($hasLighting) <span class="px-2 py-0.5 bg-yellow-100 text-yellow-700 rounded text-xs">{{ __('إضاءة') }}</span> @endif
@if($hasAc) <span class="px-2 py-0.5 bg-green-100 text-green-700 rounded text-xs">{{ __('تكييف') }}</span> @endif
@if(!$isIndoor && !$hasLighting && !$hasAc) <span class="text-gray-400 text-xs">{{ __('لا توجد خصائص إضافية') }}</span> @endif
</dd>
</div>
</dl>
</div>
{{-- Operating Hours Section --}}
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="text-sm font-semibold text-gray-600 mb-3">{{ __('ساعات التشغيل') }}</h3>
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
@if($operatingStart && $operatingEnd)
<div>
<dt class="text-gray-500">{{ __('ساعات العمل') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ $operatingStart }} - {{ $operatingEnd }}</dd>
</div>
@endif
@if($rentalCostPerHour)
<div>
<dt class="text-gray-500">{{ __('تكلفة الإيجار بالساعة') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ number_format((float)$rentalCostPerHour, 2) }} {{ __('ج.م') }}</dd>
</div>
@endif
@if($address)
<div class="sm:col-span-2">
<dt class="text-gray-500">{{ __('العنوان') }}</dt>
<dd class="font-medium text-gray-800">{{ $address }}</dd>
</div>
@endif
</dl>
</div>
</div>
</div>
@endif
{{-- Success State --}}
@if($completed)
<div class="text-center py-8">
<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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
</div>
<h2 class="text-xl font-bold text-gray-800 mb-2">{{ __('تم إنشاء المنشأة بنجاح') }}</h2>
<p class="text-gray-500 mb-6">{{ __('يمكنك الآن إدارة المنشأة وإضافة تخطيطات المساحة') }}</p>
</div>
@endif
{{-- Navigation Buttons --}}
@if(!$completed)
<div class="flex items-center justify-between mt-8 pt-4 border-t border-gray-200">
@if($currentStep > 1)
<button wire:click="previousStep" type="button"
class="inline-flex items-center gap-2 px-5 py-2.5 text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 font-medium text-sm transition-colors">
<svg class="w-4 h-4 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>
@else
<div></div>
@endif
@if($currentStep < $totalSteps)
<button wire:click="nextStep" type="button"
wire:loading.attr="disabled" wire:target="nextStep"
class="inline-flex items-center gap-2 px-5 py-2.5 text-white bg-blue-600 rounded-lg hover:bg-blue-700 font-medium text-sm transition-colors">
<span wire:loading.remove wire:target="nextStep">{{ __('التالي') }}</span>
<span wire:loading wire:target="nextStep">{{ __('جارٍ التحقق...') }}</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
</button>
@else
<button wire:click="confirm" type="button"
wire:loading.attr="disabled" wire:target="confirm"
class="inline-flex items-center gap-2 px-6 py-2.5 text-white bg-green-600 rounded-lg hover:bg-green-700 font-medium text-sm transition-colors">
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد الإنشاء') }}</span>
<span wire:loading wire:target="confirm">{{ __('جارٍ الحفظ...') }}</span>
</button>
@endif
</div>
@endif
</div>
</div>
<div>
<div class="flex items-center justify-between mb-6">
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('إنشاء مجموعة تدريبية') }}</h1>
</div>
{{-- Step Indicator --}}
<div class="flex items-center justify-center gap-2 mb-8">
@foreach($stepLabels as $num => $label)
<button wire:click="goToStep({{ $num }})"
class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<span class="flex items-center justify-center w-8 h-8 rounded-full text-sm font-bold
{{ $num < $currentStep ? 'bg-green-500 text-white' : ($num === $currentStep ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-500') }}">
@if($num < $currentStep)
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
@else
{{ $num }}
@endif
</span>
<span class="hidden sm:inline text-sm {{ $num === $currentStep ? 'text-blue-700 font-medium' : 'text-gray-500' }}">{{ $label }}</span>
</button>
@if(!$loop->last)
<div class="w-8 h-0.5 {{ $num < $currentStep ? 'bg-green-400' : 'bg-gray-200' }}"></div>
@endif
@endforeach
</div>
@if(session('error'))
<div class="mb-4 p-3 rounded-lg bg-red-50 border border-red-200 text-red-700 text-sm">{{ session('error') }}</div>
@endif
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
{{-- Step 1: Program Selection --}}
@if($currentStep === 1)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('اختر البرنامج التدريبي') }}</h2>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تصفية بالنشاط') }}</label>
<select wire:model.live="activityFilter" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('جميع الأنشطة') }}</option>
@foreach($activities as $activity)
<option value="{{ $activity->id }}">{{ $activity->name_ar }}</option>
@endforeach
</select>
</div>
<div class="space-y-2 max-h-64 overflow-y-auto">
@forelse($programs as $program)
<label class="flex items-center gap-3 p-3 border rounded-lg cursor-pointer transition-all
{{ $programId === $program->id ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-gray-300' }}">
<input type="radio" wire:model="programId" value="{{ $program->id }}" class="text-blue-600 focus:ring-blue-500">
<div>
<span class="text-sm font-medium text-gray-800">{{ $program->name_ar }}</span>
@if($program->name)
<span class="text-xs text-gray-500 ms-2" dir="ltr">{{ $program->name }}</span>
@endif
</div>
</label>
@empty
<div class="text-center py-6 text-sm text-gray-500">{{ __('لا توجد برامج متاحة') }}</div>
@endforelse
</div>
@error('programId') <p class="mt-2 text-xs text-red-600">{{ $message }}</p> @enderror
@endif
{{-- Step 2: Group Info --}}
@if($currentStep === 2)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('بيانات المجموعة') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<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="nameAr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('nameAr') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('اسم المجموعة بالإنجليزي') }}</label>
<input type="text" wire:model="name" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('كود المجموعة') }} <span class="text-red-500">*</span></label>
<input type="text" wire:model="code" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm" placeholder="GRP-001">
@error('code') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الموسم') }}</label>
<input type="text" wire:model="season" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm" placeholder="{{ __('مثال: صيف 2026') }}">
</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="startDate" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('startDate') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تاريخ الانتهاء') }}</label>
<input type="date" wire:model="endDate" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('endDate') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('ملاحظات') }}</label>
<textarea wire:model="notes" rows="2"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"></textarea>
</div>
</div>
@endif
{{-- Step 3: Capacity & Trainer --}}
@if($currentStep === 3)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('السعة والمدرب') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('السعة القصوى') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="maxCapacity" dir="ltr" min="1" max="500"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('maxCapacity') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
@if($selectedProgram && $selectedProgram->max_participants)
<p class="mt-1 text-xs text-gray-500">{{ __('الحد الأقصى للبرنامج:') }} {{ $selectedProgram->max_participants }}</p>
@endif
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('المدرب الرئيسي') }}</label>
<select wire:model="headTrainerId" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('اختر مدرب...') }}</option>
@foreach($trainers as $trainer)
<option value="{{ $trainer->employee?->user_id }}">{{ $trainer->employee?->person?->name_ar ?? $trainer->trainer_number }}</option>
@endforeach
</select>
</div>
</div>
@endif
{{-- Step 4: Review --}}
@if($currentStep === 4 && !$completed)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('مراجعة وتأكيد') }}</h2>
<dl class="space-y-3">
@if($selectedProgram)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('البرنامج') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $selectedProgram->name_ar }}</dd>
</div>
@endif
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('اسم المجموعة') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $nameAr }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الكود') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ $code }}</dd>
</div>
@if($season)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الموسم') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $season }}</dd>
</div>
@endif
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('تاريخ البدء') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ $startDate }}</dd>
</div>
@if($endDate)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('تاريخ الانتهاء') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ $endDate }}</dd>
</div>
@endif
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('السعة القصوى') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $maxCapacity }} {{ __('مشترك') }}</dd>
</div>
</dl>
@endif
{{-- Success State --}}
@if($completed)
<div class="text-center py-8">
<div class="mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-green-100 mb-4">
<svg class="w-8 h-8 text-green-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
</svg>
</div>
<h3 class="text-lg font-semibold text-gray-800 mb-2">{{ __('تم إنشاء المجموعة التدريبية بنجاح') }}</h3>
<p class="text-sm text-gray-600 mb-6">{{ __('يمكنك الآن إضافة جداول الحصص أو إنشاء مجموعة أخرى') }}</p>
<div class="flex items-center justify-center gap-3">
<a href="{{ route('groups.index') }}" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors">
{{ __('قائمة المجموعات') }}
</a>
<button wire:click="$refresh" onclick="window.location.reload()" class="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
{{ __('إنشاء مجموعة أخرى') }}
</button>
</div>
</div>
@endif
</div>
{{-- Navigation Buttons --}}
@if(!$completed)
<div class="flex items-center justify-between mt-6">
<div>
@if($currentStep > 1)
<button wire:click="previousStep" class="px-5 py-2.5 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors">
{{ __('السابق') }}
</button>
@endif
</div>
<div>
@if($currentStep < $totalSteps)
<button wire:click="nextStep" class="px-5 py-2.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
{{ __('التالي') }}
</button>
@elseif($currentStep === $totalSteps)
<button wire:click="confirm" wire:loading.attr="disabled" wire:target="confirm"
class="px-5 py-2.5 bg-green-600 text-white rounded-lg text-sm font-medium hover:bg-green-700 transition-colors disabled:opacity-50">
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد') }}</span>
<span wire:loading wire:target="confirm">{{ __('جارٍ الحفظ...') }}</span>
</button>
@endif
</div>
</div>
@endif
</div>
<div>
<div class="flex items-center justify-between mb-6">
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('إضافة موظف جديد') }}</h1>
</div>
{{-- Step Indicator --}}
<div class="flex items-center justify-center gap-2 mb-8">
@foreach($stepLabels as $num => $label)
<button wire:click="goToStep({{ $num }})"
class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<span class="flex items-center justify-center w-8 h-8 rounded-full text-sm font-bold
{{ $num < $currentStep ? 'bg-green-500 text-white' : ($num === $currentStep ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-500') }}">
@if($num < $currentStep)
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
@else
{{ $num }}
@endif
</span>
<span class="hidden sm:inline text-sm {{ $num === $currentStep ? 'text-blue-700 font-medium' : 'text-gray-500' }}">{{ $label }}</span>
</button>
@if(!$loop->last)
<div class="w-8 h-0.5 {{ $num < $currentStep ? 'bg-green-400' : 'bg-gray-200' }}"></div>
@endif
@endforeach
</div>
@if(session('error'))
<div class="mb-4 p-3 rounded-lg bg-red-50 border border-red-200 text-red-700 text-sm">{{ session('error') }}</div>
@endif
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
{{-- Step 1: Person --}}
@if($currentStep === 1)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('بيانات الشخص') }}</h2>
@if(!$createNewPerson && !$personId)
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('بحث عن شخص موجود') }}</label>
<input type="text" wire:model.live.debounce.300ms="personSearch"
placeholder="{{ __('ابحث بالاسم أو رقم الهاتف أو الرقم القومي...') }}"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
</div>
@if($this->personResults->isNotEmpty())
<div class="border border-gray-200 rounded-lg divide-y divide-gray-100 mb-4 max-h-48 overflow-y-auto">
@foreach($this->personResults as $person)
<button wire:click="selectPerson({{ $person->id }})"
class="w-full text-start px-4 py-3 hover:bg-blue-50 transition-colors">
<div class="font-medium text-gray-800">{{ $person->name_ar ?? $person->name }}</div>
<div class="text-xs text-gray-500">{{ $person->phone }} @if($person->national_id) - {{ $person->national_id }} @endif</div>
</button>
@endforeach
</div>
@endif
<button wire:click="toggleCreateNew" class="text-sm text-blue-600 hover:text-blue-800 font-medium">
{{ __('+ إنشاء شخص جديد') }}
</button>
@endif
@if($personId && !$createNewPerson)
<div class="p-4 bg-green-50 border border-green-200 rounded-lg mb-4">
<div class="flex items-center justify-between">
<div>
<p class="font-medium text-green-800">{{ $personNameAr }}</p>
<p class="text-sm text-green-600">{{ $personPhone }}</p>
</div>
<button wire:click="$set('personId', null)" class="text-sm text-red-600 hover:text-red-800">{{ __('تغيير') }}</button>
</div>
</div>
@endif
@if($createNewPerson)
<div class="space-y-4 border border-gray-200 rounded-lg p-4">
<div class="flex items-center justify-between mb-2">
<h3 class="text-sm font-medium text-gray-700">{{ __('بيانات الشخص الجديد') }}</h3>
<button wire:click="toggleCreateNew" class="text-sm text-red-600 hover:text-red-800">{{ __('إلغاء') }}</button>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<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="personNameAr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('personNameAr') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الاسم بالإنجليزي') }}</label>
<input type="text" wire:model="personName" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('رقم الهاتف') }} <span class="text-red-500">*</span></label>
<input type="text" wire:model="personPhone" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('personPhone') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الرقم القومي') }}</label>
<input type="text" wire:model="personNationalId" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تاريخ الميلاد') }}</label>
<input type="date" wire:model="personDateOfBirth" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الجنس') }} <span class="text-red-500">*</span></label>
<select wire:model="personGender" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('اختر...') }}</option>
<option value="male">{{ __('ذكر') }}</option>
<option value="female">{{ __('أنثى') }}</option>
</select>
@error('personGender') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
</div>
</div>
@endif
@error('personId') <p class="mt-2 text-xs text-red-600">{{ $message }}</p> @enderror
@endif
{{-- Step 2: Employment --}}
@if($currentStep === 2)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('بيانات التوظيف') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<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="department" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('department') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('المسمى الوظيفي') }} <span class="text-red-500">*</span></label>
<input type="text" wire:model="position" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('position') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نوع التوظيف') }} <span class="text-red-500">*</span></label>
<select wire:model="employmentType" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('اختر...') }}</option>
@foreach($employmentTypes as $type)
<option value="{{ $type->value }}">{{ $type->label() }}</option>
@endforeach
</select>
@error('employmentType') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تاريخ البدء') }} <span class="text-red-500">*</span></label>
<input type="date" wire:model="startDate" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('startDate') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تاريخ الانتهاء') }}</label>
<input type="date" wire:model="endDate" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('endDate') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
</div>
@endif
{{-- Step 3: Branch & Manager --}}
@if($currentStep === 3)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('الفرع والمسؤول') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الفرع') }} <span class="text-red-500">*</span></label>
<select wire:model="branchId" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('اختر الفرع...') }}</option>
@foreach($branches as $branch)
<option value="{{ $branch->id }}">{{ $branch->name_ar }}</option>
@endforeach
</select>
@error('branchId') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('المسؤول المباشر') }}</label>
<select wire:model="managerId" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('بدون مسؤول') }}</option>
@foreach($employees as $emp)
<option value="{{ $emp->id }}">{{ $emp->person?->name_ar ?? $emp->employee_number }}</option>
@endforeach
</select>
</div>
</div>
@endif
{{-- Step 4: Salary --}}
@if($currentStep === 4)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('الراتب') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('مبلغ الراتب') }} <span class="text-red-500">*</span></label>
<div class="relative">
<input type="number" wire:model="salaryAmount" dir="ltr" step="0.01" min="0"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm pe-12">
<span class="absolute end-3 top-1/2 -translate-y-1/2 text-sm text-gray-500">{{ __('ج.م') }}</span>
</div>
@error('salaryAmount') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('دورية الراتب') }} <span class="text-red-500">*</span></label>
<select wire:model="salaryFrequency" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('اختر...') }}</option>
@foreach($salaryFrequencies as $freq)
<option value="{{ $freq->value }}">{{ $freq->label() }}</option>
@endforeach
</select>
@error('salaryFrequency') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('ساعات العمل الأسبوعية') }}</label>
<input type="number" wire:model="workingHoursPerWeek" dir="ltr" step="0.5" min="1" max="168"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('workingHoursPerWeek') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
</div>
@endif
{{-- Step 5: Review --}}
@if($currentStep === 5 && !$completed)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('مراجعة وتأكيد') }}</h2>
<dl class="space-y-3">
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الاسم') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $personNameAr }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الهاتف') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ $personPhone }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('القسم') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $department }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('المسمى الوظيفي') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $position }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('نوع التوظيف') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ \App\Domain\HR\Enums\EmploymentType::from($employmentType)->label() }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('تاريخ البدء') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ $startDate }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الراتب') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ number_format((float)$salaryAmount, 2) }} {{ __('ج.م') }} / {{ \App\Domain\HR\Enums\SalaryFrequency::from($salaryFrequency)->label() }}</dd>
</div>
@if($workingHoursPerWeek)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('ساعات العمل') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $workingHoursPerWeek }} {{ __('ساعة/أسبوع') }}</dd>
</div>
@endif
</dl>
@endif
{{-- Success State --}}
@if($completed)
<div class="text-center py-8">
<div class="mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-green-100 mb-4">
<svg class="w-8 h-8 text-green-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
</svg>
</div>
<h3 class="text-lg font-semibold text-gray-800 mb-2">{{ __('تم إضافة الموظف بنجاح') }}</h3>
<p class="text-sm text-gray-600 mb-6">{{ __('يمكنك الآن عرض بيانات الموظف أو إضافة موظف آخر') }}</p>
<div class="flex items-center justify-center gap-3">
<a href="{{ route('employees.index') }}" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors">
{{ __('قائمة الموظفين') }}
</a>
<button wire:click="$refresh" onclick="window.location.reload()" class="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
{{ __('إضافة موظف آخر') }}
</button>
</div>
</div>
@endif
</div>
{{-- Navigation Buttons --}}
@if(!$completed)
<div class="flex items-center justify-between mt-6">
<div>
@if($currentStep > 1)
<button wire:click="previousStep" class="px-5 py-2.5 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors">
{{ __('السابق') }}
</button>
@endif
</div>
<div>
@if($currentStep < $totalSteps)
<button wire:click="nextStep" class="px-5 py-2.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
{{ __('التالي') }}
</button>
@elseif($currentStep === $totalSteps)
<button wire:click="confirm" wire:loading.attr="disabled" wire:target="confirm"
class="px-5 py-2.5 bg-green-600 text-white rounded-lg text-sm font-medium hover:bg-green-700 transition-colors disabled:opacity-50">
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد') }}</span>
<span wire:loading wire:target="confirm">{{ __('جارٍ الحفظ...') }}</span>
</button>
@endif
</div>
</div>
@endif
</div>
<div>
<div class="flex items-center justify-between mb-6">
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('إضافة مدرب جديد') }}</h1>
</div>
{{-- Step Indicator --}}
<div class="flex items-center justify-center gap-2 mb-8">
@foreach($stepLabels as $num => $label)
<button wire:click="goToStep({{ $num }})"
class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<span class="flex items-center justify-center w-8 h-8 rounded-full text-sm font-bold
{{ $num < $currentStep ? 'bg-green-500 text-white' : ($num === $currentStep ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-500') }}">
@if($num < $currentStep)
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
@else
{{ $num }}
@endif
</span>
<span class="hidden sm:inline text-sm {{ $num === $currentStep ? 'text-blue-700 font-medium' : 'text-gray-500' }}">{{ $label }}</span>
</button>
@if(!$loop->last)
<div class="w-8 h-0.5 {{ $num < $currentStep ? 'bg-green-400' : 'bg-gray-200' }}"></div>
@endif
@endforeach
</div>
@if(session('error'))
<div class="mb-4 p-3 rounded-lg bg-red-50 border border-red-200 text-red-700 text-sm">{{ session('error') }}</div>
@endif
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
{{-- Step 1: Person --}}
@if($currentStep === 1)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('بيانات الشخص') }}</h2>
@if(!$createNewPerson && !$personId)
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('بحث عن شخص موجود') }}</label>
<input type="text" wire:model.live.debounce.300ms="personSearch"
placeholder="{{ __('ابحث بالاسم أو رقم الهاتف أو الرقم القومي...') }}"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
</div>
@if($this->personResults->isNotEmpty())
<div class="border border-gray-200 rounded-lg divide-y divide-gray-100 mb-4 max-h-48 overflow-y-auto">
@foreach($this->personResults as $person)
<button wire:click="selectPerson({{ $person->id }})"
class="w-full text-start px-4 py-3 hover:bg-blue-50 transition-colors">
<div class="font-medium text-gray-800">{{ $person->name_ar ?? $person->name }}</div>
<div class="text-xs text-gray-500">{{ $person->phone }} @if($person->national_id) - {{ $person->national_id }} @endif</div>
</button>
@endforeach
</div>
@endif
<button wire:click="toggleCreateNew" class="text-sm text-blue-600 hover:text-blue-800 font-medium">
{{ __('+ إنشاء شخص جديد') }}
</button>
@endif
@if($personId && !$createNewPerson)
<div class="p-4 bg-green-50 border border-green-200 rounded-lg mb-4">
<div class="flex items-center justify-between">
<div>
<p class="font-medium text-green-800">{{ $personNameAr }}</p>
<p class="text-sm text-green-600">{{ $personPhone }}</p>
</div>
<button wire:click="$set('personId', null)" class="text-sm text-red-600 hover:text-red-800">{{ __('تغيير') }}</button>
</div>
</div>
@endif
@if($createNewPerson)
<div class="space-y-4 border border-gray-200 rounded-lg p-4">
<div class="flex items-center justify-between mb-2">
<h3 class="text-sm font-medium text-gray-700">{{ __('بيانات الشخص الجديد') }}</h3>
<button wire:click="toggleCreateNew" class="text-sm text-red-600 hover:text-red-800">{{ __('إلغاء') }}</button>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<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="personNameAr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('personNameAr') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الاسم بالإنجليزي') }}</label>
<input type="text" wire:model="personName" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('رقم الهاتف') }} <span class="text-red-500">*</span></label>
<input type="text" wire:model="personPhone" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('personPhone') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الرقم القومي') }}</label>
<input type="text" wire:model="personNationalId" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تاريخ الميلاد') }}</label>
<input type="date" wire:model="personDateOfBirth" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الجنس') }} <span class="text-red-500">*</span></label>
<select wire:model="personGender" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('اختر...') }}</option>
<option value="male">{{ __('ذكر') }}</option>
<option value="female">{{ __('أنثى') }}</option>
</select>
@error('personGender') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
</div>
</div>
@endif
@error('personId') <p class="mt-2 text-xs text-red-600">{{ $message }}</p> @enderror
@endif
{{-- Step 2: Employment --}}
@if($currentStep === 2)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('بيانات التوظيف') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<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="department" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('department') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('المسمى الوظيفي') }} <span class="text-red-500">*</span></label>
<input type="text" wire:model="position" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('position') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نوع التوظيف') }} <span class="text-red-500">*</span></label>
<select wire:model="employmentType" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('اختر...') }}</option>
@foreach($employmentTypes as $type)
<option value="{{ $type->value }}">{{ $type->label() }}</option>
@endforeach
</select>
@error('employmentType') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تاريخ البدء') }} <span class="text-red-500">*</span></label>
<input type="date" wire:model="startDate" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('startDate') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الفرع') }} <span class="text-red-500">*</span></label>
<select wire:model="branchId" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('اختر الفرع...') }}</option>
@foreach($branches as $branch)
<option value="{{ $branch->id }}">{{ $branch->name_ar }}</option>
@endforeach
</select>
@error('branchId') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
</div>
@endif
{{-- Step 3: Compensation --}}
@if($currentStep === 3)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('التعويض') }}</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نموذج التعويض') }} <span class="text-red-500">*</span></label>
<select wire:model="compensationModel" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('اختر...') }}</option>
@foreach($compensationModels as $model)
<option value="{{ $model->value }}">{{ $model->label() }}</option>
@endforeach
</select>
@error('compensationModel') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
@if(in_array($compensationModel, ['hourly', 'hybrid']))
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('سعر الساعة') }}</label>
<div class="relative">
<input type="number" wire:model="hourlyRate" dir="ltr" step="0.01" min="0"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm pe-12">
<span class="absolute end-3 top-1/2 -translate-y-1/2 text-sm text-gray-500">{{ __('ج.م') }}</span>
</div>
</div>
@endif
@if(in_array($compensationModel, ['per_session', 'hybrid']))
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('سعر الحصة') }}</label>
<div class="relative">
<input type="number" wire:model="sessionRate" dir="ltr" step="0.01" min="0"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm pe-12">
<span class="absolute end-3 top-1/2 -translate-y-1/2 text-sm text-gray-500">{{ __('ج.م') }}</span>
</div>
</div>
@endif
@if(in_array($compensationModel, ['per_group', 'hybrid']))
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('سعر المجموعة') }}</label>
<div class="relative">
<input type="number" wire:model="groupRate" dir="ltr" step="0.01" min="0"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm pe-12">
<span class="absolute end-3 top-1/2 -translate-y-1/2 text-sm text-gray-500">{{ __('ج.م') }}</span>
</div>
</div>
@endif
@if(in_array($compensationModel, ['per_player', 'hybrid']))
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('سعر اللاعب') }}</label>
<div class="relative">
<input type="number" wire:model="playerRate" dir="ltr" step="0.01" min="0"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm pe-12">
<span class="absolute end-3 top-1/2 -translate-y-1/2 text-sm text-gray-500">{{ __('ج.م') }}</span>
</div>
</div>
@endif
@if(in_array($compensationModel, ['revenue_share', 'hybrid']))
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نسبة الإيرادات') }}</label>
<div class="relative">
<input type="number" wire:model="revenueSharePercent" dir="ltr" step="0.1" min="0" max="100"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm pe-8">
<span class="absolute end-3 top-1/2 -translate-y-1/2 text-sm text-gray-500">%</span>
</div>
</div>
@endif
</div>
@endif
{{-- Step 4: Availability --}}
@if($currentStep === 4)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('أوقات التوفر') }}</h2>
<div class="space-y-3">
@foreach($availabilities as $index => $day)
<div class="flex items-center gap-3 p-3 border border-gray-200 rounded-lg {{ $day['enabled'] ? 'bg-blue-50 border-blue-200' : '' }}">
<label class="flex items-center gap-2 min-w-[100px]">
<input type="checkbox" wire:model="availabilities.{{ $index }}.enabled"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<span class="text-sm font-medium text-gray-700">{{ $day['day_name'] }}</span>
</label>
@if($day['enabled'])
<input type="time" wire:model="availabilities.{{ $index }}.start_time" dir="ltr"
class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<span class="text-sm text-gray-500">{{ __('إلى') }}</span>
<input type="time" wire:model="availabilities.{{ $index }}.end_time" dir="ltr"
class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<select wire:model="availabilities.{{ $index }}.type"
class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
@foreach($availabilityTypes as $type)
<option value="{{ $type->value }}">{{ $type->label() }}</option>
@endforeach
</select>
@endif
</div>
@endforeach
</div>
@endif
{{-- Step 5: Qualifications --}}
@if($currentStep === 5)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('المؤهلات والشهادات') }}</h2>
<div class="space-y-4">
@foreach($qualifications as $index => $qual)
<div class="p-4 border border-gray-200 rounded-lg">
<div class="flex items-center justify-between mb-3">
<span class="text-sm font-medium text-gray-700">{{ __('شهادة') }} {{ $index + 1 }}</span>
@if(count($qualifications) > 1)
<button wire:click="removeQualification({{ $index }})" class="text-sm text-red-600 hover:text-red-800">{{ __('حذف') }}</button>
@endif
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('اسم الشهادة') }}</label>
<input type="text" wire:model="qualifications.{{ $index }}.name"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('جهة الإصدار') }}</label>
<input type="text" wire:model="qualifications.{{ $index }}.issuer"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('تاريخ الإصدار') }}</label>
<input type="date" wire:model="qualifications.{{ $index }}.issue_date" dir="ltr"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('تاريخ الانتهاء') }}</label>
<input type="date" wire:model="qualifications.{{ $index }}.expiry_date" dir="ltr"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
</div>
</div>
@endforeach
<button wire:click="addQualification" class="w-full py-2 border-2 border-dashed border-gray-300 rounded-lg text-sm text-gray-500 hover:border-blue-400 hover:text-blue-600 transition-colors">
{{ __('+ إضافة شهادة أخرى') }}
</button>
</div>
@endif
{{-- Step 6: Review --}}
@if($currentStep === 6 && !$completed)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('مراجعة وتأكيد') }}</h2>
<dl class="space-y-3">
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الاسم') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $personNameAr }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الهاتف') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ $personPhone }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('المسمى الوظيفي') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $position }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('نوع التوظيف') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ \App\Domain\HR\Enums\EmploymentType::from($employmentType)->label() }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('نموذج التعويض') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ \App\Domain\HR\Enums\CompensationModel::from($compensationModel)->label() }}</dd>
</div>
@if($hourlyRate)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('سعر الساعة') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ number_format((float)$hourlyRate, 2) }} {{ __('ج.م') }}</dd>
</div>
@endif
@if($sessionRate)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('سعر الحصة') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ number_format((float)$sessionRate, 2) }} {{ __('ج.م') }}</dd>
</div>
@endif
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('أيام التوفر') }}</dt>
<dd class="text-sm font-medium text-gray-800">
{{ collect($availabilities)->filter(fn($a) => $a['enabled'])->pluck('day_name')->join('، ') ?: __('لم يتم التحديد') }}
</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('المؤهلات') }}</dt>
<dd class="text-sm font-medium text-gray-800">
{{ collect($qualifications)->filter(fn($q) => !empty($q['name']))->count() }} {{ __('شهادة') }}
</dd>
</div>
</dl>
@endif
{{-- Success State --}}
@if($completed)
<div class="text-center py-8">
<div class="mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-green-100 mb-4">
<svg class="w-8 h-8 text-green-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
</svg>
</div>
<h3 class="text-lg font-semibold text-gray-800 mb-2">{{ __('تم إضافة المدرب بنجاح') }}</h3>
<p class="text-sm text-gray-600 mb-6">{{ __('يمكنك الآن عرض بيانات المدرب أو إضافة مدرب آخر') }}</p>
<div class="flex items-center justify-center gap-3">
<a href="{{ route('trainers.index') }}" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors">
{{ __('قائمة المدربين') }}
</a>
<button wire:click="$refresh" onclick="window.location.reload()" class="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
{{ __('إضافة مدرب آخر') }}
</button>
</div>
</div>
@endif
</div>
{{-- Navigation Buttons --}}
@if(!$completed)
<div class="flex items-center justify-between mt-6">
<div>
@if($currentStep > 1)
<button wire:click="previousStep" class="px-5 py-2.5 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors">
{{ __('السابق') }}
</button>
@endif
</div>
<div>
@if($currentStep < $totalSteps)
<button wire:click="nextStep" class="px-5 py-2.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
{{ __('التالي') }}
</button>
@elseif($currentStep === $totalSteps)
<button wire:click="confirm" wire:loading.attr="disabled" wire:target="confirm"
class="px-5 py-2.5 bg-green-600 text-white rounded-lg text-sm font-medium hover:bg-green-700 transition-colors disabled:opacity-50">
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد') }}</span>
<span wire:loading wire:target="confirm">{{ __('جارٍ الحفظ...') }}</span>
</button>
@endif
</div>
</div>
@endif
</div>
<div>
{{-- Header --}}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('إنشاء منتج جديد') }}</h1>
<p class="text-sm text-gray-500 mt-1">{{ __('إضافة منتج جديد لنظام المخزون') }}</p>
</div>
</div>
{{-- Flash Messages --}}
@if(session('error'))
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-xl text-red-700 text-sm">
{{ session('error') }}
</div>
@endif
{{-- Step Indicator --}}
@if(!$completed)
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6">
<div class="flex items-center justify-between">
@php $steps = $this->getStepLabels(); @endphp
@foreach($steps as $num => $label)
<div class="flex items-center {{ !$loop->last ? 'flex-1' : '' }}">
<button wire:click="goToStep({{ $num }})"
@if($num >= $currentStep) disabled @endif
class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold transition-colors
{{ $num === $currentStep ? 'bg-blue-600 text-white' : '' }}
{{ $num < $currentStep ? 'bg-green-500 text-white' : '' }}
{{ $num > $currentStep ? 'bg-gray-200 text-gray-500' : '' }}">
@if($num < $currentStep)
<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>
@else
{{ $num }}
@endif
</div>
<span class="text-xs font-medium hidden sm:inline
{{ $num === $currentStep ? 'text-blue-600' : '' }}
{{ $num < $currentStep ? 'text-green-600' : '' }}
{{ $num > $currentStep ? 'text-gray-400' : '' }}">
{{ __($label) }}
</span>
</button>
@if(!$loop->last)
<div class="flex-1 h-0.5 mx-3 {{ $num < $currentStep ? 'bg-green-500' : 'bg-gray-200' }}"></div>
@endif
</div>
@endforeach
</div>
</div>
@endif
{{-- Step Content --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6">
{{-- Step 1: Basic Info --}}
@if($currentStep === 1)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('المعلومات الأساسية') }}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{{-- Name Arabic --}}
<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="nameAr"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="{{ __('مثال: كرة قدم') }}">
@error('nameAr') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Name English --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('اسم المنتج بالإنجليزية') }}</label>
<input type="text" wire:model="name" dir="ltr"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Football">
@error('name') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- SKU --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('رمز المنتج (SKU)') }}</label>
<input type="text" wire:model="sku" dir="ltr"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="PRD-001">
@error('sku') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Barcode --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الباركود') }}</label>
<input type="text" wire:model="barcode" dir="ltr"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="1234567890123">
@error('barcode') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Category --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('التصنيف') }}</label>
<select wire:model="categoryId"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="">{{ __('-- اختر التصنيف --') }}</option>
@foreach($categories as $category)
<option value="{{ $category['id'] }}">{{ $category['name_ar'] }}</option>
@endforeach
</select>
@error('categoryId') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Type --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نوع المنتج') }} <span class="text-red-500">*</span></label>
<select wire:model="type"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="physical">{{ __('مادي') }}</option>
<option value="digital">{{ __('رقمي') }}</option>
<option value="service">{{ __('خدمة') }}</option>
</select>
@error('type') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Description --}}
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الوصف') }}</label>
<textarea wire:model="descriptionAr" rows="3"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="{{ __('وصف مختصر للمنتج...') }}"></textarea>
@error('descriptionAr') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
</div>
</div>
@endif
{{-- Step 2: Pricing --}}
@if($currentStep === 2)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('التسعير') }}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{{-- Selling Price --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('سعر البيع') }} <span class="text-red-500">*</span></label>
<div class="relative">
<input type="number" wire:model="sellingPrice" dir="ltr" step="0.01" min="0"
class="w-full px-4 py-2.5 pe-12 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="0.00">
<span class="absolute end-3 top-1/2 -translate-y-1/2 text-sm text-gray-500">{{ __('ج.م') }}</span>
</div>
@error('sellingPrice') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Cost Price --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('سعر التكلفة') }}</label>
<div class="relative">
<input type="number" wire:model="costPrice" dir="ltr" step="0.01" min="0"
class="w-full px-4 py-2.5 pe-12 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="0.00">
<span class="absolute end-3 top-1/2 -translate-y-1/2 text-sm text-gray-500">{{ __('ج.م') }}</span>
</div>
@error('costPrice') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Tax Rate --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نسبة الضريبة') }}</label>
<div class="relative">
<input type="number" wire:model="taxRate" dir="ltr" step="1" min="0" max="100"
class="w-full px-4 py-2.5 pe-12 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="0">
<span class="absolute end-3 top-1/2 -translate-y-1/2 text-sm text-gray-500">%</span>
</div>
@error('taxRate') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Profit Preview --}}
@if($sellingPrice && $costPrice)
<div class="flex items-end">
@php
$profit = (float)$sellingPrice - (float)$costPrice;
$margin = (float)$sellingPrice > 0 ? ($profit / (float)$sellingPrice) * 100 : 0;
@endphp
<div class="w-full p-3 bg-gray-50 rounded-lg">
<p class="text-xs text-gray-500 mb-1">{{ __('هامش الربح') }}</p>
<p class="text-lg font-bold {{ $profit >= 0 ? 'text-green-600' : 'text-red-600' }}" dir="ltr">
{{ number_format($profit, 2) }} {{ __('ج.م') }}
<span class="text-sm font-normal">({{ number_format($margin, 1) }}%)</span>
</p>
</div>
</div>
@endif
</div>
</div>
@endif
{{-- Step 3: Inventory Settings --}}
@if($currentStep === 3)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('إعدادات المخزون') }}</h2>
<div class="space-y-6">
{{-- Track Inventory --}}
<div class="flex items-start gap-3">
<input type="checkbox" wire:model="trackInventory" id="trackInventory"
class="w-5 h-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500 mt-0.5">
<div>
<label for="trackInventory" class="text-sm font-medium text-gray-700 cursor-pointer">{{ __('تتبع المخزون') }}</label>
<p class="text-xs text-gray-500 mt-0.5">{{ __('تفعيل تتبع الكمية المتوفرة والحركات') }}</p>
</div>
</div>
@if($trackInventory)
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 ps-8">
{{-- Min Stock Level --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الحد الأدنى للمخزون') }}</label>
<input type="number" wire:model="minStockLevel" dir="ltr" min="0"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="10">
<p class="text-xs text-gray-500 mt-1">{{ __('يتم التنبيه عند وصول المخزون لهذا الحد') }}</p>
@error('minStockLevel') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Max Stock Level --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الحد الأقصى للمخزون') }}</label>
<input type="number" wire:model="maxStockLevel" dir="ltr" min="0"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="100">
@error('maxStockLevel') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
</div>
@endif
{{-- Is Active --}}
<div class="flex items-start gap-3">
<input type="checkbox" wire:model="isActive" id="isActive"
class="w-5 h-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500 mt-0.5">
<div>
<label for="isActive" class="text-sm font-medium text-gray-700 cursor-pointer">{{ __('منتج نشط') }}</label>
<p class="text-xs text-gray-500 mt-0.5">{{ __('المنتجات غير النشطة لن تظهر في نقطة البيع') }}</p>
</div>
</div>
</div>
</div>
@endif
{{-- Step 4: Review --}}
@if($currentStep === 4 && !$completed)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('مراجعة بيانات المنتج') }}</h2>
<div class="space-y-4">
{{-- Basic Info --}}
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="text-sm font-semibold text-gray-600 mb-3">{{ __('المعلومات الأساسية') }}</h3>
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
<div>
<dt class="text-gray-500">{{ __('الاسم بالعربية') }}</dt>
<dd class="font-medium text-gray-800">{{ $nameAr }}</dd>
</div>
@if($name)
<div>
<dt class="text-gray-500">{{ __('الاسم بالإنجليزية') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ $name }}</dd>
</div>
@endif
@if($sku)
<div>
<dt class="text-gray-500">{{ __('رمز المنتج') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ $sku }}</dd>
</div>
@endif
@if($barcode)
<div>
<dt class="text-gray-500">{{ __('الباركود') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ $barcode }}</dd>
</div>
@endif
<div>
<dt class="text-gray-500">{{ __('النوع') }}</dt>
<dd class="font-medium text-gray-800">
@php
$typeLabels = ['physical' => 'مادي', 'digital' => 'رقمي', 'service' => 'خدمة'];
@endphp
{{ $typeLabels[$type] ?? $type }}
</dd>
</div>
@if($categoryId)
<div>
<dt class="text-gray-500">{{ __('التصنيف') }}</dt>
<dd class="font-medium text-gray-800">{{ collect($categories)->firstWhere('id', $categoryId)['name_ar'] ?? '-' }}</dd>
</div>
@endif
</dl>
</div>
{{-- Pricing --}}
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="text-sm font-semibold text-gray-600 mb-3">{{ __('التسعير') }}</h3>
<dl class="grid grid-cols-1 sm:grid-cols-3 gap-3 text-sm">
<div>
<dt class="text-gray-500">{{ __('سعر البيع') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ number_format((float)$sellingPrice, 2) }} {{ __('ج.م') }}</dd>
</div>
@if($costPrice)
<div>
<dt class="text-gray-500">{{ __('سعر التكلفة') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ number_format((float)$costPrice, 2) }} {{ __('ج.م') }}</dd>
</div>
@endif
@if($taxRate > 0)
<div>
<dt class="text-gray-500">{{ __('نسبة الضريبة') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ $taxRate }}%</dd>
</div>
@endif
</dl>
</div>
{{-- Inventory Settings --}}
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="text-sm font-semibold text-gray-600 mb-3">{{ __('إعدادات المخزون') }}</h3>
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
<div>
<dt class="text-gray-500">{{ __('تتبع المخزون') }}</dt>
<dd class="font-medium text-gray-800">
@if($trackInventory)
<span class="px-2 py-0.5 bg-green-100 text-green-700 rounded text-xs">{{ __('مفعل') }}</span>
@else
<span class="px-2 py-0.5 bg-gray-100 text-gray-600 rounded text-xs">{{ __('غير مفعل') }}</span>
@endif
</dd>
</div>
<div>
<dt class="text-gray-500">{{ __('الحالة') }}</dt>
<dd class="font-medium text-gray-800">
@if($isActive)
<span class="px-2 py-0.5 bg-green-100 text-green-700 rounded text-xs">{{ __('نشط') }}</span>
@else
<span class="px-2 py-0.5 bg-red-100 text-red-700 rounded text-xs">{{ __('غير نشط') }}</span>
@endif
</dd>
</div>
@if($trackInventory && $minStockLevel)
<div>
<dt class="text-gray-500">{{ __('الحد الأدنى') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ $minStockLevel }}</dd>
</div>
@endif
@if($trackInventory && $maxStockLevel)
<div>
<dt class="text-gray-500">{{ __('الحد الأقصى') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ $maxStockLevel }}</dd>
</div>
@endif
</dl>
</div>
</div>
</div>
@endif
{{-- Success State --}}
@if($completed)
<div class="text-center py-8">
<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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
</div>
<h2 class="text-xl font-bold text-gray-800 mb-2">{{ __('تم إنشاء المنتج بنجاح') }}</h2>
<p class="text-gray-500 mb-6">{{ __('يمكنك الآن إدارة المنتج وإضافة مخزون أولي') }}</p>
</div>
@endif
{{-- Navigation Buttons --}}
@if(!$completed)
<div class="flex items-center justify-between mt-8 pt-4 border-t border-gray-200">
@if($currentStep > 1)
<button wire:click="previousStep" type="button"
class="inline-flex items-center gap-2 px-5 py-2.5 text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 font-medium text-sm transition-colors">
<svg class="w-4 h-4 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>
@else
<div></div>
@endif
@if($currentStep < $totalSteps)
<button wire:click="nextStep" type="button"
wire:loading.attr="disabled" wire:target="nextStep"
class="inline-flex items-center gap-2 px-5 py-2.5 text-white bg-blue-600 rounded-lg hover:bg-blue-700 font-medium text-sm transition-colors">
<span wire:loading.remove wire:target="nextStep">{{ __('التالي') }}</span>
<span wire:loading wire:target="nextStep">{{ __('جارٍ التحقق...') }}</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
</button>
@else
<button wire:click="confirm" type="button"
wire:loading.attr="disabled" wire:target="confirm"
class="inline-flex items-center gap-2 px-6 py-2.5 text-white bg-green-600 rounded-lg hover:bg-green-700 font-medium text-sm transition-colors">
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد إنشاء المنتج') }}</span>
<span wire:loading wire:target="confirm">{{ __('جارٍ الحفظ...') }}</span>
</button>
@endif
</div>
@endif
</div>
</div>
<div>
{{-- Header --}}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('تسوية المخزون') }}</h1>
<p class="text-sm text-gray-500 mt-1">{{ __('تعديل كميات المخزون بالزيادة أو النقص') }}</p>
</div>
</div>
{{-- Flash Messages --}}
@if(session('error'))
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-xl text-red-700 text-sm">
{{ session('error') }}
</div>
@endif
{{-- Success State --}}
@if($completed)
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-8 text-center">
<div class="w-16 h-16 mx-auto mb-4 bg-green-100 rounded-full flex items-center justify-center">
<svg class="w-8 h-8 text-green-600" 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>
<h2 class="text-xl font-bold text-gray-800 mb-2">{{ __('تمت التسوية بنجاح') }}</h2>
<p class="text-gray-500 mb-6">{{ __('تم تعديل المخزون وتسجيل الحركة بنجاح') }}</p>
<a href="{{ route('inventory.levels.index') }}" wire:navigate
class="inline-flex items-center gap-2 px-6 py-3 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 font-medium transition-colors">
{{ __('العودة لمستويات المخزون') }}
</a>
</div>
@else
{{-- Step Indicator --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6">
<div class="flex items-center justify-between">
@foreach($this->getStepLabels() as $num => $label)
<div class="flex items-center {{ !$loop->last ? 'flex-1' : '' }}">
<button wire:click="goToStep({{ $num }})"
@if($num >= $currentStep) disabled @endif
class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold transition-colors
{{ $num === $currentStep ? 'bg-emerald-600 text-white' : '' }}
{{ $num < $currentStep ? 'bg-green-500 text-white' : '' }}
{{ $num > $currentStep ? 'bg-gray-200 text-gray-500' : '' }}">
@if($num < $currentStep)
<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>
@else
{{ $num }}
@endif
</div>
<span class="text-xs font-medium hidden sm:inline
{{ $num === $currentStep ? 'text-emerald-600' : '' }}
{{ $num < $currentStep ? 'text-green-600' : '' }}
{{ $num > $currentStep ? 'text-gray-400' : '' }}">
{{ __($label) }}
</span>
</button>
@if(!$loop->last)
<div class="flex-1 h-0.5 mx-3 {{ $num < $currentStep ? 'bg-green-500' : 'bg-gray-200' }}"></div>
@endif
</div>
@endforeach
</div>
</div>
{{-- Step Content --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6">
{{-- Step 1: Warehouse --}}
@if($currentStep === 1)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-2">{{ __('اختيار المستودع') }}</h2>
<p class="text-sm text-gray-500 mb-6">{{ __('حدد المستودع الذي سيتم تعديل مخزونه') }}</p>
@if($warehouses->count() > 0)
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
@foreach($warehouses as $warehouse)
<label class="relative cursor-pointer">
<input type="radio" wire:click="selectWarehouse({{ $warehouse->id }})" name="warehouse"
{{ $warehouseId === $warehouse->id ? 'checked' : '' }} class="peer sr-only">
<div class="p-4 border-2 rounded-xl transition-all
peer-checked:border-emerald-500 peer-checked:bg-emerald-50
border-gray-200 hover:border-gray-300">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center">
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
</svg>
</div>
<div>
<div class="font-medium text-gray-800">{{ $warehouse->name_ar }}</div>
@if($warehouse->code)
<div class="text-xs text-gray-400">{{ $warehouse->code }}</div>
@endif
</div>
</div>
</div>
</label>
@endforeach
</div>
@else
<div class="p-6 text-center text-gray-500">
{{ __('لا توجد مستودعات نشطة') }}
</div>
@endif
@error('warehouseId') <p class="text-red-500 text-xs mt-2">{{ $message }}</p> @enderror
</div>
@endif
{{-- Step 2: Product --}}
@if($currentStep === 2)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-2">{{ __('اختيار المنتج') }}</h2>
<p class="text-sm text-gray-500 mb-6">{{ __('حدد المنتج المراد تعديل مخزونه') }}</p>
@if($products->count() > 0)
<div class="space-y-2 max-h-96 overflow-y-auto">
@foreach($products as $product)
<label class="relative cursor-pointer block">
<input type="radio" wire:click="selectProduct({{ $product->id }})" name="product"
{{ $productId === $product->id ? 'checked' : '' }} class="peer sr-only">
<div class="p-4 border-2 rounded-xl transition-all
peer-checked:border-emerald-500 peer-checked:bg-emerald-50
border-gray-200 hover:border-gray-300">
<div class="flex items-center justify-between">
<div>
<div class="font-medium text-gray-800">{{ $product->name_ar }}</div>
@if($product->sku)
<div class="text-xs text-gray-400" dir="ltr">SKU: {{ $product->sku }}</div>
@endif
</div>
</div>
</div>
</label>
@endforeach
</div>
@else
<div class="p-6 text-center text-gray-500">
{{ __('لا توجد منتجات قابلة للتتبع في هذا المستودع') }}
</div>
@endif
{{-- Current Stock Display --}}
@if($currentStock !== null)
<div class="mt-4 p-4 bg-blue-50 border border-blue-200 rounded-xl">
<div class="flex items-center gap-3">
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<div>
<span class="text-sm text-blue-700">{{ __('المخزون الحالي:') }}</span>
<span class="font-bold text-blue-800 ms-1" dir="ltr">{{ $currentStock }}</span>
<span class="text-sm text-blue-600">{{ __('وحدة') }}</span>
</div>
</div>
</div>
@endif
@error('productId') <p class="text-red-500 text-xs mt-2">{{ $message }}</p> @enderror
</div>
@endif
{{-- Step 3: Quantity & Reason --}}
@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>
{{-- Direction --}}
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __('اتجاه التسوية') }}</label>
<div class="grid grid-cols-2 gap-4">
<label class="relative cursor-pointer">
<input type="radio" wire:model="adjustmentDirection" value="up" class="peer sr-only">
<div class="p-4 border-2 rounded-xl transition-all text-center
peer-checked:border-green-500 peer-checked:bg-green-50
border-gray-200 hover:border-gray-300">
<svg class="w-8 h-8 mx-auto mb-2 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 11l5-5m0 0l5 5m-5-5v12"/>
</svg>
<div class="font-medium text-gray-800">{{ __('زيادة') }}</div>
<div class="text-xs text-gray-500">{{ __('إضافة كمية للمخزون') }}</div>
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model="adjustmentDirection" value="down" class="peer sr-only">
<div class="p-4 border-2 rounded-xl transition-all text-center
peer-checked:border-red-500 peer-checked:bg-red-50
border-gray-200 hover:border-gray-300">
<svg class="w-8 h-8 mx-auto mb-2 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 13l-5 5m0 0l-5-5m5 5V6"/>
</svg>
<div class="font-medium text-gray-800">{{ __('نقص') }}</div>
<div class="text-xs text-gray-500">{{ __('خصم كمية من المخزون') }}</div>
</div>
</label>
</div>
@error('adjustmentDirection') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Quantity --}}
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الكمية') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="quantity" dir="ltr" min="1"
class="w-full max-w-xs px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
@error('quantity') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Reason --}}
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('السبب') }} <span class="text-red-500">*</span></label>
<textarea wire:model="reason" rows="3"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
placeholder="{{ __('اذكر سبب تعديل المخزون...') }}"></textarea>
@error('reason') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Reference Number --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('رقم المرجع') }}</label>
<input type="text" wire:model="referenceNumber" dir="ltr"
class="w-full max-w-xs px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
placeholder="{{ __('اختياري') }}">
@error('referenceNumber') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
</div>
@endif
{{-- Step 4: Review --}}
@if($currentStep === 4)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('مراجعة التسوية') }}</h2>
<div class="space-y-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('المستودع') }}</div>
<div class="font-medium text-gray-800">{{ $selectedWarehouse['name_ar'] ?? '-' }}</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('المنتج') }}</div>
<div class="font-medium text-gray-800">{{ $selectedProduct['name_ar'] ?? '-' }}</div>
</div>
</div>
{{-- Before / After Visual --}}
<div class="p-6 bg-gray-50 rounded-xl">
<div class="flex items-center justify-center gap-6">
<div class="text-center">
<div class="text-xs text-gray-500 mb-1">{{ __('قبل') }}</div>
<div class="text-2xl font-bold text-gray-800" dir="ltr">{{ $currentStock ?? 0 }}</div>
</div>
<div class="text-center px-4">
@if($adjustmentDirection === 'up')
<span class="text-2xl font-bold text-green-600" dir="ltr">+{{ $quantity ?? 0 }}</span>
@else
<span class="text-2xl font-bold text-red-600" dir="ltr">-{{ $quantity ?? 0 }}</span>
@endif
</div>
<div class="text-center">
<div class="text-xs text-gray-500 mb-1">{{ __('بعد') }}</div>
<div class="text-2xl font-bold {{ $this->expectedAfter <= 0 ? 'text-red-600' : 'text-gray-800' }}" dir="ltr">
{{ $this->expectedAfter ?? 0 }}
</div>
</div>
</div>
</div>
{{-- Warning if going to zero --}}
@if($this->expectedAfter !== null && $this->expectedAfter <= 0)
<div class="p-4 bg-amber-50 border border-amber-200 rounded-lg">
<div class="flex items-start gap-2">
<svg class="w-5 h-5 text-amber-600 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
<div class="text-sm text-amber-700">
{{ __('تحذير: المخزون سيصل إلى صفر أو أقل بعد هذه التسوية!') }}
</div>
</div>
</div>
@endif
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('الاتجاه') }}</div>
<div class="font-medium text-gray-800">
{{ $adjustmentDirection === 'up' ? __('زيادة (تسوية جرد بالزيادة)') : __('نقص (تسوية جرد بالنقص)') }}
</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('الكمية') }}</div>
<div class="font-medium text-gray-800" dir="ltr">{{ $quantity }}</div>
</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('السبب') }}</div>
<div class="text-gray-800">{{ $reason }}</div>
</div>
@if($referenceNumber)
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('رقم المرجع') }}</div>
<div class="text-gray-800" dir="ltr">{{ $referenceNumber }}</div>
</div>
@endif
</div>
</div>
@endif
{{-- Step 5: Final Confirm --}}
@if($currentStep === 5)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('التأكيد النهائي') }}</h2>
<div class="p-6 bg-amber-50 border border-amber-200 rounded-xl mb-6">
<div class="flex items-start gap-3">
<svg class="w-6 h-6 text-amber-600 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
<div>
<div class="font-medium text-amber-800 mb-1">{{ __('هل أنت متأكد؟') }}</div>
<div class="text-sm text-amber-700">
{{ __('سيتم تعديل المخزون فوراً. هذا الإجراء سيُسجل كحركة مخزون ولا يمكن التراجع عنه إلا بإجراء تسوية عكسية.') }}
</div>
</div>
</div>
</div>
<div class="text-center">
<p class="text-gray-600 mb-4">
{{ __('سيتم') }}
<strong>{{ $adjustmentDirection === 'up' ? __('إضافة') : __('خصم') }}</strong>
<strong dir="ltr">{{ $quantity }}</strong>
{{ __('وحدة') }}
{{ $adjustmentDirection === 'up' ? __('إلى') : __('من') }}
<strong>{{ $selectedProduct['name_ar'] ?? '' }}</strong>
{{ __('في') }}
<strong>{{ $selectedWarehouse['name_ar'] ?? '' }}</strong>
</p>
</div>
</div>
@endif
{{-- Navigation Buttons --}}
<div class="flex items-center justify-between mt-8 pt-6 border-t border-gray-200">
<div>
@if($currentStep > 1)
<button wire:click="previousStep"
class="px-5 py-2.5 text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 font-medium transition-colors">
{{ __('السابق') }}
</button>
@endif
</div>
<div>
@if($currentStep < $totalSteps)
<button wire:click="nextStep" wire:loading.attr="disabled" wire:target="nextStep"
class="px-6 py-2.5 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 font-medium transition-colors disabled:opacity-50">
<span wire:loading.remove wire:target="nextStep">{{ __('التالي') }}</span>
<span wire:loading wire:target="nextStep">{{ __('جارٍ التحقق...') }}</span>
</button>
@else
<button wire:click="confirm" wire:loading.attr="disabled" wire:target="confirm"
class="px-6 py-3 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 font-medium disabled:opacity-50">
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد التسوية') }}</span>
<span wire:loading wire:target="confirm">{{ __('جارٍ التنفيذ...') }}</span>
</button>
@endif
</div>
</div>
</div>
@endif
</div>
<div>
{{-- Header --}}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('إنشاء فاتورة جديدة') }}</h1>
<p class="text-sm text-gray-500 mt-1">{{ __('إنشاء فاتورة يدوية لمشترك') }}</p>
</div>
</div>
{{-- Flash Messages --}}
@if(session('error'))
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-xl text-red-700 text-sm">
{{ session('error') }}
</div>
@endif
{{-- Step Indicator --}}
@if(!$completed)
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6">
<div class="flex items-center justify-between">
@php $steps = $this->getStepLabels(); @endphp
@foreach($steps as $num => $label)
<div class="flex items-center {{ !$loop->last ? 'flex-1' : '' }}">
<button wire:click="goToStep({{ $num }})"
@if($num >= $currentStep) disabled @endif
class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold transition-colors
{{ $num === $currentStep ? 'bg-blue-600 text-white' : '' }}
{{ $num < $currentStep ? 'bg-green-500 text-white' : '' }}
{{ $num > $currentStep ? 'bg-gray-200 text-gray-500' : '' }}">
@if($num < $currentStep)
<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>
@else
{{ $num }}
@endif
</div>
<span class="text-xs font-medium hidden sm:inline
{{ $num === $currentStep ? 'text-blue-600' : '' }}
{{ $num < $currentStep ? 'text-green-600' : '' }}
{{ $num > $currentStep ? 'text-gray-400' : '' }}">
{{ __($label) }}
</span>
</button>
@if(!$loop->last)
<div class="flex-1 h-0.5 mx-3 {{ $num < $currentStep ? 'bg-green-500' : 'bg-gray-200' }}"></div>
@endif
</div>
@endforeach
</div>
</div>
@endif
{{-- Step Content --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6">
{{-- Step 1: Client --}}
@if($currentStep === 1)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('اختيار العميل') }}</h2>
{{-- Participant Search --}}
<div class="mb-4">
<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.300ms="participantSearch"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="{{ __('بحث بالاسم أو رقم المشترك أو الهاتف...') }}">
@error('participantId') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Selected Participant --}}
@if($participantId)
<div class="mb-4 p-4 bg-blue-50 border border-blue-200 rounded-xl flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-blue-200 flex items-center justify-center">
<svg class="w-5 h-5 text-blue-700" 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>
<span class="font-medium text-blue-800">{{ $participantName }}</span>
</div>
<button wire:click="$set('participantId', null)" class="text-blue-600 hover:text-blue-800 text-sm">
{{ __('تغيير') }}
</button>
</div>
@endif
{{-- Search Results --}}
@if(strlen($participantSearch) >= 2 && !$participantId)
<div class="space-y-2 mb-4" wire:loading.class="opacity-50">
@forelse($searchResults as $result)
<button wire:click="selectParticipant({{ $result->id }})"
class="w-full p-3 border border-gray-200 rounded-lg text-start hover:border-blue-300 hover:bg-blue-50 transition-all flex items-center justify-between">
<div>
<p class="font-medium text-gray-800">{{ $result->person?->name_ar }}</p>
<div class="flex items-center gap-3 mt-1 text-xs text-gray-500">
@if($result->participant_number)
<span dir="ltr">{{ $result->participant_number }}</span>
@endif
@if($result->person?->phone)
<span dir="ltr">{{ $result->person->phone }}</span>
@endif
</div>
</div>
</button>
@empty
<p class="text-center text-gray-500 text-sm py-4">{{ __('لا توجد نتائج') }}</p>
@endforelse
</div>
@endif
{{-- Contact Details --}}
@if($participantId)
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('اسم جهة الاتصال') }}</label>
<input type="text" wire:model="contactName"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('هاتف جهة الاتصال') }}</label>
<input type="text" wire:model="contactPhone" dir="ltr"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
</div>
@endif
</div>
@endif
{{-- Step 2: Items --}}
@if($currentStep === 2)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('بنود الفاتورة') }}</h2>
<div class="space-y-4">
@foreach($items as $index => $item)
<div class="border border-gray-200 rounded-lg p-4 relative">
<div class="flex items-center justify-between mb-3">
<span class="text-sm font-medium text-gray-600">{{ __('بند') }} {{ $index + 1 }}</span>
@if(count($items) > 1)
<button wire:click="removeItem({{ $index }})" type="button"
class="text-red-500 hover:text-red-700 text-sm">
<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="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
</button>
@endif
</div>
<div class="grid grid-cols-1 md:grid-cols-4 gap-3">
<div class="md:col-span-2">
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('الوصف') }} <span class="text-red-500">*</span></label>
<input type="text" wire:model="items.{{ $index }}.description"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
placeholder="{{ __('وصف البند...') }}">
@error("items.{$index}.description") <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('الكمية') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="items.{{ $index }}.quantity" dir="ltr" min="1"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error("items.{$index}.quantity") <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('سعر الوحدة') }} ({{ __('ج.م') }}) <span class="text-red-500">*</span></label>
<input type="number" wire:model="items.{{ $index }}.unit_price" dir="ltr" step="0.01" min="0"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
placeholder="0.00">
@error("items.{$index}.unit_price") <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
</div>
@if(($item['unit_price'] ?? 0) > 0 && ($item['quantity'] ?? 0) > 0)
<div class="mt-2 text-end text-sm text-gray-500">
{{ __('الإجمالي') }}: <span dir="ltr" class="font-medium">{{ number_format((float)$item['unit_price'] * (int)$item['quantity'], 2) }} {{ __('ج.م') }}</span>
</div>
@endif
</div>
@endforeach
</div>
<button wire:click="addItem" type="button"
class="mt-4 inline-flex items-center gap-2 px-4 py-2 text-blue-600 bg-blue-50 rounded-lg hover:bg-blue-100 text-sm font-medium 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="M12 4v16m8-8H4"/>
</svg>
{{ __('إضافة بند') }}
</button>
{{-- Subtotal Preview --}}
@php
$subtotalPreview = 0;
foreach($items as $item) {
$subtotalPreview += ((float)($item['unit_price'] ?? 0)) * ((int)($item['quantity'] ?? 0));
}
@endphp
@if($subtotalPreview > 0)
<div class="mt-4 p-3 bg-gray-50 rounded-lg text-end">
<span class="text-sm text-gray-600">{{ __('المجموع الفرعي') }}:</span>
<span class="text-sm font-bold text-gray-800 ms-2" dir="ltr">{{ number_format($subtotalPreview, 2) }} {{ __('ج.م') }}</span>
</div>
@endif
</div>
@endif
{{-- Step 3: Discount & Tax --}}
@if($currentStep === 3)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('الخصم والضريبة') }}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{{-- Discount --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('مبلغ الخصم') }}</label>
<div class="relative">
<input type="number" wire:model="discountAmount" dir="ltr" step="0.01" min="0"
class="w-full px-4 py-2.5 pe-12 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="0.00">
<span class="absolute end-3 top-1/2 -translate-y-1/2 text-sm text-gray-500">{{ __('ج.م') }}</span>
</div>
@error('discountAmount') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Tax --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('مبلغ الضريبة') }}</label>
<div class="relative">
<input type="number" wire:model="taxAmount" dir="ltr" step="0.01" min="0"
class="w-full px-4 py-2.5 pe-12 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="0.00">
<span class="absolute end-3 top-1/2 -translate-y-1/2 text-sm text-gray-500">{{ __('ج.م') }}</span>
</div>
@error('taxAmount') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
</div>
{{-- Totals Preview --}}
@php
$subtotalCalc = 0;
foreach($items as $item) {
$subtotalCalc += ((float)($item['unit_price'] ?? 0)) * ((int)($item['quantity'] ?? 0));
}
$discountCalc = (float)($discountAmount ?? 0);
$taxCalc = (float)($taxAmount ?? 0);
$totalCalc = $subtotalCalc - $discountCalc + $taxCalc;
@endphp
<div class="mt-6 p-4 bg-gray-50 rounded-lg space-y-2">
<div class="flex justify-between text-sm">
<span class="text-gray-600">{{ __('المجموع الفرعي') }}</span>
<span class="font-medium" dir="ltr">{{ number_format($subtotalCalc, 2) }} {{ __('ج.م') }}</span>
</div>
@if($discountCalc > 0)
<div class="flex justify-between text-sm">
<span class="text-red-600">{{ __('الخصم') }} (-)</span>
<span class="font-medium text-red-600" dir="ltr">{{ number_format($discountCalc, 2) }} {{ __('ج.م') }}</span>
</div>
@endif
@if($taxCalc > 0)
<div class="flex justify-between text-sm">
<span class="text-gray-600">{{ __('الضريبة') }} (+)</span>
<span class="font-medium" dir="ltr">{{ number_format($taxCalc, 2) }} {{ __('ج.م') }}</span>
</div>
@endif
<div class="flex justify-between text-sm pt-2 border-t border-gray-300">
<span class="font-bold text-gray-800">{{ __('الإجمالي') }}</span>
<span class="font-bold text-gray-800" dir="ltr">{{ number_format($totalCalc, 2) }} {{ __('ج.م') }}</span>
</div>
</div>
</div>
@endif
{{-- Step 4: Dates --}}
@if($currentStep === 4)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('تاريخ الاستحقاق والملاحظات') }}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{{-- Issue Date --}}
<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="issueDate" dir="ltr"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
@error('issueDate') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Due Date --}}
<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="dueDate" dir="ltr"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
@error('dueDate') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
{{-- Notes --}}
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('ملاحظات') }}</label>
<textarea wire:model="notes" rows="3"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="{{ __('ملاحظات إضافية على الفاتورة...') }}"></textarea>
@error('notes') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
</div>
</div>
@endif
{{-- Step 5: Review --}}
@if($currentStep === 5 && !$completed)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('مراجعة الفاتورة') }}</h2>
<div class="space-y-4">
{{-- Client Info --}}
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="text-sm font-semibold text-gray-600 mb-3">{{ __('بيانات العميل') }}</h3>
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
<div>
<dt class="text-gray-500">{{ __('المشترك') }}</dt>
<dd class="font-medium text-gray-800">{{ $participantName }}</dd>
</div>
@if($contactPhone)
<div>
<dt class="text-gray-500">{{ __('الهاتف') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ $contactPhone }}</dd>
</div>
@endif
</dl>
</div>
{{-- Items Table --}}
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="text-sm font-semibold text-gray-600 mb-3">{{ __('البنود') }}</h3>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-gray-200">
<th class="text-start py-2 text-gray-600 font-medium">{{ __('الوصف') }}</th>
<th class="text-center py-2 text-gray-600 font-medium">{{ __('الكمية') }}</th>
<th class="text-end py-2 text-gray-600 font-medium">{{ __('السعر') }}</th>
<th class="text-end py-2 text-gray-600 font-medium">{{ __('الإجمالي') }}</th>
</tr>
</thead>
<tbody>
@foreach($items as $item)
<tr class="border-b border-gray-100">
<td class="py-2 text-gray-800">{{ $item['description'] }}</td>
<td class="py-2 text-center text-gray-800" dir="ltr">{{ $item['quantity'] }}</td>
<td class="py-2 text-end text-gray-800" dir="ltr">{{ number_format((float)$item['unit_price'], 2) }}</td>
<td class="py-2 text-end font-medium text-gray-800" dir="ltr">{{ number_format((float)$item['unit_price'] * (int)$item['quantity'], 2) }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
{{-- Financial Summary --}}
@php
$reviewSubtotal = 0;
foreach($items as $item) {
$reviewSubtotal += ((float)($item['unit_price'] ?? 0)) * ((int)($item['quantity'] ?? 0));
}
$reviewDiscount = (float)($discountAmount ?? 0);
$reviewTax = (float)($taxAmount ?? 0);
$reviewTotal = $reviewSubtotal - $reviewDiscount + $reviewTax;
@endphp
<div class="bg-blue-50 rounded-lg p-4">
<h3 class="text-sm font-semibold text-gray-600 mb-3">{{ __('الملخص المالي') }}</h3>
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span class="text-gray-600">{{ __('المجموع الفرعي') }}</span>
<span class="font-medium" dir="ltr">{{ number_format($reviewSubtotal, 2) }} {{ __('ج.م') }}</span>
</div>
@if($reviewDiscount > 0)
<div class="flex justify-between">
<span class="text-red-600">{{ __('الخصم') }}</span>
<span class="font-medium text-red-600" dir="ltr">-{{ number_format($reviewDiscount, 2) }} {{ __('ج.م') }}</span>
</div>
@endif
@if($reviewTax > 0)
<div class="flex justify-between">
<span class="text-gray-600">{{ __('الضريبة') }}</span>
<span class="font-medium" dir="ltr">+{{ number_format($reviewTax, 2) }} {{ __('ج.م') }}</span>
</div>
@endif
<div class="flex justify-between pt-2 border-t border-blue-200">
<span class="font-bold text-gray-800">{{ __('الإجمالي المستحق') }}</span>
<span class="font-bold text-blue-700 text-lg" dir="ltr">{{ number_format($reviewTotal, 2) }} {{ __('ج.م') }}</span>
</div>
</div>
</div>
{{-- Dates --}}
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="text-sm font-semibold text-gray-600 mb-3">{{ __('التواريخ') }}</h3>
<dl class="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
<div>
<dt class="text-gray-500">{{ __('تاريخ الإصدار') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ $issueDate }}</dd>
</div>
<div>
<dt class="text-gray-500">{{ __('تاريخ الاستحقاق') }}</dt>
<dd class="font-medium text-gray-800" dir="ltr">{{ $dueDate }}</dd>
</div>
@if($notes)
<div class="sm:col-span-2">
<dt class="text-gray-500">{{ __('ملاحظات') }}</dt>
<dd class="font-medium text-gray-800">{{ $notes }}</dd>
</div>
@endif
</dl>
</div>
</div>
</div>
@endif
{{-- Success State --}}
@if($completed)
<div class="text-center py-8">
<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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
</div>
<h2 class="text-xl font-bold text-gray-800 mb-2">{{ __('تم إنشاء الفاتورة بنجاح') }}</h2>
<p class="text-gray-500 mb-6">{{ __('تم إنشاء الفاتورة بحالة مسودة ويمكنك إرسالها للعميل') }}</p>
</div>
@endif
{{-- Navigation Buttons --}}
@if(!$completed)
<div class="flex items-center justify-between mt-8 pt-4 border-t border-gray-200">
@if($currentStep > 1)
<button wire:click="previousStep" type="button"
class="inline-flex items-center gap-2 px-5 py-2.5 text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 font-medium text-sm transition-colors">
<svg class="w-4 h-4 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>
@else
<div></div>
@endif
@if($currentStep < $totalSteps)
<button wire:click="nextStep" type="button"
wire:loading.attr="disabled" wire:target="nextStep"
class="inline-flex items-center gap-2 px-5 py-2.5 text-white bg-blue-600 rounded-lg hover:bg-blue-700 font-medium text-sm transition-colors">
<span wire:loading.remove wire:target="nextStep">{{ __('التالي') }}</span>
<span wire:loading wire:target="nextStep">{{ __('جارٍ التحقق...') }}</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
</button>
@else
<button wire:click="confirm" type="button"
wire:loading.attr="disabled" wire:target="confirm"
class="inline-flex items-center gap-2 px-6 py-2.5 text-white bg-green-600 rounded-lg hover:bg-green-700 font-medium text-sm transition-colors">
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد إنشاء الفاتورة') }}</span>
<span wire:loading wire:target="confirm">{{ __('جارٍ الحفظ...') }}</span>
</button>
@endif
</div>
@endif
</div>
</div>
<div>
{{-- Header --}}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('إنشاء قاعدة تسعير') }}</h1>
<p class="text-sm text-gray-500 mt-1">{{ __('إنشاء قاعدة خصم أو تعديل سعر جديدة') }}</p>
</div>
</div>
{{-- Flash Messages --}}
@if(session('error'))
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-xl text-red-700 text-sm">
{{ session('error') }}
</div>
@endif
{{-- Success State --}}
@if($completed)
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-8 text-center">
<div class="w-16 h-16 mx-auto mb-4 bg-green-100 rounded-full flex items-center justify-center">
<svg class="w-8 h-8 text-green-600" 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>
<h2 class="text-xl font-bold text-gray-800 mb-2">{{ __('تم إنشاء القاعدة بنجاح') }}</h2>
<p class="text-gray-500 mb-6">{{ __('تم حفظ قاعدة التسعير وستُطبق على العمليات القادمة') }}</p>
<a href="{{ route('pricing.rules.index') }}" wire:navigate
class="inline-flex items-center gap-2 px-6 py-3 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 font-medium transition-colors">
{{ __('العودة لقائمة القواعد') }}
</a>
</div>
@else
{{-- Step Indicator --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6">
<div class="flex items-center justify-between">
@foreach($this->getStepLabels() as $num => $label)
<div class="flex items-center {{ !$loop->last ? 'flex-1' : '' }}">
<button wire:click="goToStep({{ $num }})"
@if($num >= $currentStep) disabled @endif
class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold transition-colors
{{ $num === $currentStep ? 'bg-emerald-600 text-white' : '' }}
{{ $num < $currentStep ? 'bg-green-500 text-white' : '' }}
{{ $num > $currentStep ? 'bg-gray-200 text-gray-500' : '' }}">
@if($num < $currentStep)
<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>
@else
{{ $num }}
@endif
</div>
<span class="text-xs font-medium hidden sm:inline
{{ $num === $currentStep ? 'text-emerald-600' : '' }}
{{ $num < $currentStep ? 'text-green-600' : '' }}
{{ $num > $currentStep ? 'text-gray-400' : '' }}">
{{ __($label) }}
</span>
</button>
@if(!$loop->last)
<div class="flex-1 h-0.5 mx-3 {{ $num < $currentStep ? 'bg-green-500' : 'bg-gray-200' }}"></div>
@endif
</div>
@endforeach
</div>
</div>
{{-- Step Content --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6">
{{-- Step 1: Rule Type --}}
@if($currentStep === 1)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-2">{{ __('اختر نوع القاعدة') }}</h2>
<p class="text-sm text-gray-500 mb-6">{{ __('حدد نوع الشرط الذي سيتم التحقق منه عند تطبيق الخصم') }}</p>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
@foreach($ruleTypes as $type)
<label class="relative cursor-pointer">
<input type="radio" wire:model="ruleType" value="{{ $type->value }}" class="peer sr-only">
<div class="p-4 border-2 rounded-xl transition-all
peer-checked:border-emerald-500 peer-checked:bg-emerald-50
border-gray-200 hover:border-gray-300">
<div class="font-medium text-gray-800">{{ $type->label() }}</div>
<div class="text-xs text-gray-500 mt-1">{{ $type->value }}</div>
</div>
</label>
@endforeach
</div>
@error('ruleType')
<p class="text-red-500 text-sm mt-2">{{ $message }}</p>
@enderror
</div>
@endif
{{-- Step 2: Conditions --}}
@if($currentStep === 2)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-2">{{ __('شروط القاعدة') }}</h2>
<p class="text-sm text-gray-500 mb-6">{{ __('حدد الشروط التي يجب تحققها لتطبيق هذه القاعدة') }}</p>
@if($ruleType === 'age')
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الحد الأدنى للعمر') }}</label>
<input type="number" wire:model="minAge" dir="ltr" min="1" max="100"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
@error('minAge') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الحد الأقصى للعمر') }}</label>
<input type="number" wire:model="maxAge" dir="ltr" min="1" max="100"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
@error('maxAge') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
</div>
@elseif($ruleType === 'membership_duration')
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الحد الأدنى لأشهر العضوية') }}</label>
<input type="number" wire:model="minMonths" dir="ltr" min="1"
class="w-full max-w-xs px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
@error('minMonths') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
@elseif($ruleType === 'family_size')
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الحد الأدنى لعدد الأبناء') }}</label>
<input type="number" wire:model="minChildren" dir="ltr" min="2"
class="w-full max-w-xs px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
@error('minChildren') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
@elseif($ruleType === 'sibling_order')
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('ترتيب الأخ (مثال: 2 = الأخ الثاني)') }}</label>
<input type="number" wire:model="orderNumber" dir="ltr" min="1"
class="w-full max-w-xs px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
@error('orderNumber') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
@elseif($ruleType === 'gender')
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __('الجنس المستهدف') }}</label>
<div class="flex gap-4">
<label class="relative cursor-pointer">
<input type="radio" wire:model="targetGender" value="male" class="peer sr-only">
<div class="px-6 py-3 border-2 rounded-xl transition-all
peer-checked:border-emerald-500 peer-checked:bg-emerald-50
border-gray-200 hover:border-gray-300 font-medium">
{{ __('ذكر') }}
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model="targetGender" value="female" class="peer sr-only">
<div class="px-6 py-3 border-2 rounded-xl transition-all
peer-checked:border-emerald-500 peer-checked:bg-emerald-50
border-gray-200 hover:border-gray-300 font-medium">
{{ __('أنثى') }}
</div>
</label>
</div>
@error('targetGender') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
@elseif($ruleType === 'branch')
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الفرع المستهدف') }}</label>
<select wire:model="targetBranchId"
class="w-full max-w-md px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
<option value="">{{ __('اختر الفرع') }}</option>
@foreach($branches as $branch)
<option value="{{ $branch->id }}">{{ $branch->name_ar }}</option>
@endforeach
</select>
@error('targetBranchId') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
@elseif($ruleType === 'classification')
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __('التصنيف المستهدف') }}</label>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3">
@php
$classifications = [
'regular' => 'عادي',
'vip' => 'VIP',
'scholarship' => 'منحة',
'staff_child' => 'ابن موظف',
'trial' => 'تجريبي',
];
@endphp
@foreach($classifications as $value => $label)
<label class="relative cursor-pointer">
<input type="radio" wire:model="targetClassification" value="{{ $value }}" class="peer sr-only">
<div class="px-4 py-3 border-2 rounded-xl transition-all text-center
peer-checked:border-emerald-500 peer-checked:bg-emerald-50
border-gray-200 hover:border-gray-300 font-medium text-sm">
{{ __($label) }}
</div>
</label>
@endforeach
</div>
@error('targetClassification') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
@else
{{-- Generic conditions for: enrollment_timing, enrollment_volume, seasonal, day_time, loyalty, custom --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الشروط (JSON)') }}</label>
<textarea wire:model="genericConditions" rows="5" dir="ltr"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 font-mono text-sm"
placeholder='{"key": "value"}'></textarea>
<p class="text-xs text-gray-400 mt-1">{{ __('أدخل الشروط بصيغة JSON. اتركه فارغاً إذا لا توجد شروط إضافية.') }}</p>
</div>
@endif
</div>
@endif
{{-- Step 3: Adjustment --}}
@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>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-6">
@foreach($adjustmentTypes as $type)
<label class="relative cursor-pointer">
<input type="radio" wire:model="adjustmentType" value="{{ $type->value }}" class="peer sr-only">
<div class="p-4 border-2 rounded-xl transition-all
peer-checked:border-emerald-500 peer-checked:bg-emerald-50
border-gray-200 hover:border-gray-300">
<div class="font-medium text-gray-800">{{ $type->label() }}</div>
</div>
</label>
@endforeach
</div>
@error('adjustmentType') <p class="text-red-500 text-xs mb-4">{{ $message }}</p> @enderror
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
{{ __('القيمة') }}
@if(in_array($adjustmentType, ['percentage_discount', 'percentage_increase']))
<span class="text-gray-400">({{ __('%') }})</span>
@else
<span class="text-gray-400">({{ __('ج.م') }})</span>
@endif
</label>
<input type="number" wire:model="adjustmentValue" dir="ltr" min="1"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
@if(in_array($adjustmentType, ['fixed_discount', 'fixed_price']))
<p class="text-xs text-gray-400 mt-1">{{ __('القيمة بالقروش (100 قرش = 1 جنيه)') }}</p>
@endif
@error('adjustmentValue') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الحد الأقصى للخصم %') }}</label>
<input type="number" wire:model="maxDiscountPercent" dir="ltr" min="1" max="100"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
placeholder="{{ __('اختياري') }}">
@error('maxDiscountPercent') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
</div>
</div>
@endif
{{-- Step 4: Scope & Priority --}}
@if($currentStep === 4)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-2">{{ __('النطاق والأولوية') }}</h2>
<p class="text-sm text-gray-500 mb-6">{{ __('حدد تفاصيل القاعدة ونطاق تطبيقها') }}</p>
<div class="space-y-4">
{{-- Name --}}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<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="nameAr"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
@error('nameAr') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الاسم بالإنجليزية') }}</label>
<input type="text" wire:model="name" dir="ltr"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
@error('name') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
</div>
{{-- Target & Branch --}}
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نوع الهدف') }}</label>
<select wire:model="targetType"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
<option value="">{{ __('الكل') }}</option>
<option value="program">{{ __('برنامج') }}</option>
<option value="activity">{{ __('نشاط') }}</option>
<option value="product">{{ __('منتج') }}</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('معرف الهدف') }}</label>
<input type="number" wire:model="targetId" dir="ltr"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
placeholder="{{ __('اختياري') }}">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الفرع') }}</label>
<select wire:model="branchId"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
<option value="">{{ __('جميع الفروع') }}</option>
@foreach($branches as $branch)
<option value="{{ $branch->id }}">{{ $branch->name_ar }}</option>
@endforeach
</select>
</div>
</div>
{{-- Priority & Stackable --}}
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الأولوية') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="priority" dir="ltr" min="1" max="100"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
<p class="text-xs text-gray-400 mt-1">{{ __('رقم أقل = أولوية أعلى') }}</p>
@error('priority') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('حد الاستخدام') }}</label>
<input type="number" wire:model="usageLimit" dir="ltr" min="1"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500"
placeholder="{{ __('بدون حد') }}">
@error('usageLimit') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
<div class="flex items-end gap-6 pb-2">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" wire:model="isStackable"
class="w-5 h-5 rounded border-gray-300 text-emerald-600 focus:ring-emerald-500">
<span class="text-sm font-medium text-gray-700">{{ __('قابلة للتراكم') }}</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" wire:model="isActive"
class="w-5 h-5 rounded border-gray-300 text-emerald-600 focus:ring-emerald-500">
<span class="text-sm font-medium text-gray-700">{{ __('مفعّلة') }}</span>
</label>
</div>
</div>
{{-- Dates --}}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<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="effectiveFrom" dir="ltr"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
@error('effectiveFrom') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تاريخ الانتهاء') }}</label>
<input type="date" wire:model="effectiveTo" dir="ltr"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
<p class="text-xs text-gray-400 mt-1">{{ __('اتركه فارغاً لقاعدة مفتوحة') }}</p>
@error('effectiveTo') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
</div>
</div>
</div>
@endif
{{-- Step 5: Review --}}
@if($currentStep === 5)
<div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('مراجعة القاعدة') }}</h2>
<div class="space-y-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('الاسم') }}</div>
<div class="font-medium text-gray-800">{{ $nameAr }}</div>
@if($name)
<div class="text-sm text-gray-500">{{ $name }}</div>
@endif
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('نوع القاعدة') }}</div>
<div class="font-medium text-gray-800">{{ $this->ruleTypeLabel }}</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('نوع التعديل') }}</div>
<div class="font-medium text-gray-800">{{ $this->adjustmentTypeLabel }}</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('قيمة التعديل') }}</div>
<div class="font-medium text-gray-800" dir="ltr">
@if(in_array($adjustmentType, ['percentage_discount', 'percentage_increase']))
{{ $adjustmentValue }}%
@else
{{ number_format($adjustmentValue / 100, 2) }} {{ __('ج.م') }}
@endif
</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('الأولوية') }}</div>
<div class="font-medium text-gray-800">{{ $priority }}</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('قابلة للتراكم') }}</div>
<div class="font-medium text-gray-800">{{ $isStackable ? __('نعم') : __('لا') }}</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('تاريخ البدء') }}</div>
<div class="font-medium text-gray-800" dir="ltr">{{ $effectiveFrom }}</div>
</div>
<div class="p-4 bg-gray-50 rounded-lg">
<div class="text-xs text-gray-500 mb-1">{{ __('تاريخ الانتهاء') }}</div>
<div class="font-medium text-gray-800" dir="ltr">{{ $effectiveTo ?: __('مفتوح') }}</div>
</div>
</div>
@if($maxDiscountPercent)
<div class="p-4 bg-amber-50 border border-amber-200 rounded-lg">
<div class="text-sm text-amber-700">
{{ __('الحد الأقصى للخصم:') }} {{ $maxDiscountPercent }}%
</div>
</div>
@endif
</div>
</div>
@endif
{{-- Navigation Buttons --}}
<div class="flex items-center justify-between mt-8 pt-6 border-t border-gray-200">
<div>
@if($currentStep > 1)
<button wire:click="previousStep"
class="px-5 py-2.5 text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 font-medium transition-colors">
{{ __('السابق') }}
</button>
@endif
</div>
<div>
@if($currentStep < $totalSteps)
<button wire:click="nextStep" wire:loading.attr="disabled" wire:target="nextStep"
class="px-6 py-2.5 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 font-medium transition-colors disabled:opacity-50">
<span wire:loading.remove wire:target="nextStep">{{ __('التالي') }}</span>
<span wire:loading wire:target="nextStep">{{ __('جارٍ التحقق...') }}</span>
</button>
@else
<button wire:click="confirm" wire:loading.attr="disabled" wire:target="confirm"
class="px-6 py-3 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 font-medium disabled:opacity-50">
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد') }}</span>
<span wire:loading wire:target="confirm">{{ __('جارٍ الحفظ...') }}</span>
</button>
@endif
</div>
</div>
</div>
@endif
</div>
<div>
<div class="flex items-center justify-between mb-6">
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('إنشاء برنامج تدريبي') }}</h1>
</div>
{{-- Step Indicator --}}
<div class="flex items-center justify-center gap-2 mb-8">
@foreach($stepLabels as $num => $label)
<button wire:click="goToStep({{ $num }})"
class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<span class="flex items-center justify-center w-8 h-8 rounded-full text-sm font-bold
{{ $num < $currentStep ? 'bg-green-500 text-white' : ($num === $currentStep ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-500') }}">
@if($num < $currentStep)
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
@else
{{ $num }}
@endif
</span>
<span class="hidden sm:inline text-sm {{ $num === $currentStep ? 'text-blue-700 font-medium' : 'text-gray-500' }}">{{ $label }}</span>
</button>
@if(!$loop->last)
<div class="w-8 h-0.5 {{ $num < $currentStep ? 'bg-green-400' : 'bg-gray-200' }}"></div>
@endif
@endforeach
</div>
@if(session('error'))
<div class="mb-4 p-3 rounded-lg bg-red-50 border border-red-200 text-red-700 text-sm">{{ session('error') }}</div>
@endif
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
{{-- Step 1: Basic Info --}}
@if($currentStep === 1)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('المعلومات الأساسية') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('اسم البرنامج بالعربي') }} <span class="text-red-500">*</span></label>
<input type="text" wire:model="nameAr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('nameAr') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('اسم البرنامج بالإنجليزي') }}</label>
<input type="text" wire:model="name" dir="ltr" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('النشاط') }} <span class="text-red-500">*</span></label>
<select wire:model="activityId" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('اختر النشاط...') }}</option>
@foreach($activities as $activity)
<option value="{{ $activity->id }}">{{ $activity->name_ar }}</option>
@endforeach
</select>
@error('activityId') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الفرع') }} <span class="text-red-500">*</span></label>
<select wire:model="branchId" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('اختر الفرع...') }}</option>
@foreach($branches as $branch)
<option value="{{ $branch->id }}">{{ $branch->name_ar }}</option>
@endforeach
</select>
@error('branchId') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('المستوى') }}</label>
<select wire:model="skillLevel" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('جميع المستويات') }}</option>
<option value="beginner">{{ __('مبتدئ') }}</option>
<option value="intermediate">{{ __('متوسط') }}</option>
<option value="advanced">{{ __('متقدم') }}</option>
<option value="professional">{{ __('محترف') }}</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الجنس') }}</label>
<select wire:model="gender" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<option value="">{{ __('مختلط') }}</option>
<option value="male">{{ __('ذكور فقط') }}</option>
<option value="female">{{ __('إناث فقط') }}</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الحد الأدنى للعمر') }}</label>
<input type="number" wire:model="ageMin" dir="ltr" min="2" max="99"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('ageMin') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الحد الأقصى للعمر') }}</label>
<input type="number" wire:model="ageMax" dir="ltr" min="2" max="99"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('ageMax') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الوصف') }}</label>
<textarea wire:model="descriptionAr" rows="3"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"></textarea>
</div>
</div>
@endif
{{-- Step 2: Schedule & Duration --}}
@if($currentStep === 2)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('الجدول والمدة') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('عدد الحصص في الأسبوع') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="sessionsPerWeek" dir="ltr" min="1" max="14"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('sessionsPerWeek') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('مدة الحصة (بالدقائق)') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="sessionDurationMinutes" dir="ltr" min="15" max="300" step="5"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('sessionDurationMinutes') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('مدة البرنامج (بالأسابيع)') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="programDurationWeeks" dir="ltr" min="1" max="104"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('programDurationWeeks') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
{{-- Spacer --}}
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الحد الأدنى للمشتركين') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="minParticipants" dir="ltr" min="1"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('minParticipants') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الحد الأقصى للمشتركين') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="maxParticipants" dir="ltr" min="1"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
@error('maxParticipants') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
</div>
@endif
{{-- Step 3: Pricing --}}
@if($currentStep === 3)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('التسعير') }}</h2>
<div class="max-w-md">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('السعر الأساسي') }} <span class="text-red-500">*</span></label>
<div class="relative">
<input type="number" wire:model="basePrice" dir="ltr" step="0.01" min="0"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm pe-12">
<span class="absolute end-3 top-1/2 -translate-y-1/2 text-sm text-gray-500">{{ __('ج.م') }}</span>
</div>
@error('basePrice') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
<p class="mt-2 text-xs text-gray-500">{{ __('هذا هو السعر الأساسي للاشتراك الشهري. يمكن تعديل السعر لاحقاً من إعدادات التسعير.') }}</p>
</div>
@endif
{{-- Step 4: Review --}}
@if($currentStep === 4 && !$completed)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('مراجعة وتأكيد') }}</h2>
<dl class="space-y-3">
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('اسم البرنامج') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $nameAr }}</dd>
</div>
@if($activityId)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('النشاط') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $activities->firstWhere('id', $activityId)?->name_ar }}</dd>
</div>
@endif
@if($branchId)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الفرع') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $branches->firstWhere('id', $branchId)?->name_ar }}</dd>
</div>
@endif
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الحصص') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $sessionsPerWeek }} {{ __('حصة/أسبوع') }} - {{ $sessionDurationMinutes }} {{ __('دقيقة') }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('مدة البرنامج') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $programDurationWeeks }} {{ __('أسبوع') }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('المشتركين') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $minParticipants }} - {{ $maxParticipants }}</dd>
</div>
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('السعر الأساسي') }}</dt>
<dd class="text-sm font-medium text-gray-800" dir="ltr">{{ number_format((float)$basePrice, 2) }} {{ __('ج.م') }}</dd>
</div>
@if($gender)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الجنس') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $gender === 'male' ? __('ذكور فقط') : __('إناث فقط') }}</dd>
</div>
@endif
@if($ageMin || $ageMax)
<div class="flex justify-between py-2 border-b border-gray-100">
<dt class="text-sm text-gray-500">{{ __('الفئة العمرية') }}</dt>
<dd class="text-sm font-medium text-gray-800">{{ $ageMin ?: '?' }} - {{ $ageMax ?: '?' }} {{ __('سنة') }}</dd>
</div>
@endif
</dl>
@endif
{{-- Success State --}}
@if($completed)
<div class="text-center py-8">
<div class="mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-green-100 mb-4">
<svg class="w-8 h-8 text-green-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
</svg>
</div>
<h3 class="text-lg font-semibold text-gray-800 mb-2">{{ __('تم إنشاء البرنامج التدريبي بنجاح') }}</h3>
<p class="text-sm text-gray-600 mb-6">{{ __('يمكنك الآن إنشاء مجموعات للبرنامج أو إنشاء برنامج آخر') }}</p>
<div class="flex items-center justify-center gap-3">
<a href="{{ route('programs.index') }}" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors">
{{ __('قائمة البرامج') }}
</a>
<button wire:click="$refresh" onclick="window.location.reload()" class="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
{{ __('إنشاء برنامج آخر') }}
</button>
</div>
</div>
@endif
</div>
{{-- Navigation Buttons --}}
@if(!$completed)
<div class="flex items-center justify-between mt-6">
<div>
@if($currentStep > 1)
<button wire:click="previousStep" class="px-5 py-2.5 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-200 transition-colors">
{{ __('السابق') }}
</button>
@endif
</div>
<div>
@if($currentStep < $totalSteps)
<button wire:click="nextStep" class="px-5 py-2.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
{{ __('التالي') }}
</button>
@elseif($currentStep === $totalSteps)
<button wire:click="confirm" wire:loading.attr="disabled" wire:target="confirm"
class="px-5 py-2.5 bg-green-600 text-white rounded-lg text-sm font-medium hover:bg-green-700 transition-colors disabled:opacity-50">
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد') }}</span>
<span wire:loading wire:target="confirm">{{ __('جارٍ الحفظ...') }}</span>
</button>
@endif
</div>
</div>
@endif
</div>
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
Schedule::command('reminders:overdue-invoices')->weeklyOn(4, '09:00'); Schedule::command('reminders:overdue-invoices')->weeklyOn(4, '09:00');
Schedule::command('groups:reconcile-counts')->dailyAt('03:00'); Schedule::command('groups:reconcile-counts')->dailyAt('03:00');
Schedule::command('enrollments:deactivate-expired')->dailyAt('00:30'); Schedule::command('enrollments:deactivate-expired')->dailyAt('00:30');
Schedule::command('documents:expire')->dailyAt('00:15');
Schedule::command('financials:reconcile')->weeklyOn(0, '04:00'); Schedule::command('financials:reconcile')->weeklyOn(0, '04:00');
Schedule::command('reports:parent-weekly')->weeklyOn(6, '12:00'); Schedule::command('reports:parent-weekly')->weeklyOn(6, '12:00');
Schedule::command('groups:alert-capacity --threshold=90')->dailyAt('08:00'); Schedule::command('groups:alert-capacity --threshold=90')->dailyAt('08:00');
...@@ -167,6 +167,8 @@ ...@@ -167,6 +167,8 @@
// Training Programs // Training Programs
Route::get('/programs', ProgramList::class)->name('programs.list') Route::get('/programs', ProgramList::class)->name('programs.list')
->middleware('permission:programs.list'); ->middleware('permission:programs.list');
Route::get('/programs/wizard', \App\Livewire\Programs\CreateProgramWizard::class)->name('programs.wizard')
->middleware('permission:programs.create');
Route::get('/programs/create', ProgramForm::class)->name('programs.create') Route::get('/programs/create', ProgramForm::class)->name('programs.create')
->middleware('permission:programs.create'); ->middleware('permission:programs.create');
Route::get('/programs/{program}/edit', ProgramForm::class)->name('programs.edit') Route::get('/programs/{program}/edit', ProgramForm::class)->name('programs.edit')
...@@ -175,6 +177,8 @@ ...@@ -175,6 +177,8 @@
// Training Groups // Training Groups
Route::get('/groups', GroupList::class)->name('groups.list') Route::get('/groups', GroupList::class)->name('groups.list')
->middleware('permission:groups.list'); ->middleware('permission:groups.list');
Route::get('/groups/wizard', \App\Livewire\Groups\CreateGroupWizard::class)->name('groups.wizard')
->middleware('permission:groups.create');
Route::get('/groups/create', GroupForm::class)->name('groups.create') Route::get('/groups/create', GroupForm::class)->name('groups.create')
->middleware('permission:groups.create'); ->middleware('permission:groups.create');
Route::get('/groups/{group}/edit', GroupForm::class)->name('groups.edit') Route::get('/groups/{group}/edit', GroupForm::class)->name('groups.edit')
...@@ -189,6 +193,8 @@ ...@@ -189,6 +193,8 @@
->middleware('permission:enrollments.create'); ->middleware('permission:enrollments.create');
Route::get('/enrollments/transfer', TransferGroup::class)->name('enrollments.transfer') Route::get('/enrollments/transfer', TransferGroup::class)->name('enrollments.transfer')
->middleware('permission:enrollments.create'); ->middleware('permission:enrollments.create');
Route::get('/enrollments/transfer-wizard', \App\Livewire\Enrollments\TransferParticipantWizard::class)->name('enrollments.transfer-wizard')
->middleware('permission:enrollments.create');
// Wallets // Wallets
Route::get('/wallets', WalletList::class)->name('wallets.list') Route::get('/wallets', WalletList::class)->name('wallets.list')
...@@ -209,6 +215,8 @@ ...@@ -209,6 +215,8 @@
// Invoices // Invoices
Route::get('/invoices', InvoiceList::class)->name('invoices.list') Route::get('/invoices', InvoiceList::class)->name('invoices.list')
->middleware('permission:invoices.list'); ->middleware('permission:invoices.list');
Route::get('/invoices/wizard', \App\Livewire\Invoices\CreateInvoiceWizard::class)->name('invoices.wizard')
->middleware('permission:invoices.create');
Route::get('/invoices/create', InvoiceCreate::class)->name('invoices.create') Route::get('/invoices/create', InvoiceCreate::class)->name('invoices.create')
->middleware('permission:invoices.create'); ->middleware('permission:invoices.create');
Route::get('/invoices/{invoice}', InvoiceShow::class)->name('invoices.show') Route::get('/invoices/{invoice}', InvoiceShow::class)->name('invoices.show')
...@@ -217,6 +225,8 @@ ...@@ -217,6 +225,8 @@
// Facilities // Facilities
Route::get('/facilities', FacilityList::class)->name('facilities.list') Route::get('/facilities', FacilityList::class)->name('facilities.list')
->middleware('permission:facilities.list'); ->middleware('permission:facilities.list');
Route::get('/facilities/wizard', \App\Livewire\Facilities\CreateFacilityWizard::class)->name('facilities.wizard')
->middleware('permission:facilities.create');
Route::get('/facilities/create', FacilityForm::class)->name('facilities.create') Route::get('/facilities/create', FacilityForm::class)->name('facilities.create')
->middleware('permission:facilities.create'); ->middleware('permission:facilities.create');
Route::get('/facilities/{facility}/edit', FacilityForm::class)->name('facilities.edit') Route::get('/facilities/{facility}/edit', FacilityForm::class)->name('facilities.edit')
...@@ -243,6 +253,8 @@ ...@@ -243,6 +253,8 @@
// HR - Employees // HR - Employees
Route::get('/hr/employees', \App\Livewire\HR\EmployeeList::class)->name('employees.list') Route::get('/hr/employees', \App\Livewire\HR\EmployeeList::class)->name('employees.list')
->middleware('permission:employees.list'); ->middleware('permission:employees.list');
Route::get('/hr/employees/wizard', \App\Livewire\HR\CreateEmployeeWizard::class)->name('employees.wizard')
->middleware('permission:employees.create');
Route::get('/hr/employees/create', \App\Livewire\HR\EmployeeForm::class)->name('employees.create') Route::get('/hr/employees/create', \App\Livewire\HR\EmployeeForm::class)->name('employees.create')
->middleware('permission:employees.create'); ->middleware('permission:employees.create');
Route::get('/hr/employees/{employee}/edit', \App\Livewire\HR\EmployeeForm::class)->name('employees.edit') Route::get('/hr/employees/{employee}/edit', \App\Livewire\HR\EmployeeForm::class)->name('employees.edit')
...@@ -251,6 +263,8 @@ ...@@ -251,6 +263,8 @@
// HR - Trainers // HR - Trainers
Route::get('/hr/trainers', \App\Livewire\HR\TrainerList::class)->name('trainers.list') Route::get('/hr/trainers', \App\Livewire\HR\TrainerList::class)->name('trainers.list')
->middleware('permission:trainers.list'); ->middleware('permission:trainers.list');
Route::get('/hr/trainers/wizard', \App\Livewire\HR\CreateTrainerWizard::class)->name('trainers.wizard')
->middleware('permission:trainers.create');
Route::get('/hr/trainers/create', \App\Livewire\HR\TrainerForm::class)->name('trainers.create') Route::get('/hr/trainers/create', \App\Livewire\HR\TrainerForm::class)->name('trainers.create')
->middleware('permission:trainers.create'); ->middleware('permission:trainers.create');
Route::get('/hr/trainers/{trainer}/edit', \App\Livewire\HR\TrainerForm::class)->name('trainers.edit') Route::get('/hr/trainers/{trainer}/edit', \App\Livewire\HR\TrainerForm::class)->name('trainers.edit')
...@@ -273,6 +287,8 @@ ...@@ -273,6 +287,8 @@
// Pricing — Rules // Pricing — Rules
Route::get('/pricing/rules', PricingRuleList::class)->name('pricing.rules') Route::get('/pricing/rules', PricingRuleList::class)->name('pricing.rules')
->middleware('permission:pricing.list'); ->middleware('permission:pricing.list');
Route::get('/pricing/rules/wizard', \App\Livewire\Pricing\CreatePricingRuleWizard::class)->name('pricing.rules.wizard')
->middleware('permission:pricing.create');
Route::get('/pricing/rules/create', PricingRuleForm::class)->name('pricing.rules.create') Route::get('/pricing/rules/create', PricingRuleForm::class)->name('pricing.rules.create')
->middleware('permission:pricing.create'); ->middleware('permission:pricing.create');
Route::get('/pricing/rules/{pricingRule}/edit', PricingRuleForm::class)->name('pricing.rules.edit') Route::get('/pricing/rules/{pricingRule}/edit', PricingRuleForm::class)->name('pricing.rules.edit')
...@@ -295,6 +311,8 @@ ...@@ -295,6 +311,8 @@
// Inventory // Inventory
Route::get('/inventory/products', InventoryProductList::class)->name('inventory.products') Route::get('/inventory/products', InventoryProductList::class)->name('inventory.products')
->middleware('permission:inventory.list'); ->middleware('permission:inventory.list');
Route::get('/inventory/products/wizard', \App\Livewire\Inventory\CreateProductWizard::class)->name('inventory.products.wizard')
->middleware('permission:inventory.create');
Route::get('/inventory/products/create', InventoryProductForm::class)->name('inventory.products.create') Route::get('/inventory/products/create', InventoryProductForm::class)->name('inventory.products.create')
->middleware('permission:inventory.create'); ->middleware('permission:inventory.create');
Route::get('/inventory/products/{product}/edit', InventoryProductForm::class)->name('inventory.products.edit') Route::get('/inventory/products/{product}/edit', InventoryProductForm::class)->name('inventory.products.edit')
...@@ -309,6 +327,8 @@ ...@@ -309,6 +327,8 @@
->middleware('permission:inventory.list'); ->middleware('permission:inventory.list');
Route::get('/inventory/adjustments', StockAdjustment::class)->name('inventory.adjustments') Route::get('/inventory/adjustments', StockAdjustment::class)->name('inventory.adjustments')
->middleware('permission:inventory.adjust'); ->middleware('permission:inventory.adjust');
Route::get('/inventory/adjustments/wizard', \App\Livewire\Inventory\StockAdjustmentWizard::class)->name('inventory.adjustments.wizard')
->middleware('permission:inventory.adjust');
// Settings // Settings
Route::get('/settings', \App\Livewire\Settings\AcademySettings::class)->name('settings.academy') Route::get('/settings', \App\Livewire\Settings\AcademySettings::class)->name('settings.academy')
...@@ -426,6 +446,18 @@ ...@@ -426,6 +446,18 @@
Route::get('/admin/system-settings', \App\Livewire\Admin\SystemSettings::class)->name('admin.system-settings') Route::get('/admin/system-settings', \App\Livewire\Admin\SystemSettings::class)->name('admin.system-settings')
->middleware('permission:settings.manage'); ->middleware('permission:settings.manage');
// Documents
Route::get('/documents/upload/{documentableType}/{documentableId}', \App\Livewire\Documents\DocumentUploadWizard::class)
->name('documents.upload')->middleware('permission:documents.upload');
Route::get('/documents/approvals', \App\Livewire\Documents\DocumentApprovalList::class)
->name('documents.approvals')->middleware('permission:documents.approve');
Route::get('/documents/{document}/review', \App\Livewire\Documents\DocumentApproval::class)
->name('documents.review')->middleware('permission:documents.approve');
Route::get('/documents/{document}/view', [\App\Http\Controllers\DocumentController::class, 'show'])
->name('documents.view')->middleware('permission:documents.view');
Route::get('/documents/{document}/download', [\App\Http\Controllers\DocumentController::class, 'download'])
->name('documents.download')->middleware('permission:documents.view');
// Guardian Portal // Guardian Portal
Route::get('/guardian', GuardianDashboard::class)->name('guardian.dashboard') Route::get('/guardian', GuardianDashboard::class)->name('guardian.dashboard')
->middleware('permission:dashboard.view'); ->middleware('permission:dashboard.view');
......
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