Commit 1fa2ae79 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Employee wizard user step, person search in UserForm, delete fixes, cash dashboard

- Add Step 5 (User Account) to CreateEmployeeWizard: email, password, role
- Replace person dropdown in UserForm with live search by name/phone
- PersonList.deletePerson: cascades to linked user
- UserList.deleteUser: deletes user only, preserves person
- Expense form: visual radio-card payment source selector
- Dashboard: cash-on-hand card (collections - expenses this month)
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 02675f8e
...@@ -78,6 +78,19 @@ public function render() ...@@ -78,6 +78,19 @@ public function render()
->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
->sum('amount'); ->sum('amount');
$cashExpensesThisMonth = Expense::whereBetween('expense_date', [now()->startOfMonth(), now()->endOfMonth()])
->where('payment_method', 'cash')
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
->sum('amount');
$cashCollectionsThisMonth = Payment::where('status', 'confirmed')
->where('method', 'cash')
->whereBetween('created_at', [now()->startOfMonth(), now()->endOfMonth()])
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
->sum('amount');
$expectedCashOnHand = $cashCollectionsThisMonth - $cashExpensesThisMonth;
$activeParticipants = Participant::where('status', 'active') $activeParticipants = Participant::where('status', 'active')
->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
->count(); ->count();
...@@ -168,6 +181,9 @@ public function render() ...@@ -168,6 +181,9 @@ public function render()
'paymentsToday' => $paymentsToday, 'paymentsToday' => $paymentsToday,
'expensesToday' => $expensesToday, 'expensesToday' => $expensesToday,
'expensesThisMonth' => $expensesThisMonth, 'expensesThisMonth' => $expensesThisMonth,
'cashExpensesThisMonth' => $cashExpensesThisMonth,
'cashCollectionsThisMonth' => $cashCollectionsThisMonth,
'expectedCashOnHand' => $expectedCashOnHand,
'activeParticipants' => $activeParticipants, 'activeParticipants' => $activeParticipants,
// Row 2 // Row 2
'overdueInvoicesCount' => $overdueInvoicesCount, 'overdueInvoicesCount' => $overdueInvoicesCount,
......
...@@ -7,8 +7,12 @@ ...@@ -7,8 +7,12 @@
use App\Domain\HR\Services\EmployeeService; use App\Domain\HR\Services\EmployeeService;
use App\Domain\Identity\Models\Branch; use App\Domain\Identity\Models\Branch;
use App\Domain\Identity\Models\Person; use App\Domain\Identity\Models\Person;
use App\Domain\Identity\Models\Role;
use App\Domain\Identity\Services\PersonService;
use App\Domain\Shared\Exceptions\DomainException; use App\Domain\Shared\Exceptions\DomainException;
use App\Models\User;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Component; use Livewire\Component;
...@@ -18,7 +22,7 @@ ...@@ -18,7 +22,7 @@
class CreateEmployeeWizard extends Component class CreateEmployeeWizard extends Component
{ {
public int $currentStep = 1; public int $currentStep = 1;
public int $totalSteps = 5; public int $totalSteps = 6;
public bool $completed = false; public bool $completed = false;
// Step 1: Person // Step 1: Person
...@@ -48,6 +52,13 @@ class CreateEmployeeWizard extends Component ...@@ -48,6 +52,13 @@ class CreateEmployeeWizard extends Component
public string $salaryFrequency = ''; public string $salaryFrequency = '';
public string $workingHoursPerWeek = ''; public string $workingHoursPerWeek = '';
// Step 5: User Account
public bool $createUserAccount = true;
public string $userEmail = '';
public string $userPassword = '';
public string $userPasswordConfirmation = '';
public ?int $userRoleId = null;
public function mount(): void public function mount(): void
{ {
$this->authorize('employees.create'); $this->authorize('employees.create');
...@@ -61,7 +72,8 @@ public function getStepLabels(): array ...@@ -61,7 +72,8 @@ public function getStepLabels(): array
2 => 'بيانات التوظيف', 2 => 'بيانات التوظيف',
3 => 'الفرع والمسؤول', 3 => 'الفرع والمسؤول',
4 => 'الراتب', 4 => 'الراتب',
5 => 'مراجعة', 5 => 'حساب المستخدم',
6 => 'مراجعة',
]; ];
} }
...@@ -126,6 +138,12 @@ public function rulesForStep(int $step): array ...@@ -126,6 +138,12 @@ public function rulesForStep(int $step): array
'salaryFrequency' => 'required|in:' . implode(',', array_column(SalaryFrequency::cases(), 'value')), 'salaryFrequency' => 'required|in:' . implode(',', array_column(SalaryFrequency::cases(), 'value')),
'workingHoursPerWeek' => 'nullable|numeric|min:1|max:168', 'workingHoursPerWeek' => 'nullable|numeric|min:1|max:168',
], ],
5 => $this->createUserAccount ? [
'userEmail' => 'required|email|max:255|unique:users,email',
'userPassword' => 'required|string|min:8',
'userPasswordConfirmation' => 'required|same:userPassword',
'userRoleId' => 'required|exists:roles,id',
] : [],
default => [], default => [],
}; };
} }
...@@ -164,6 +182,17 @@ public function messagesForStep(int $step): array ...@@ -164,6 +182,17 @@ public function messagesForStep(int $step): array
'workingHoursPerWeek.min' => 'ساعات العمل يجب أن تكون أكثر من صفر', 'workingHoursPerWeek.min' => 'ساعات العمل يجب أن تكون أكثر من صفر',
'workingHoursPerWeek.max' => 'ساعات العمل لا يمكن أن تتجاوز 168 ساعة', 'workingHoursPerWeek.max' => 'ساعات العمل لا يمكن أن تتجاوز 168 ساعة',
], ],
5 => [
'userEmail.required' => 'البريد الإلكتروني مطلوب',
'userEmail.email' => 'صيغة البريد الإلكتروني غير صحيحة',
'userEmail.unique' => 'البريد الإلكتروني مستخدم بالفعل',
'userPassword.required' => 'كلمة المرور مطلوبة',
'userPassword.min' => 'كلمة المرور يجب أن تكون 8 أحرف على الأقل',
'userPasswordConfirmation.required' => 'تأكيد كلمة المرور مطلوب',
'userPasswordConfirmation.same' => 'تأكيد كلمة المرور غير متطابق',
'userRoleId.required' => 'يجب اختيار الصلاحيات',
'userRoleId.exists' => 'الصلاحيات المختارة غير موجودة',
],
default => [], default => [],
}; };
} }
...@@ -216,6 +245,21 @@ public function confirm(): void ...@@ -216,6 +245,21 @@ public function confirm(): void
]; ];
app(EmployeeService::class)->create($data, auth()->user()); app(EmployeeService::class)->create($data, auth()->user());
if ($this->createUserAccount) {
$user = User::create([
'academy_id' => app('current_academy')->id,
'name' => $this->personName ?: $this->personNameAr,
'name_ar' => $this->personNameAr,
'email' => $this->userEmail,
'password' => Hash::make($this->userPassword),
'role_id' => $this->userRoleId,
'person_id' => $person->id,
'status' => 'active',
]);
app(PersonService::class)->linkUser($person, $user);
}
}); });
$this->completed = true; $this->completed = true;
...@@ -245,6 +289,12 @@ private function resolveOrCreatePerson(): Person ...@@ -245,6 +289,12 @@ private function resolveOrCreatePerson(): Person
public function render() public function render()
{ {
$rolesQuery = Role::orderBy('level', 'desc');
if (! auth()->user()->is_super_admin) {
$currentUserLevel = auth()->user()->primaryRole?->level ?? 0;
$rolesQuery->where('level', '<', $currentUserLevel);
}
return view('livewire.hr.create-employee-wizard', [ return view('livewire.hr.create-employee-wizard', [
'stepLabels' => $this->getStepLabels(), 'stepLabels' => $this->getStepLabels(),
'branches' => Branch::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar']), 'branches' => Branch::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar']),
...@@ -253,6 +303,7 @@ public function render() ...@@ -253,6 +303,7 @@ public function render()
: collect(), : collect(),
'employmentTypes' => EmploymentType::cases(), 'employmentTypes' => EmploymentType::cases(),
'salaryFrequencies' => SalaryFrequency::cases(), 'salaryFrequencies' => SalaryFrequency::cases(),
'roles' => $rolesQuery->get(['id', 'name_ar', 'slug', 'level']),
]); ]);
} }
} }
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
use App\Domain\Identity\Models\Person; use App\Domain\Identity\Models\Person;
use App\Livewire\Concerns\AppliesRoleScope; use App\Livewire\Concerns\AppliesRoleScope;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -42,6 +44,23 @@ public function updatedClassification(): void ...@@ -42,6 +44,23 @@ public function updatedClassification(): void
$this->resetPage(); $this->resetPage();
} }
public function deletePerson(int $id): void
{
$this->authorize('people.delete');
DB::transaction(function () use ($id) {
$person = Person::findOrFail($id);
if ($person->user_id) {
User::where('id', $person->user_id)->first()?->delete();
}
$person->delete();
});
session()->flash('success', 'تم حذف الشخص بنجاح');
}
public function render() public function render()
{ {
$query = Person::query() $query = Person::query()
......
...@@ -38,6 +38,10 @@ class UserForm extends Component ...@@ -38,6 +38,10 @@ class UserForm extends Component
public ?int $person_id = null; public ?int $person_id = null;
public string $status = 'active'; public string $status = 'active';
// ─── Person Search ───────────────────────────────────────────
public string $personSearch = '';
public ?string $selectedPersonName = null;
// ─── Role-Aware: Selected role slug (computed from role_id) ── // ─── Role-Aware: Selected role slug (computed from role_id) ──
public string $selectedRoleSlug = ''; public string $selectedRoleSlug = '';
...@@ -80,6 +84,9 @@ public function mount(?User $user = null): void ...@@ -80,6 +84,9 @@ public function mount(?User $user = null): void
$this->role_id = $user->role_id; $this->role_id = $user->role_id;
$this->person_id = $user->person_id; $this->person_id = $user->person_id;
$this->status = $user->status ?? 'active'; $this->status = $user->status ?? 'active';
if ($user->person) {
$this->selectedPersonName = $user->person->name_ar ?? $user->person->name;
}
$this->resolveRoleSlug(); $this->resolveRoleSlug();
$this->detectExistingEntities(); $this->detectExistingEntities();
} else { } else {
...@@ -124,6 +131,41 @@ private function detectExistingEntities(): void ...@@ -124,6 +131,41 @@ private function detectExistingEntities(): void
} }
} }
public function getPersonSearchResultsProperty(): \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}%");
})->limit(10)->get(['id', 'name', 'name_ar', 'phone']);
}
public function selectPerson(int $id): void
{
$person = Person::findOrFail($id);
$this->person_id = $person->id;
$this->selectedPersonName = $person->name_ar ?? $person->name;
$this->personSearch = '';
if (! $this->name_ar && $person->name_ar) {
$this->name_ar = $person->name_ar;
}
if (! $this->name && $person->name) {
$this->name = $person->name;
}
}
public function clearPerson(): void
{
$this->person_id = null;
$this->selectedPersonName = null;
$this->personSearch = '';
}
public function updatedRoleId(): void public function updatedRoleId(): void
{ {
$this->resolveRoleSlug(); $this->resolveRoleSlug();
...@@ -468,9 +510,6 @@ public function render() ...@@ -468,9 +510,6 @@ public function render()
$viewData = [ $viewData = [
'roles' => $rolesQuery->get(['id', 'name_ar', 'slug', 'level']), 'roles' => $rolesQuery->get(['id', 'name_ar', 'slug', 'level']),
'people' => Person::orderBy('name_ar')
->get(['id', 'name_ar'])
->map(fn ($p) => ['id' => $p->id, 'name' => $p->name_ar]),
'branches' => Branch::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar']), 'branches' => Branch::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar']),
'compensationModels' => CompensationModel::cases(), 'compensationModels' => CompensationModel::cases(),
'employmentTypes' => EmploymentType::cases(), 'employmentTypes' => EmploymentType::cases(),
......
...@@ -31,6 +31,19 @@ public function toggleActive(string $uuid): void ...@@ -31,6 +31,19 @@ public function toggleActive(string $uuid): void
$user->update(['status' => $newStatus]); $user->update(['status' => $newStatus]);
} }
public function deleteUser(string $uuid): void
{
$this->authorize('users.delete');
$user = User::where('uuid', $uuid)->firstOrFail();
if ($user->person_id) {
$user->person?->update(['user_id' => null]);
}
$user->delete();
session()->flash('success', 'تم حذف المستخدم بنجاح');
}
public function render() public function render()
{ {
$query = User::query() $query = User::query()
......
...@@ -86,6 +86,45 @@ ...@@ -86,6 +86,45 @@
</div> </div>
</div> </div>
<!-- Cash On Hand Card -->
@can('expenses.create')
<div class="mb-6">
<div class="bg-gradient-to-l from-green-50 to-emerald-50 rounded-xl shadow-sm border border-green-200 p-5">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-3">
<div class="w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center">
<svg class="w-6 h-6 text-green-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z"/>
</svg>
</div>
<div>
<h3 class="text-lg font-bold text-green-900">{{ __('الكاش المفروض في الخزنة') }}</h3>
<p class="text-xs text-green-600">{{ now()->translatedFormat('F Y') }}</p>
</div>
</div>
<div class="text-end">
<p class="text-3xl font-bold {{ $expectedCashOnHand >= 0 ? 'text-green-700' : 'text-red-600' }}" dir="ltr">
{{ number_format(abs($expectedCashOnHand) / 100, 0) }} <span class="text-sm">{{ __('ج.م') }}</span>
</p>
@if($expectedCashOnHand < 0)
<p class="text-xs text-red-600 font-medium">{{ __('عجز') }}</p>
@endif
</div>
</div>
<div class="grid grid-cols-2 gap-4 pt-3 border-t border-green-200">
<div>
<p class="text-xs text-gray-500 mb-0.5">{{ __('تحصيلات كاش هذا الشهر') }}</p>
<p class="text-sm font-bold text-emerald-700" dir="ltr">{{ number_format($cashCollectionsThisMonth / 100, 0) }} {{ __('ج.م') }}</p>
</div>
<div>
<p class="text-xs text-gray-500 mb-0.5">{{ __('مصروفات كاش هذا الشهر') }}</p>
<p class="text-sm font-bold text-red-600" dir="ltr">{{ number_format($cashExpensesThisMonth / 100, 0) }} {{ __('ج.م') }}</p>
</div>
</div>
</div>
</div>
@endcan
<!-- Row 2: Needs Attention (only show if any count > 0) --> <!-- Row 2: Needs Attention (only show if any count > 0) -->
@if($overdueInvoicesCount > 0 || $pendingPayslips > 0 || $pendingDocuments > 0 || $lowStockItems > 0 || $expiringMedicalCerts > 0 || $attendanceNotTaken > 0) @if($overdueInvoicesCount > 0 || $pendingPayslips > 0 || $pendingDocuments > 0 || $lowStockItems > 0 || $expiringMedicalCerts > 0 || $attendanceNotTaken > 0)
<div class="mb-6"> <div class="mb-6">
......
...@@ -52,16 +52,56 @@ class="w-full rounded-lg border-gray-300 text-sm py-2.5" ...@@ -52,16 +52,56 @@ class="w-full rounded-lg border-gray-300 text-sm py-2.5"
@error('recipient_name') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror @error('recipient_name') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div> </div>
{{-- Payment Method --}} {{-- Payment Source --}}
<div> <div class="md:col-span-2">
<label class="block text-sm text-gray-600 mb-1">{{ __('طريقة الدفع') }} <span class="text-red-500">*</span></label> <label class="block text-sm text-gray-600 mb-2">{{ __('الفلوس طلعت منين؟') }} <span class="text-red-500">*</span></label>
<select wire:model="payment_method" class="w-full rounded-lg border-gray-300 text-sm py-2.5"> <div class="grid grid-cols-2 sm:grid-cols-5 gap-2" x-data="{ method: @entangle('payment_method') }">
<option value="cash">{{ __('نقدي') }}</option> <label class="relative cursor-pointer">
<option value="card">{{ __('بطاقة') }}</option> <input type="radio" wire:model="payment_method" value="cash" class="peer sr-only">
<option value="bank_transfer">{{ __('تحويل بنكي') }}</option> <div class="peer-checked:ring-2 peer-checked:ring-green-500 peer-checked:border-green-500 peer-checked:bg-green-50 flex flex-col items-center gap-1.5 p-3 rounded-xl border-2 border-gray-200 hover:border-green-300 transition-all">
<option value="cheque">{{ __('شيك') }}</option> <svg class="w-7 h-7 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<option value="other">{{ __('أخرى') }}</option> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z"/>
</select> </svg>
<span class="text-xs font-medium text-gray-700">{{ __('من الخزنة (كاش)') }}</span>
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model="payment_method" value="card" class="peer sr-only">
<div class="peer-checked:ring-2 peer-checked:ring-blue-500 peer-checked:border-blue-500 peer-checked:bg-blue-50 flex flex-col items-center gap-1.5 p-3 rounded-xl border-2 border-gray-200 hover:border-blue-300 transition-all">
<svg class="w-7 h-7 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/>
</svg>
<span class="text-xs font-medium text-gray-700">{{ __('فيزا / بطاقة') }}</span>
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model="payment_method" value="bank_transfer" class="peer sr-only">
<div class="peer-checked:ring-2 peer-checked:ring-purple-500 peer-checked:border-purple-500 peer-checked:bg-purple-50 flex flex-col items-center gap-1.5 p-3 rounded-xl border-2 border-gray-200 hover:border-purple-300 transition-all">
<svg class="w-7 h-7 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 14v3m4-3v3m4-3v3M3 21h18M3 10h18M3 7l9-4 9 4M4 10h16v11H4V10z"/>
</svg>
<span class="text-xs font-medium text-gray-700">{{ __('تحويل بنكي') }}</span>
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model="payment_method" value="cheque" class="peer sr-only">
<div class="peer-checked:ring-2 peer-checked:ring-amber-500 peer-checked:border-amber-500 peer-checked:bg-amber-50 flex flex-col items-center gap-1.5 p-3 rounded-xl border-2 border-gray-200 hover:border-amber-300 transition-all">
<svg class="w-7 h-7 text-amber-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>
<span class="text-xs font-medium text-gray-700">{{ __('شيك') }}</span>
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model="payment_method" value="other" class="peer sr-only">
<div class="peer-checked:ring-2 peer-checked:ring-gray-500 peer-checked:border-gray-500 peer-checked:bg-gray-50 flex flex-col items-center gap-1.5 p-3 rounded-xl border-2 border-gray-200 hover:border-gray-300 transition-all">
<svg class="w-7 h-7 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h.01M12 12h.01M19 12h.01M6 12a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0z"/>
</svg>
<span class="text-xs font-medium text-gray-700">{{ __('أخرى') }}</span>
</div>
</label>
</div>
@error('payment_method') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror @error('payment_method') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div> </div>
......
...@@ -4,22 +4,22 @@ ...@@ -4,22 +4,22 @@
</div> </div>
{{-- Step Indicator --}} {{-- Step Indicator --}}
<div class="flex items-center justify-center gap-2 mb-8"> <div class="flex items-center justify-center gap-2 mb-8 overflow-x-auto">
@foreach($stepLabels as $num => $label) @foreach($stepLabels as $num => $label)
<button wire:click="goToStep({{ $num }})" <button wire:click="goToStep({{ $num }})"
class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}"> class="flex items-center gap-1.5 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<span class="flex items-center justify-center w-8 h-8 rounded-full text-sm font-bold <span class="flex items-center justify-center w-7 h-7 sm:w-8 sm:h-8 rounded-full text-xs sm:text-sm font-bold
{{ $num < $currentStep ? 'bg-green-500 text-white' : ($num === $currentStep ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-500') }}"> {{ $num < $currentStep ? 'bg-green-500 text-white' : ($num === $currentStep ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-500') }}">
@if($num < $currentStep) @if($num < $currentStep)
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg> <svg class="w-3.5 h-3.5" 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 @else
{{ $num }} {{ $num }}
@endif @endif
</span> </span>
<span class="hidden sm:inline text-sm {{ $num === $currentStep ? 'text-blue-700 font-medium' : 'text-gray-500' }}">{{ $label }}</span> <span class="hidden lg:inline text-xs {{ $num === $currentStep ? 'text-blue-700 font-medium' : 'text-gray-500' }}">{{ $label }}</span>
</button> </button>
@if(!$loop->last) @if(!$loop->last)
<div class="w-8 h-0.5 {{ $num < $currentStep ? 'bg-green-400' : 'bg-gray-200' }}"></div> <div class="w-5 sm:w-8 h-0.5 {{ $num < $currentStep ? 'bg-green-400' : 'bg-gray-200' }}"></div>
@endif @endif
@endforeach @endforeach
</div> </div>
...@@ -212,8 +212,64 @@ class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:r ...@@ -212,8 +212,64 @@ class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:r
</div> </div>
@endif @endif
{{-- Step 5: Review --}} {{-- Step 5: User Account --}}
@if($currentStep === 5 && !$completed) @if($currentStep === 5)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('حساب المستخدم') }}</h2>
<label class="flex items-center gap-3 mb-4 cursor-pointer">
<input type="checkbox" wire:model.live="createUserAccount" class="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-blue-500">
<span class="text-sm font-medium text-gray-700">{{ __('إنشاء حساب مستخدم للموظف') }}</span>
</label>
@if($createUserAccount)
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 pt-3 border-t border-gray-100">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('البريد الإلكتروني (اسم المستخدم)') }} <span class="text-red-500">*</span></label>
<input type="email" wire:model="userEmail" dir="ltr"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm">
<p class="mt-1 text-xs text-gray-500">{{ __('سيُستخدم لتسجيل الدخول إلى النظام') }}</p>
@error('userEmail') <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="userRoleId" 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($roles as $role)
<option value="{{ $role->id }}">{{ $role->name_ar }} ({{ $role->slug }})</option>
@endforeach
</select>
@error('userRoleId') <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="password" wire:model="userPassword"
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('userPassword') <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="password" wire:model="userPasswordConfirmation"
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('userPasswordConfirmation') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
</div>
<div class="mt-3 p-3 bg-blue-50 rounded-lg border border-blue-100">
<p class="text-xs text-blue-700">
<svg xmlns="http://www.w3.org/2000/svg" class="inline w-4 h-4 me-1" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{{ __('سيتمكن الموظف من تسجيل الدخول للنظام بهذا البريد وكلمة المرور') }}
</p>
</div>
@else
<div class="p-4 bg-gray-50 rounded-lg border border-gray-200">
<p class="text-sm text-gray-600">{{ __('لن يتم إنشاء حساب مستخدم. يمكنك إنشاؤه لاحقاً من صفحة المستخدمين.') }}</p>
</div>
@endif
@endif
{{-- Step 6: Review --}}
@if($currentStep === 6 && !$completed)
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('مراجعة وتأكيد') }}</h2> <h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('مراجعة وتأكيد') }}</h2>
<dl class="space-y-3"> <dl class="space-y-3">
<div class="flex justify-between py-2 border-b border-gray-100"> <div class="flex justify-between py-2 border-b border-gray-100">
...@@ -250,6 +306,17 @@ class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:r ...@@ -250,6 +306,17 @@ class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:r
<dd class="text-sm font-medium text-gray-800">{{ $workingHoursPerWeek }} {{ __('ساعة/أسبوع') }}</dd> <dd class="text-sm font-medium text-gray-800">{{ $workingHoursPerWeek }} {{ __('ساعة/أسبوع') }}</dd>
</div> </div>
@endif @endif
@if($createUserAccount)
<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-green-700">{{ __('سيتم إنشاؤه') }} ({{ $userEmail }})</dd>
</div>
@else
<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-500">{{ __('لن يتم إنشاؤه') }}</dd>
</div>
@endif
</dl> </dl>
@endif @endif
......
...@@ -97,6 +97,11 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin ...@@ -97,6 +97,11 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin
@can('participants.update') @can('participants.update')
<a href="{{ route('people.edit', $person) }}" wire:navigate class="text-green-600 hover:text-green-800 text-sm">{{ __('تعديل') }}</a> <a href="{{ route('people.edit', $person) }}" wire:navigate class="text-green-600 hover:text-green-800 text-sm">{{ __('تعديل') }}</a>
@endcan @endcan
@can('people.delete')
<button wire:click="deletePerson({{ $person->id }})"
wire:confirm="{{ __('هل أنت متأكد؟ سيتم حذف الشخص وأي حساب مستخدم مرتبط به.') }}"
class="text-red-600 hover:text-red-800 text-sm">{{ __('حذف') }}</button>
@endcan
</td> </td>
</tr> </tr>
@empty @empty
......
...@@ -62,13 +62,30 @@ ...@@ -62,13 +62,30 @@
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('ربط بشخص (اختياري)') }}</label> <label class="block text-sm font-medium text-gray-700 mb-1">{{ __('ربط بشخص (اختياري)') }}</label>
<select wire:model="person_id" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"> @if($person_id && $selectedPersonName)
<option value="">— {{ __('إنشاء سجل جديد تلقائياً') }} —</option> <div class="flex items-center gap-2 px-4 py-2.5 bg-green-50 border border-green-200 rounded-lg">
@foreach($people as $p) <span class="text-sm font-medium text-green-800 flex-1">{{ $selectedPersonName }}</span>
<option value="{{ $p['id'] }}">{{ $p['name'] }}</option> <button type="button" wire:click="clearPerson" class="text-xs text-red-600 hover:text-red-800 font-medium">{{ __('إلغاء') }}</button>
@endforeach </div>
</select> @else
<p class="mt-1 text-xs text-gray-500">{{ __('إذا لم يتم اختيار شخص، سيتم إنشاء سجل شخص تلقائياً عند الحاجة') }}</p> <div class="relative">
<input type="text" wire:model.live.debounce.300ms="personSearch"
placeholder="{{ __('ابحث بالاسم أو رقم الهاتف...') }}"
class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
@if($this->personSearchResults->isNotEmpty())
<div class="absolute z-20 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg max-h-48 overflow-y-auto">
@foreach($this->personSearchResults as $p)
<button type="button" wire:click="selectPerson({{ $p->id }})"
class="w-full text-start px-4 py-2.5 hover:bg-blue-50 transition-colors border-b border-gray-100 last:border-0">
<div class="text-sm font-medium text-gray-800">{{ $p->name_ar ?? $p->name }}</div>
<div class="text-xs text-gray-500" dir="ltr">{{ $p->phone ?? '' }}</div>
</button>
@endforeach
</div>
@endif
</div>
@endif
<p class="mt-1 text-xs text-gray-500">{{ __('ابحث بالاسم أو رقم الهاتف لربط المستخدم بشخص موجود') }}</p>
</div> </div>
</div> </div>
</div> </div>
......
<div> <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
<!-- Header --> <!-- Header -->
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-4 sm:mb-6"> <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-4 sm:mb-6">
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">المستخدمين</h1> <h1 class="text-xl sm:text-2xl font-bold text-gray-800">المستخدمين</h1>
...@@ -73,10 +77,19 @@ class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 f ...@@ -73,10 +77,19 @@ class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 f
@endif @endif
</td> </td>
<td class="px-4 py-3 text-center"> <td class="px-4 py-3 text-center">
@can('users.update') <div class="flex items-center justify-center gap-2">
<a href="{{ route('users.edit', $u) }}" wire:navigate @can('users.update')
class="inline-flex items-center justify-center text-blue-600 hover:text-blue-800 text-sm py-2 min-h-[44px]">تعديل</a> <a href="{{ route('users.edit', $u) }}" wire:navigate
@endcan class="text-blue-600 hover:text-blue-800 text-sm">تعديل</a>
@endcan
@can('users.delete')
@if(!$u->is_super_admin)
<button wire:click="deleteUser('{{ $u->uuid }}')"
wire:confirm="{{ __('هل أنت متأكد من حذف هذا المستخدم؟') }}"
class="text-red-600 hover:text-red-800 text-sm">حذف</button>
@endif
@endcan
</div>
</td> </td>
</tr> </tr>
@empty @empty
...@@ -120,7 +133,14 @@ class="inline-flex items-center justify-center text-blue-600 hover:text-blue-800 ...@@ -120,7 +133,14 @@ class="inline-flex items-center justify-center text-blue-600 hover:text-blue-800
@endif @endif
@can('users.update') @can('users.update')
<a href="{{ route('users.edit', $u) }}" wire:navigate <a href="{{ route('users.edit', $u) }}" wire:navigate
class="inline-flex items-center justify-center text-blue-600 hover:text-blue-800 text-sm py-2 min-h-[44px]">تعديل</a> class="text-blue-600 hover:text-blue-800 text-sm">تعديل</a>
@endcan
@can('users.delete')
@if(!$u->is_super_admin)
<button wire:click="deleteUser('{{ $u->uuid }}')"
wire:confirm="{{ __('هل أنت متأكد من حذف هذا المستخدم؟') }}"
class="text-red-600 hover:text-red-800 text-sm">حذف</button>
@endif
@endcan @endcan
</div> </div>
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment