Commit d74bc423 authored by Mahmoud Aglan's avatar Mahmoud Aglan

kokowawa

parent 8ec37422
[LocalizedFileNames]
Screenshot 2026-07-06 023737.png=@Screenshot 2026-07-06 023737,0
Screenshot 2026-07-06 023724.png=@Screenshot 2026-07-06 023724,0
Screenshot 2026-07-06 023709.png=@Screenshot 2026-07-06 023709,0
Screenshot 2026-07-06 023654.png=@Screenshot 2026-07-06 023654,0
Screenshot 2026-07-06 023646.png=@Screenshot 2026-07-06 023646,0
Screenshot 2026-07-06 023639.png=@Screenshot 2026-07-06 023639,0
Screenshot 2026-07-06 023625.png=@Screenshot 2026-07-06 023625,0
Screenshot 2026-07-06 023613.png=@Screenshot 2026-07-06 023613,0
Screenshot 2026-07-06 023607.png=@Screenshot 2026-07-06 023607,0
Screenshot 2026-07-06 023552.png=@Screenshot 2026-07-06 023552,0
Screenshot 2026-07-06 023546.png=@Screenshot 2026-07-06 023546,0
Screenshot 2026-07-06 023537.png=@Screenshot 2026-07-06 023537,0
Screenshot 2026-07-06 023525.png=@Screenshot 2026-07-06 023525,0
Screenshot 2026-07-06 023509.png=@Screenshot 2026-07-06 023509,0
Screenshot 2026-07-06 023452.png=@Screenshot 2026-07-06 023452,0
Screenshot 2026-07-06 023439.png=@Screenshot 2026-07-06 023439,0
Screenshot 2026-07-06 023432.png=@Screenshot 2026-07-06 023432,0
Screenshot 2026-07-06 023427.png=@Screenshot 2026-07-06 023427,0
Screenshot 2026-07-06 023412.png=@Screenshot 2026-07-06 023412,0
Screenshot 2026-07-06 023403.png=@Screenshot 2026-07-06 023403,0
Screenshot 2026-07-06 154014.png=@Screenshot 2026-07-06 154014,0
Screenshot 2026-07-06 154000.png=@Screenshot 2026-07-06 154000,0
Screenshot 2026-07-06 153949.png=@Screenshot 2026-07-06 153949,0
Screenshot 2026-07-06 153941.png=@Screenshot 2026-07-06 153941,0
Screenshot 2026-07-06 153932.png=@Screenshot 2026-07-06 153932,0
Screenshot 2026-07-06 153923.png=@Screenshot 2026-07-06 153923,0
Screenshot 2026-07-06 153916.png=@Screenshot 2026-07-06 153916,0
Screenshot 2026-07-06 153909.png=@Screenshot 2026-07-06 153909,0
Screenshot 2026-07-06 153821.png=@Screenshot 2026-07-06 153821,0
Screenshot 2026-07-06 153812.png=@Screenshot 2026-07-06 153812,0
Screenshot 2026-07-06 153802.png=@Screenshot 2026-07-06 153802,0
Screenshot 2026-07-06 153750.png=@Screenshot 2026-07-06 153750,0
Screenshot 2026-07-06 153731.png=@Screenshot 2026-07-06 153731,0
Screenshot 2026-07-06 153659.png=@Screenshot 2026-07-06 153659,0
Screenshot 2026-07-06 153645.png=@Screenshot 2026-07-06 153645,0
Screenshot 2026-07-06 153632.png=@Screenshot 2026-07-06 153632,0
Screenshot 2026-07-06 153625.png=@Screenshot 2026-07-06 153625,0
Screenshot 2026-07-06 153620.png=@Screenshot 2026-07-06 153620,0
Screenshot 2026-07-06 153554.png=@Screenshot 2026-07-06 153554,0
Screenshot 2026-07-06 153548.png=@Screenshot 2026-07-06 153548,0
Screenshot 2026-07-06 153541.png=@Screenshot 2026-07-06 153541,0
......@@ -5,6 +5,7 @@
use App\Domain\Financial\Events\PaymentPlanDefaulted;
use App\Domain\Financial\Models\Installment;
use App\Domain\Financial\Models\PaymentPlan;
use App\Domain\Shared\Models\SystemSetting;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
......@@ -13,12 +14,12 @@ class DetectPaymentPlanDefaults extends Command
protected $signature = 'payments:detect-defaults';
protected $description = 'كشف التخلف عن خطط الدفع';
private const GRACE_DAYS = 7;
private const DEFAULT_THRESHOLD = 2; // missed installments to consider plan defaulted
private const DEFAULT_THRESHOLD = 2;
public function handle(): int
{
$graceCutoff = now()->subDays(self::GRACE_DAYS)->toDateString();
$graceDays = (int) SystemSetting::get('installment_grace_days', 7);
$graceCutoff = now()->subDays($graceDays)->toDateString();
// Find pending installments that are overdue past grace period
$overdueInstallments = Installment::where('status', 'pending')
......
<?php
namespace App\Console\Commands;
use App\Domain\Identity\Models\Role;
use App\Domain\Shared\Models\Academy;
use App\Models\User;
use Database\Seeders\FinancialAccountsSeeder;
use Database\Seeders\NotificationTemplateSeeder;
use Database\Seeders\PaymentNotificationTemplateSeeder;
use Database\Seeders\PermissionSeeder;
use Database\Seeders\RolesAndPermissionsSeeder;
use Database\Seeders\SystemSettingsSeeder;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class SetupFreshInstall extends Command
{
protected $signature = 'el-captain:setup {--no-interaction}';
protected $description = 'First-time setup: create academy, admin user, and seed reference data';
public function handle(): int
{
if (Academy::count() > 0) {
$this->warn('Academy already exists. Skipping setup.');
return self::SUCCESS;
}
$this->info('Setting up El Captain for first use...');
$academyNameAr = env('ACADEMY_NAME_AR', 'أكاديمية الكابتن');
$academyNameEn = env('ACADEMY_NAME_EN', 'El Captain Academy');
$adminEmail = env('ADMIN_EMAIL', 'admin@example.com');
$adminPassword = env('ADMIN_PASSWORD', 'password123');
$adminName = env('ADMIN_NAME', 'مدير النظام');
$slug = Str::slug($academyNameEn);
$academy = Academy::create([
'name' => $academyNameEn,
'name_ar' => $academyNameAr,
'slug' => $slug,
'email' => $adminEmail,
'currency' => 'EGP',
'timezone' => 'Africa/Cairo',
'locale' => 'ar',
'status' => 'active',
]);
$this->info("Academy created: {$academyNameAr}");
app()->instance('current_academy', $academy);
$admin = User::create([
'academy_id' => $academy->id,
'name' => $adminName,
'name_ar' => $adminName,
'email' => $adminEmail,
'password' => Hash::make($adminPassword),
'status' => 'active',
'is_super_admin' => true,
]);
$this->info("Admin user created: {$adminEmail}");
$this->call('db:seed', ['--class' => FinancialAccountsSeeder::class, '--force' => true]);
$this->call('db:seed', ['--class' => RolesAndPermissionsSeeder::class, '--force' => true]);
$this->call('db:seed', ['--class' => PermissionSeeder::class, '--force' => true]);
$this->call('db:seed', ['--class' => SystemSettingsSeeder::class, '--force' => true]);
$this->call('db:seed', ['--class' => PaymentNotificationTemplateSeeder::class, '--force' => true]);
if (class_exists(NotificationTemplateSeeder::class)) {
$this->call('db:seed', ['--class' => NotificationTemplateSeeder::class, '--force' => true]);
}
$ownerRole = Role::where('academy_id', $academy->id)
->where('slug', 'academy_owner')
->first();
if ($ownerRole) {
$admin->update(['role_id' => $ownerRole->id]);
$admin->roles()->syncWithoutDetaching([$ownerRole->id]);
}
$this->info('Roles, permissions, and settings seeded.');
$this->newLine();
$this->components->info("Setup complete! Log in at your app URL with: {$adminEmail}");
return self::SUCCESS;
}
}
......@@ -22,7 +22,7 @@ class TrainingProgram extends Model
protected array $uniqueFieldsToMangle = ['slug'];
protected $fillable = [
'academy_id', 'activity_id', 'branch_id',
'academy_id', 'activity_id', 'branch_id', 'default_trainer_id',
'name', 'name_ar', 'slug', 'description', 'description_ar',
'objectives', 'skill_level', 'age_min', 'age_max', 'gender',
'sessions_per_week', 'session_duration_minutes', 'total_sessions', 'program_duration_weeks',
......@@ -71,6 +71,11 @@ public function branch(): BelongsTo
return $this->belongsTo(Branch::class);
}
public function defaultTrainer(): BelongsTo
{
return $this->belongsTo(User::class, 'default_trainer_id');
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
......
......@@ -25,6 +25,7 @@ public function create(array $data, User $actor): TrainingGroup
$program = TrainingProgram::findOrFail($data['training_program_id']);
return TrainingGroup::create(array_merge($data, [
'head_trainer_id' => $data['head_trainer_id'] ?? $program->default_trainer_id,
'max_capacity' => $data['max_capacity'] ?? $program->max_participants,
'status' => 'forming',
'status_changed_at' => now(),
......
......@@ -49,6 +49,7 @@ private function createDefaultGroup(TrainingProgram $program, User $actor): Trai
'academy_id' => $program->academy_id,
'training_program_id' => $program->id,
'branch_id' => $program->branch_id,
'head_trainer_id' => $program->default_trainer_id,
'name' => "Group {$program->name} 1",
'name_ar' => "مجموعة {$programName} 1",
'code' => $code,
......
......@@ -43,6 +43,13 @@ class SystemSettings extends Component
'refund_approval_threshold' => ['type' => 'number', 'label' => 'حد مبلغ الاسترداد للموافقة (قروش)', 'step' => '1', 'min' => 0],
'installment_late_fee' => ['type' => 'number', 'label' => 'غرامة تأخير القسط (قروش)', 'step' => '1', 'min' => 0],
'auto_overdue_after_days' => ['type' => 'number', 'label' => 'أيام لتحويل الفاتورة لمتأخرة', 'step' => '1', 'min' => 1],
'installment_enabled' => ['type' => 'boolean', 'label' => 'تفعيل نظام التقسيط'],
'installment_min_down_payment_percent' => ['type' => 'number', 'label' => 'الحد الأدنى للمقدم (%)', 'step' => '1', 'min' => 0, 'max' => 100],
'installment_max_down_payment_percent' => ['type' => 'number', 'label' => 'الحد الأقصى للمقدم (%)', 'step' => '1', 'min' => 0, 'max' => 100],
'installment_max_months' => ['type' => 'number', 'label' => 'الحد الأقصى لعدد أشهر التقسيط', 'step' => '1', 'min' => 1, 'max' => 36],
'installment_min_amount' => ['type' => 'number', 'label' => 'الحد الأدنى لمبلغ الفاتورة للتقسيط (قروش)', 'step' => '1', 'min' => 0],
'installment_allowed_frequencies' => ['type' => 'select', 'label' => 'دورية الأقساط الافتراضية', 'options' => ['weekly' => 'أسبوعي', 'biweekly' => 'نصف شهري', 'monthly' => 'شهري', 'quarterly' => 'ربع سنوي']],
'installment_grace_days' => ['type' => 'number', 'label' => 'أيام السماح قبل احتساب التأخير', 'step' => '1', 'min' => 0],
],
'attendance' => [
'participant_grace_period_minutes' => ['type' => 'number', 'label' => 'فترة السماح للمشتركين (دقيقة)', 'step' => '1', 'min' => 0],
......@@ -120,6 +127,13 @@ class SystemSettings extends Component
'refund_approval_threshold' => '50000',
'installment_late_fee' => '500',
'auto_overdue_after_days' => '3',
'installment_enabled' => '1',
'installment_min_down_payment_percent' => '20',
'installment_max_down_payment_percent' => '70',
'installment_max_months' => '12',
'installment_min_amount' => '10000',
'installment_allowed_frequencies' => 'monthly',
'installment_grace_days' => '7',
// attendance
'participant_grace_period_minutes' => '15',
'trainer_grace_period_minutes' => '10',
......
......@@ -57,6 +57,11 @@ public function updatedDateTo(): void
$this->resetPage();
}
public function updatedFilter(): void
{
$this->resetPage();
}
public function render()
{
$branchId = $this->getActiveBranchId();
......
......@@ -46,6 +46,16 @@ public function updatedActionFilter(): void
$this->resetPage();
}
public function updatedDateFrom(): void
{
$this->resetPage();
}
public function updatedDateTo(): void
{
$this->resetPage();
}
public function toggleExpand(int $id): void
{
$this->expandedRow = $this->expandedRow === $id ? 0 : $id;
......
......@@ -22,6 +22,11 @@ public function updatedSearch(): void
$this->resetPage();
}
public function updatedShowInactive(): void
{
$this->resetPage();
}
public function toggleActive(string $uuid): void
{
$branch = Branch::where('uuid', $uuid)->firstOrFail();
......
......@@ -4,6 +4,7 @@
use App\Domain\Financial\Models\Invoice;
use App\Domain\Financial\Models\PaymentPlan;
use App\Domain\Shared\Models\SystemSetting;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Layout;
......@@ -19,11 +20,26 @@ class PaymentPlanCreate extends Component
public int $downPayment = 0;
public array $installments = [];
// Settings-driven constraints
public int $maxMonths = 12;
public int $minDownPaymentPercent = 20;
public int $maxDownPaymentPercent = 70;
public int $minInvoiceAmount = 10000;
public int $graceDays = 7;
public function mount(?int $invoice = null): void
{
$this->authorize('invoices.create');
$this->invoiceId = $invoice;
$this->startDate = now()->addDays(7)->toDateString();
$this->maxMonths = (int) SystemSetting::get('installment_max_months', 12);
$this->minDownPaymentPercent = (int) SystemSetting::get('installment_min_down_payment_percent', 20);
$this->maxDownPaymentPercent = (int) SystemSetting::get('installment_max_down_payment_percent', 70);
$this->minInvoiceAmount = (int) SystemSetting::get('installment_min_amount', 10000);
$this->graceDays = (int) SystemSetting::get('installment_grace_days', 7);
$this->frequency = SystemSetting::get('installment_allowed_frequencies', 'monthly');
$this->calculateInstallments();
}
......@@ -91,6 +107,31 @@ public function save(): void
$invoice = Invoice::findOrFail($this->invoiceId);
if ($invoice->due_amount < $this->minInvoiceAmount) {
session()->flash('error', __('مبلغ الفاتورة أقل من الحد الأدنى للتقسيط'));
return;
}
if ($this->installmentCount > $this->maxMonths) {
session()->flash('error', __('عدد الأقساط يتجاوز الحد الأقصى المسموح') . " ({$this->maxMonths})");
return;
}
$downPaymentPiasters = $this->downPayment * 100;
$downPaymentPercent = $invoice->due_amount > 0
? round(($downPaymentPiasters / $invoice->due_amount) * 100)
: 0;
if ($downPaymentPercent < $this->minDownPaymentPercent) {
session()->flash('error', __('المقدم أقل من الحد الأدنى المطلوب') . " ({$this->minDownPaymentPercent}%)");
return;
}
if ($downPaymentPercent > $this->maxDownPaymentPercent) {
session()->flash('error', __('المقدم يتجاوز الحد الأقصى المسموح') . " ({$this->maxDownPaymentPercent}%)");
return;
}
DB::transaction(function () use ($invoice) {
$plan = PaymentPlan::create([
'academy_id' => $invoice->academy_id,
......@@ -130,6 +171,10 @@ public function render()
return view('livewire.financial.payment-plan-create', [
'invoice' => $invoice,
'invoices' => $invoices,
'maxMonths' => $this->maxMonths,
'minDownPaymentPercent' => $this->minDownPaymentPercent,
'maxDownPaymentPercent' => $this->maxDownPaymentPercent,
'minInvoiceAmount' => $this->minInvoiceAmount,
]);
}
}
......@@ -41,6 +41,11 @@ public function updatedSearch(): void
$this->resetPage();
}
public function updatedStatusFilter(): void
{
$this->resetPage();
}
public function openCreateModal(): void
{
$this->reset(['selectedTrainerId', 'amount', 'installmentsCount', 'reason']);
......
......@@ -21,6 +21,11 @@ class NotificationCenter extends Component
#[Url]
public string $filter = 'all';
public function updatedFilter(): void
{
$this->resetPage();
}
public function markAsRead(int $id, NotificationService $service): void
{
$service->markAsRead($id);
......
......@@ -262,6 +262,7 @@ public function confirm(): void
'name' => $this->name ?: $this->nameAr,
'activity_id' => $this->activityId,
'branch_id' => $this->branchId,
'default_trainer_id' => $this->headTrainerId,
'skill_level' => $this->skillLevel ?: 'all',
'age_min' => $this->ageMin ?: null,
'age_max' => $this->ageMax ?: null,
......
......@@ -8,6 +8,7 @@
use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Services\TrainingProgramService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Models\User;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
......@@ -24,6 +25,7 @@ class ProgramForm extends Component
public string $slug = '';
public ?int $activity_id = null;
public ?int $branch_id = null;
public ?int $default_trainer_id = null;
public string $description = '';
public string $description_ar = '';
public string $skill_level = 'all';
......@@ -63,6 +65,7 @@ public function mount(?TrainingProgram $program = null): void
$this->slug = $program->slug ?? '';
$this->activity_id = $program->activity_id;
$this->branch_id = $program->branch_id;
$this->default_trainer_id = $program->default_trainer_id;
$this->description = $program->description ?? '';
$this->description_ar = $program->description_ar ?? '';
$this->skill_level = $program->skill_level ?? 'all';
......@@ -113,6 +116,7 @@ public function rules(): array
'slug' => 'nullable|string|max:100',
'activity_id' => 'required|exists:activities,id',
'branch_id' => 'nullable|exists:branches,id',
'default_trainer_id' => 'nullable|exists:users,id',
'description' => 'nullable|string',
'description_ar' => 'nullable|string',
'skill_level' => 'required|in:all,beginner,intermediate,advanced,professional',
......@@ -188,6 +192,7 @@ public function save(TrainingProgramService $service): void
'slug' => $this->slug ?: null,
'activity_id' => $this->activity_id,
'branch_id' => $this->branch_id,
'default_trainer_id' => $this->default_trainer_id,
'status' => $this->status,
'description' => $this->description ?: null,
'description_ar' => $this->description_ar ?: null,
......@@ -276,6 +281,10 @@ public function render()
return view('livewire.programs.program-form', [
'activities' => Activity::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar']),
'branches' => Branch::orderBy('name_ar')->get(['id', 'name_ar']),
'trainers' => User::whereHas('roles', fn ($q) => $q->whereIn('slug', ['trainer', 'head_trainer']))
->where('status', 'active')
->orderBy('name')
->get(['id', 'name']),
]);
}
}
......@@ -22,6 +22,7 @@ class UserList extends Component
public function updatedSearch(): void { $this->resetPage(); }
public function updatedRole(): void { $this->resetPage(); }
public function updatedShowInactive(): void { $this->resetPage(); }
public function toggleActive(string $uuid): void
{
......
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('training_programs', function (Blueprint $table) {
$table->foreignId('default_trainer_id')->nullable()->after('branch_id')->constrained('users')->nullOnDelete();
$table->index(['academy_id', 'default_trainer_id']);
});
}
public function down(): void
{
Schema::table('training_programs', function (Blueprint $table) {
$table->dropForeign(['default_trainer_id']);
$table->dropIndex(['academy_id', 'default_trainer_id']);
$table->dropColumn('default_trainer_id');
});
}
};
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
FROM php:8.3-apache
RUN a2enmod rewrite && \
sed -i 's/AllowOverride None/AllowOverride All/g' /etc/apache2/apache2.conf
WORKDIR /var/www/html
COPY index.php .htaccess ./
COPY docs/ docs/
RUN chown -R www-data:www-data /var/www/html
EXPOSE 80
{
"schemaVersion": 2,
"dockerfilePath": "./Dockerfile"
}
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
BUILD_DIR=$(mktemp -d)
APP_NAME="el-captain-docs"
SERVER="ubuntu@18.192.166.221"
SSH_KEY="/Users/mahmoudaglan/NewMigration/newServer.pem"
SSH_CMD="ssh -i $SSH_KEY -o StrictHostKeyChecking=no $SERVER"
echo "==> Preparing build context..."
cp "$SCRIPT_DIR/index.php" "$BUILD_DIR/"
cp "$SCRIPT_DIR/.htaccess" "$BUILD_DIR/"
cp "$SCRIPT_DIR/Dockerfile" "$BUILD_DIR/"
mkdir -p "$BUILD_DIR/docs/ar"
cp "$PROJECT_DIR/docs/ar/"*.md "$BUILD_DIR/docs/ar/"
# Remove macOS metadata
find "$BUILD_DIR" -name '._*' -delete
find "$BUILD_DIR" -name '.DS_Store' -delete
echo "==> Creating tarball..."
cd "$BUILD_DIR"
tar czf /tmp/el-captain-docs.tar.gz .
echo "==> Uploading to server..."
scp -i "$SSH_KEY" -o StrictHostKeyChecking=no /tmp/el-captain-docs.tar.gz "$SERVER:/tmp/"
echo "==> Building and deploying on server..."
$SSH_CMD bash -s <<'REMOTE'
set -e
rm -rf /tmp/el-captain-docs-build
mkdir -p /tmp/el-captain-docs-build
cd /tmp/el-captain-docs-build
tar xzf /tmp/el-captain-docs.tar.gz 2>/dev/null
find . -name '._*' -delete
echo "Building Docker image..."
sudo docker build -t img-captain--el-captain-docs:latest . 2>&1 | tail -3
echo "Updating service..."
sudo docker service update --image img-captain--el-captain-docs:latest --force srv-captain--el-captain-docs 2>&1 | tail -3
echo ""
echo "Verifying..."
sleep 3
CID=$(sudo docker ps -q --filter "name=srv-captain--el-captain-docs")
TITLE=$(sudo docker exec $CID curl -s http://localhost/ | grep -o '<title>[^<]*</title>')
echo "Live: $TITLE"
rm -rf /tmp/el-captain-docs-build /tmp/el-captain-docs.tar.gz
REMOTE
rm -rf "$BUILD_DIR" /tmp/el-captain-docs.tar.gz
echo ""
echo "==> Done! https://el-captain-docs.caprover.al-arcade.com"
This diff is collapsed.
This diff is collapsed.
# نظام الكابتن لإدارة الأكاديميات الرياضية — الدليل الشامل
## نظرة عامة
نظام ERP متكامل مصمم خصيصاً للأكاديميات والأندية الرياضية في مصر. يدعم أي نشاط رياضي (كرة قدم، سباحة، جمباز، كاراتيه...) من خلال إعدادات مرنة بدون تغيير في الكود.
---
## فهرس الوحدات (Modules)
| # | الوحدة | الملف | الوصف |
|---|--------|-------|-------|
| 01 | تسجيل الدخول والهوية | [01-الهوية-والمنظمة.md](01-الهوية-والمنظمة.md) | تسجيل الدخول، الأدوار، الفروع، المستخدمين |
| 02 | المشتركين وأولياء الأمور | [02-المشتركين.md](02-المشتركين.md) | تسجيل المشتركين، الحالات، أولياء الأمور، الاستيراد |
| 03 | البرامج التدريبية والمجموعات | [03-البرامج-والمجموعات.md](03-البرامج-والمجموعات.md) | إنشاء البرامج، المجموعات، الجداول، الحصص |
| 04 | التسجيل (Enrollment) | [04-التسجيل.md](04-التسجيل.md) | تسجيل المشتركين في المجموعات، قوائم الانتظار، النقل |
| 05 | الحضور والغياب | [05-الحضور.md](05-الحضور.md) | تسجيل الحضور، التأخير، الغياب التلقائي، التنبيهات |
| 06 | المنشآت والمساحات | [06-المنشآت.md](06-المنشآت.md) | إدارة الملاعب، التخطيطات، الحجوزات، كشف التعارض |
| 07 | التعيينات (Assignments) | [07-التعيينات.md](07-التعيينات.md) | تعيين المدربين للمجموعات والحصص |
| 08 | المالية والفواتير | [08-المالية.md](08-المالية.md) | الفواتير، المدفوعات، القيد المزدوج، المحافظ |
| 09 | محرك التسعير | [09-التسعير.md](09-التسعير.md) | الأسعار الأساسية، قواعد الخصم، العروض، الكوبونات |
| 10 | نقطة البيع (POS) | [10-نقطة-البيع.md](10-نقطة-البيع.md) | محطة البيع، الورديات، الإيصالات |
| 11 | المخزون | [11-المخزون.md](11-المخزون.md) | المنتجات، المخازن، الحركات، الجرد |
| 12 | الموارد البشرية | [12-الموارد-البشرية.md](12-الموارد-البشرية.md) | الموظفين، المدربين، الرواتب، السلف |
| 13 | الإشعارات والمستندات | [13-الإشعارات-والمستندات.md](13-الإشعارات-والمستندات.md) | قوالب الإشعارات، المستندات، الموافقات |
| 14 | التقييمات | [14-التقييمات.md](14-التقييمات.md) | معايير التقييم، تقييم المشتركين |
| 15 | التقارير والإعدادات | [15-التقارير-والإعدادات.md](15-التقارير-والإعدادات.md) | التقارير المالية، الحضور، الإعدادات، لوحات التحكم |
| 16 | مكتب الاستقبال | [16-مكتب-الاستقبال.md](16-مكتب-الاستقبال.md) | ويزارد التسجيل الجديد، تسجيل موجود، تحصيل دفعة |
| 17 | مخططات الدورات الشاملة | [17-مخططات-الدورات.md](17-مخططات-الدورات.md) | 13 مخطط Mermaid: كل الدورات في مكان واحد |
---
## الأدوار المتاحة في النظام
| الدور | المستوى | الوصف |
|-------|---------|-------|
| مدير النظام (super_admin) | 100 | تحكم كامل بجميع الأكاديميات |
| مالك الأكاديمية (academy_owner) | 90 | تحكم كامل بأكاديمية واحدة |
| مدير الأكاديمية (academy_admin) | 80 | إدارة يومية كاملة |
| مدير الفرع (branch_manager) | 70 | إدارة فرع واحد |
| رئيس المدربين (head_trainer) | 60 | إشراف على المدربين |
| مدرب (trainer) | 50 | تسجيل حضور + تقييم |
| موظف استقبال (receptionist) | 40 | تسجيل مشتركين + تحصيل |
| محاسب (accountant) | 35 | عمليات مالية فقط |
| إدخال بيانات (data_entry) | 30 | إدخال بيانات فقط |
| ولي أمر (parent) | 5 | متابعة أبنائه فقط |
---
## دورات العمل الأساسية (Business Cycles)
### الدورة الكاملة لمشترك جديد:
```
تسجيل ← اختيار برنامج ← اختيار مجموعة ← حساب السعر ← إنشاء فاتورة ← دفع ← تفعيل ← حضور ← تقييم ← تخرج/تجديد
```
### الدورة المالية اليومية:
```
فتح وردية ← عمليات بيع/تحصيل ← إقفال وردية ← مطابقة
```
### دورة الحضور:
```
إنشاء حصة ← توليد سجلات متوقعة ← تسجيل الحضور ← غياب تلقائي ← تنبيهات ← إيقاف تلقائي
```
---
## المهام المجدولة (Automated Jobs)
| المهمة | التوقيت | الوظيفة |
|--------|---------|---------|
| غياب تلقائي | كل ساعة | تحويل "متوقع" إلى "غائب" بعد انتهاء الحصة بساعتين |
| فحص حدود الحضور | 06:00 يومياً | إيقاف تلقائي عند 5 غيابات متتالية |
| تنبيهات الحضور | 20:00 يومياً | إرسال تنبيهات لأولياء الأمور |
| فواتير متأخرة | 01:00 يومياً | تحويل فواتير منتهية الأجل إلى "متأخرة" |
| توليد حصص | 02:00 يومياً | إنشاء حصص الأسبوع القادم |
| تنظيف السجلات | أسبوعياً | حذف سجلات التدقيق الأقدم من سنة |
| انتهاء المستندات | 06:00 يومياً | تحديث حالة المستندات المنتهية |
| ملخص يومي | 07:00 يومياً | إرسال ملخص للإدارة |
| تهنئة أعياد الميلاد | 08:00 يومياً | إرسال رسائل تهنئة |
| كشف تعثر الأقساط | 08:00 يومياً | تنبيه عند تأخر قسط |
| مخزون منخفض | 09:00 يومياً | تنبيه عند وصول مخزون للحد الأدنى |
| تذكير اشتراكات تنتهي | 10:00 يومياً | تذكير قبل 7 و 3 أيام |
| تذكير أقساط | 11:00 يومياً | تذكير قبل 3 و 1 يوم |
| فواتير متأخرة (تذكير) | الاثنين + الخميس | إرسال تذكير المتأخرات |
| مصالحة أعداد المجموعات | 03:00 يومياً | تصحيح عدادات المجموعات |
| إنهاء اشتراكات منتهية | 00:30 يومياً | تعطيل الاشتراكات المنتهية |
| مصالحة مالية | أسبوعياً (الأحد 04:00) | فحص تطابق القيود |
| تقرير أسبوعي لأولياء الأمور | السبت 12:00 | إرسال تقرير أسبوعي |
| تنبيه امتلاء المجموعات | 08:00 يومياً | تنبيه عند وصول 90% من السعة |
---
## كيف تقرأ هذا الدليل
كل وحدة (Module) تحتوي على:
1. **وصف الشاشات** — كل شاشة بالتفصيل مع الحقول والأزرار
2. **دورات العمل** — مخططات Mermaid توضح تسلسل العمليات
3. **أمثلة عملية** — سيناريوهات واقعية مع أرقام
4. **لماذا يحتاج عملك هذا** — المبرر التجاري لكل ميزة
5. **التكاملات** — كيف تتصل هذه الوحدة بالوحدات الأخرى
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
# 06 — المنشآت والمساحات
## نظرة عامة
المنشأة = ملعب أو حمام سباحة أو صالة. لكل منشأة تخطيطات (layouts) مختلفة حسب الوقت، والتخطيط مقسّم إلى أجزاء (segments) يمكن حجزها.
```
منشأة → تخطيط (حسب اليوم/الوقت) → أجزاء (قابلة للحجز)
```
---
## الشاشات المنفذة
### 1. قائمة المنشآت (`/facilities`)
**من يراها:** من لديه صلاحية `facilities.list`
**الفلاتر:**
| الفلتر | النوع | الخيارات |
|--------|------|----------|
| بحث | نص | اسم أو كود |
| الحالة | select | active, maintenance, closed, reserved, unavailable |
| النوع | select | indoor_field, outdoor_field, pool, gym, court, track, other |
| الفرع | select | قائمة الفروع |
**أعمدة الجدول:**
| العمود | الوصف |
|--------|-------|
| الكود | code |
| الاسم | name_ar |
| النوع | شارة |
| الفرع | branch.name_ar |
| المساحة | area_sqm م² |
| تكلفة الإيجار | monthly_rental_cost ج.م/شهر |
| الحالة | شارة ملونة |
| الإجراءات | تعديل، حذف، تخطيطات |
---
### 2. إنشاء/تعديل منشأة (`/facilities/create`, `/facilities/{id}/edit`)
**الحقول:**
| الحقل | النوع | مطلوب | ملاحظات |
|--------|------|-------|---------|
| الاسم بالعربية | text | ✓ | |
| الاسم بالإنجليزية | text | ✗ | |
| الكود | text | ✗ | |
| النوع | select | ✓ | indoor_field, outdoor_field, pool, gym, court, track, other |
| الفرع | select | ✗ | |
| المساحة (م²) | number | ✗ | |
| السعة القصوى | number | ✗ | |
| أوقات العمل (من) | time | ✗ | |
| أوقات العمل (إلى) | time | ✗ | |
| تكلفة إيجار شهرية | money | ✗ | بالقروش |
| الوصف | textarea | ✗ | |
| الحالة | select | ✓ | active, maintenance, closed |
---
### 3. ويزارد إنشاء منشأة (`/facilities/wizard`)
**خطوات:**
1. البيانات الأساسية (اسم، نوع، فرع)
2. المواصفات (مساحة، سعة، أوقات العمل)
3. التخطيط الأولي (نوع التقسيم + الأبعاد)
4. مراجعة وتأكيد
---
### 4. إدارة التخطيطات (`/facilities/{id}/layouts`)
**من يراها:** من لديه صلاحية `facilities.manage_layouts`
**المفهوم الأساسي:** المنشأة الواحدة يمكن أن يكون لها تخطيطات مختلفة حسب اليوم والوقت.
**مثال:**
> ملعب كرة قدم:
> - صباحاً (8:00-14:00): مقسّم 3×2 = 6 مساحات صغيرة (تدريب أطفال)
> - مساءً (14:00-22:00): مقسّم 2×1 = ملعبين كبيرين (مباريات)
**حقول التخطيط:**
| الحقل | النوع | مطلوب | ملاحظات |
|--------|------|-------|---------|
| الاسم بالعربية | text | ✓ | |
| الاسم بالإنجليزية | text | ✗ | |
| نوع التخطيط | select | ✓ | grid, lanes, zones, custom |
| متكرر أسبوعياً | toggle | — | |
| يوم الأسبوع | select | مشروط | إذا متكرر — 0(أحد) إلى 6(سبت) |
| تاريخ محدد | date | مشروط | إذا غير متكرر |
| وقت البداية | time | ✓ | |
| وقت النهاية | time | ✓ | |
| الأبعاد | حسب النوع | ✓ | |
**أنواع التخطيط:**
| النوع | الإعداد | النتيجة |
|-------|---------|---------|
| grid | صفوف × أعمدة | مثل: 3×2 = 6 مربعات |
| lanes | عدد الحارات | مثل: 6 حارات سباحة |
| zones | تعريف يدوي | مناطق بأسماء مخصصة |
| custom | تعريف يدوي | بدون قيود هندسية |
**توليد الأجزاء التلقائي:**
- Grid(3,2) → R1C1, R1C2, R2C1, R2C2, R3C1, R3C2
- Lanes(6) → Lane 1, Lane 2, ..., Lane 6
- Zones → جزء لكل تعريف
---
### 5. ويزارد تعيين المساحات (`/facilities/space-assignment`)
**من يراها:** من لديه صلاحية `facilities.manage_layouts`
**الوظيفة:** ربط مجموعة تدريبية بأجزاء محددة من المنشأة في أوقات محددة.
---
### 6. منشئ الجدول المرئي (`/facilities/schedule-builder`)
**من يراها:** من لديه صلاحية `schedules.manage`
**الوصف:** واجهة مرئية تعرض المنشأة كشبكة (أيام × أوقات) مع الحجوزات الحالية ملوّنة.
---
## حالات المنشأة
| الحالة | المعنى | التأثير |
|--------|--------|---------|
| active | مفعّلة — تعمل | يمكن الحجز |
| maintenance | صيانة | تُلغى الحجوزات الجديدة — القائمة تُلغى |
| closed | مغلقة | لا حجوزات |
| reserved | محجوزة (بالكامل) | لا حجوزات جديدة |
| unavailable | غير متاحة | لا حجوزات |
---
## كشف التعارض (Collision Detection)
**الخدمة:** `SpaceCollisionService`
**الآلية:**
```
1. إيجاد الحجوزات المؤكدة (confirmed) في نفس المنشأة ونفس اليوم
2. تصفية: التي تتداخل مع الوقت المطلوب
3. استبعاد نفس الحجز (إذا تعديل)
4. فحص تقاطع الأجزاء المطلوبة مع الأجزاء المحجوزة
5. إذا لا تقاطع → لا مشكلة ✓
6. إذا تقاطع → رفض + تفاصيل التعارض
```
```mermaid
flowchart TD
A[طلب حجز: منشأة X، يوم Y، 16:00-17:00، أجزاء 1,2,3] --> B[البحث عن حجوزات مؤكدة]
B --> C[تصفية التداخل الزمني]
C --> D{هل يوجد حجوزات متداخلة؟}
D -->|لا| E[✓ لا تعارض — يمكن الحجز]
D -->|نعم| F[فحص تقاطع الأجزاء]
F --> G{هل الأجزاء نفسها؟}
G -->|لا| E
G -->|نعم| H[✗ تعارض — رفض]
```
**أنواع الحجز:**
| النوع | التأثير |
|-------|---------|
| confirmed | يمنع الآخرين (حجز صلب) |
| tentative | يحذّر فقط (لا يمنع) |
| cancelled | غير مرئي — لا تأثير |
---
## حجز المساحة عند إنشاء حصة
```mermaid
sequenceDiagram
participant Job as مولّد الحصص
participant S as النظام
participant Col as خدمة التعارض
Job->>S: إنشاء حصة (مجموعة X، السبت 16:00)
S->>S: هل المجموعة لها مساحة محجوزة في الجدول؟
S->>Col: فحص: هل المساحة متاحة يوم السبت 16:00?
Col-->>S: ✓ متاحة
S->>S: إنشاء حجز (confirmed)
S->>S: إنشاء الحصة + ربطها بالحجز
```
**عند إلغاء الحصة:**
- الحجز المرتبط → cancelled
- المساحة تصبح متاحة فوراً
---
## أمثلة عملية
### مثال 1: ملعب كرة قدم بتخطيطين
> **صباحاً (8:00-14:00):** Grid 3×2 = 6 مساحات صغيرة
> - المساحة 1+2: مجموعة براعم 4-6
> - المساحة 3+4: مجموعة أشبال 7-9
> - المساحة 5+6: فارغة
>
> **مساءً (14:00-22:00):** Grid 2×1 = ملعبين
> - الملعب 1: مجموعة ناشئين 10-14
> - الملعب 2: إيجار خارجي
### مثال 2: حمام سباحة بـ 6 حارات
> - التخطيط: Lanes(6) → 6 حارات
> - الحارات 1-3: مجموعة تعليم مبتدئين (4:00-5:00)
> - الحارات 4-6: مجموعة متقدمين (4:00-5:00)
> - الحارات 1-6: مجموعة فريق المنافسات (5:00-7:00)
### مثال 3: محاولة حجز مساحة محجوزة
> 1. المدير يحاول تعيين مجموعة جديدة في الملعب 1، السبت 16:00
> 2. النظام يكتشف: مجموعة الناشئين محجوزة هناك 14:00-18:00
> 3. رفض: "تعارض مع حجز مجموعة الناشئين"
> 4. البدائل: اختيار ملعب آخر أو وقت آخر
### مثال 4: منشأة تدخل في صيانة
> 1. حالة المنشأة → maintenance
> 2. كل الحجوزات المستقبلية المؤكدة → cancelled
> 3. إشعارات للمجموعات المتأثرة
> 4. الحصص المرتبطة تحتاج إعادة تعيين لمنشأة أخرى
---
## لماذا يحتاج عملك هذا
| الميزة | الفائدة |
|--------|---------|
| التخطيطات المتعددة | نفس الملعب يُستخدم بطرق مختلفة صباحاً ومساءً |
| كشف التعارض | يستحيل حجز نفس المكان مرتين — لا فوضى |
| الحجز التلقائي | عند إنشاء الحصص، المساحة تُحجز تلقائياً |
| تكلفة الإيجار | حساب break-even: كم مشترك نحتاج لتغطية الإيجار؟ |
| أنواع التخطيط | يناسب أي رياضة: ملاعب، حمامات سباحة، صالات |
---
## التكاملات
| الوحدة | العلاقة |
|--------|---------|
| المجموعات | الجدول الأسبوعي مرتبط بمنشأة |
| الحصص | كل حصة مرتبطة بمنشأة + أجزاء |
| التعيينات | المدرب مُعيّن في مساحة محددة |
| المالية | تكلفة الإيجار تدخل في الحسابات |
| لوحة التحكم | عرض حصص اليوم مع المنشأة |
# 07 — التعيينات (Assignments)
## نظرة عامة
التعيين = ربط مدرب أو موظف بمجموعة تدريبية أو حصة محددة. يحدد:
- من يُدرّب من
- في أي فترة
- بأي نطاق (كامل، جزئي، مساعد، مشرف، مراقب)
---
## الشاشات المنفذة
### 1. قائمة التعيينات (`/assignments`)
**من يراها:** من لديه صلاحية `assignments.list`
**الفلاتر:**
- بحث بالاسم
- تصفية بالمجموعة
- تصفية بالحالة
**أعمدة الجدول:**
| العمود | الوصف |
|--------|-------|
| المُعيَّن | اسم المدرب/الموظف |
| النوع | trainer, head_trainer, assistant |
| المجموعة | اسم المجموعة |
| النطاق | full, partial, supervising, assisting, observing |
| تاريخ البداية | start_date |
| تاريخ النهاية | end_date |
| الحالة | active, suspended, completed, cancelled |
| الإجراءات | — |
---
### 2. إنشاء تعيين (`/assignments/create`)
**من يراها:** من لديه صلاحية `assignments.create`
**الحقول:**
| الحقل | النوع | مطلوب | ملاحظات |
|--------|------|-------|---------|
| المُعيَّن | select | ✓ | بحث بالاسم بين المدربين |
| نوع المُعيَّن | select | ✓ | trainer, assistant, head_trainer |
| المجموعة | select | ✓ | |
| النطاق | select | ✓ | full, partial, supervising, assisting, observing |
| تاريخ البداية | date | ✓ | |
| تاريخ النهاية | date | ✗ | |
| ملاحظات | textarea | ✗ | |
**النطاقات:**
| النطاق | المعنى |
|--------|--------|
| full | مسؤول كامل عن المجموعة |
| partial | مسؤول عن جزء (أيام محددة) |
| supervising | إشراف فقط — لا يُدرّب مباشرة |
| assisting | مساعد — يساعد المدرب الرئيسي |
| observing | مراقب — لا يتدخل |
---
### حالات التعيين
```mermaid
stateDiagram-v2
[*] --> نشط
نشط --> موقوف : إيقاف مؤقت
نشط --> مكتمل : انتهاء الفترة
نشط --> ملغى : إلغاء
موقوف --> نشط : استئناف
```
---
## كشف تعارض التعيينات
**الخدمة:** `ScheduleConflictService`
**التحققات عند إنشاء تعيين:**
1. هل المدرب مُعيّن بالفعل لمجموعة أخرى في نفس الوقت؟
2. هل المدرب متاح في هذه الأيام/الأوقات (جدول التوفر)؟
3. هل تجاوز الحد الأقصى لساعات العمل الأسبوعية؟
---
## العلاقة بالحضور
**عند إنشاء تعيين:**
- تُولّد سجلات حضور "expected" للمدرب في جميع حصص المجموعة المستقبلية
**عند إنهاء تعيين:**
- تُحذف سجلات الحضور المستقبلية للمدرب (الماضي يبقى)
**التجاوز على مستوى الحصة:**
- إذا حصة معينة لها مدرب مُعيّن خصيصاً (override) → المدرب المُعيّن على مستوى المجموعة لا يُتوقع حضوره لتلك الحصة
---
## أمثلة عملية
### مثال 1: تعيين مدرب رئيسي لمجموعة
> 1. كابتن أحمد → مجموعة "أشبال السبت/الاثنين" → نطاق: full
> 2. تُولّد سجلات حضور لأحمد في كل حصص المجموعة
> 3. يظهر في لوحة المدرب: حصصه اليوم
### مثال 2: مدرب مساعد في أيام محددة
> 1. كابتن سارة → نفس المجموعة → نطاق: partial
> 2. يوم السبت فقط
> 3. حضورها مطلوب يوم السبت فقط
### مثال 3: تعارض تعيين
> 1. كابتن أحمد مُعيّن لمجموعة "أشبال" السبت 16:00-17:00
> 2. المدير يحاول تعيينه لمجموعة "ناشئين" السبت 16:30-18:00
> 3. النظام يكتشف التعارض الزمني
> 4. تحذير: "تعارض مع تعيين موجود"
---
## لماذا يحتاج عملك هذا
| الميزة | الفائدة |
|--------|---------|
| ربط مدرب بمجموعة | واضح من يُدرّب من — لا خلافات |
| كشف التعارض | مستحيل تعيين مدرب في مكانين بنفس الوقت |
| نطاقات متعددة | يناسب أكاديميات بمدربين رئيسيين ومساعدين |
| الربط بالحضور | المدرب يُحاسب على حضوره تلقائياً |
| الربط بالرواتب | حضور المدرب = أساس حساب المكافأة |
---
## التكاملات
| الوحدة | العلاقة |
|--------|---------|
| الموارد البشرية | المدرب هو الشخص المُعيَّن |
| المجموعات | المجموعة هي هدف التعيين |
| الحضور | تعيين → سجلات حضور مدرب |
| الرواتب | حضور المدرب → مكافأة الحصة |
| المنشآت | التعيين مرتبط بمساحة في المنشأة |
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -3,7 +3,7 @@
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
<h1 class="text-2xl font-bold text-gray-900">{{ __('المدربين') }}</h1>
@can('trainers.create')
<a href="{{ route('trainers.create') }}" wire:navigate
<a href="{{ route('trainers.wizard') }}" wire:navigate
class="inline-flex items-center gap-2 px-4 py-2.5 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
{{ __('إضافة مدرب') }}
......
......@@ -81,7 +81,7 @@ class="transition-all duration-700"/>
<h3 class="text-base font-semibold text-[#0F172A] mb-3">{{ __('سجل الحضور') }}</h3>
<div wire:loading.class="opacity-50 pointer-events-none">
@if(empty($attendanceRecords))
@if($records->isEmpty())
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-8 text-center">
<svg class="w-10 h-10 text-gray-200 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
......@@ -102,13 +102,13 @@ class="transition-all duration-700"/>
];
@endphp
@foreach($attendanceRecords as $record)
@php $config = $statusConfig[$record['status'] ?? ''] ?? ['label' => $record['status'] ?? '', 'bg' => 'bg-gray-100', 'text' => 'text-gray-700']; @endphp
@foreach($records as $record)
@php $config = $statusConfig[$record->status ?? ''] ?? ['label' => $record->status ?? '', 'bg' => 'bg-gray-100', 'text' => 'text-gray-700']; @endphp
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-[#0F172A]">{{ $record['session'] ?? '' }}</p>
<p class="text-xs text-[#64748B] mt-0.5" dir="ltr">{{ $record['date'] ?? '' }} - {{ $record['time'] ?? '' }}</p>
<p class="text-sm font-medium text-[#0F172A]">{{ $record->session?->group?->program?->name_ar ?? $record->session?->group?->name_ar ?? '' }}</p>
<p class="text-xs text-[#64748B] mt-0.5" dir="ltr">{{ $record->session?->session_date ?? '' }} - {{ $record->session?->start_time ?? '' }}</p>
</div>
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium {{ $config['bg'] }} {{ $config['text'] }}">
{{ __($config['label']) }}
......@@ -117,6 +117,10 @@ class="transition-all duration-700"/>
</div>
@endforeach
</div>
<div class="mt-4">
{{ $records->links() }}
</div>
@endif
</div>
</div>
......
......@@ -46,6 +46,17 @@ class="text-sm text-gray-600 hover:text-gray-800">{{ __('← العودة للق
</select>
@error('branch_id') <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="default_trainer_id" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="">{{ __('بدون مدرب افتراضي') }}</option>
@foreach($trainers as $trainer)
<option value="{{ $trainer->id }}">{{ $trainer->name }}</option>
@endforeach
</select>
@error('default_trainer_id') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
<p class="text-xs text-gray-500 mt-1">{{ __('يُعيّن تلقائياً للمجموعات الجديدة') }}</p>
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الوصف بالعربية') }}</label>
<textarea wire:model="description_ar" rows="3" class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"></textarea>
......
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