Commit e1f1837a authored by Mahmoud Aglan's avatar Mahmoud Aglan

Overhaul coach create form: full validation, NID auto-parse, edge cases

Frontend:
- Client-side NID parser (no API call) — extracts DOB, gender, age, governorate
- Shows green badge with parsed info (governorate, gender, age)
- Red highlight + Arabic message on every invalid field
- Validates: code, name, employment_type, payment_model (non-academy),
  academy_id (academy), NID length, email format, phone format
- Hides payment/rate fields for academy coaches (not needed)
- Shows note explaining academy coaches follow salary system
- Gender/DOB auto-locked when NID is valid, manual otherwise
- Age displayed under DOB field

Backend:
- Strips non-digits from NID input
- Uppercase code automatically
- Checks NID uniqueness against existing coaches
- Validates email with filter_var
- Academy coach requires academy_id selected
- payment_model defaults to 'salary' for academy, 'per_session' fallback
- max_groups minimum 1
- Success message includes coach name
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 646d11e6
...@@ -99,10 +99,10 @@ class CoachController extends Controller ...@@ -99,10 +99,10 @@ class CoachController extends Controller
{ {
$db = App::getInstance()->db(); $db = App::getInstance()->db();
$code = trim((string) $request->post('code', '')); $code = strtoupper(trim((string) $request->post('code', '')));
$fullNameAr = trim((string) $request->post('full_name_ar', '')); $fullNameAr = trim((string) $request->post('full_name_ar', ''));
$fullNameEn = trim((string) $request->post('full_name_en', '')); $fullNameEn = trim((string) $request->post('full_name_en', ''));
$nationalId = trim((string) $request->post('national_id', '')); $nationalId = preg_replace('/\D/', '', trim((string) $request->post('national_id', '')));
$phone = trim((string) $request->post('phone', '')); $phone = trim((string) $request->post('phone', ''));
$email = trim((string) $request->post('email', '')); $email = trim((string) $request->post('email', ''));
$dateOfBirth = trim((string) $request->post('date_of_birth', '')); $dateOfBirth = trim((string) $request->post('date_of_birth', ''));
...@@ -114,6 +114,11 @@ class CoachController extends Controller ...@@ -114,6 +114,11 @@ class CoachController extends Controller
$monthlyRate = trim((string) $request->post('monthly_rate', '')); $monthlyRate = trim((string) $request->post('monthly_rate', ''));
$maxGroups = trim((string) $request->post('max_groups', '')); $maxGroups = trim((string) $request->post('max_groups', ''));
$bioAr = trim((string) $request->post('bio_ar', '')); $bioAr = trim((string) $request->post('bio_ar', ''));
$coachType = trim((string) $request->post('coach_type', 'independent'));
if (!array_key_exists($coachType, Coach::getCoachTypeOptions())) {
$coachType = 'independent';
}
// Validation // Validation
$errors = []; $errors = [];
...@@ -121,6 +126,8 @@ class CoachController extends Controller ...@@ -121,6 +126,8 @@ class CoachController extends Controller
$errors[] = 'كود المدرب مطلوب'; $errors[] = 'كود المدرب مطلوب';
} elseif (mb_strlen($code) > 30) { } elseif (mb_strlen($code) > 30) {
$errors[] = 'كود المدرب يجب ألا يتجاوز 30 حرف'; $errors[] = 'كود المدرب يجب ألا يتجاوز 30 حرف';
} elseif (Coach::findByCode($code) !== null) {
$errors[] = 'كود المدرب "' . $code . '" مستخدم بالفعل — اختر كود آخر';
} }
if ($fullNameAr === '') { if ($fullNameAr === '') {
$errors[] = 'الاسم بالعربي مطلوب'; $errors[] = 'الاسم بالعربي مطلوب';
...@@ -130,15 +137,23 @@ class CoachController extends Controller ...@@ -130,15 +137,23 @@ class CoachController extends Controller
if ($employmentType === '') { if ($employmentType === '') {
$errors[] = 'نوع التوظيف مطلوب'; $errors[] = 'نوع التوظيف مطلوب';
} }
$coachType = trim((string) $request->post('coach_type', 'independent'));
if (!array_key_exists($coachType, Coach::getCoachTypeOptions())) {
$coachType = 'independent';
}
if ($coachType !== 'academy' && $paymentModel === '') { if ($coachType !== 'academy' && $paymentModel === '') {
$errors[] = 'نموذج الدفع مطلوب'; $errors[] = 'نموذج الدفع مطلوب للمدربين غير التابعين لأكاديمية';
}
if ($coachType === 'academy' && !(int) $request->post('academy_id', 0)) {
$errors[] = 'يجب اختيار الأكاديمية لمدرب أكاديمية';
}
if ($nationalId !== '' && strlen($nationalId) !== 14) {
$errors[] = 'الرقم القومي يجب أن يكون 14 رقم بالضبط';
}
if ($nationalId !== '' && strlen($nationalId) === 14) {
$existingCoach = $db->selectOne("SELECT id, full_name_ar FROM sa_coaches WHERE national_id = ? AND is_archived = 0", [$nationalId]);
if ($existingCoach) {
$errors[] = 'الرقم القومي مسجل بالفعل للمدرب: ' . ($existingCoach['full_name_ar'] ?? '');
}
} }
if ($code !== '' && Coach::findByCode($code) !== null) { if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = 'كود المدرب مستخدم بالفعل'; $errors[] = 'صيغة البريد الإلكتروني غير صحيحة';
} }
if (!empty($errors)) { if (!empty($errors)) {
...@@ -149,11 +164,12 @@ class CoachController extends Controller ...@@ -149,11 +164,12 @@ class CoachController extends Controller
return $this->redirect('/sa/coaches/create'); return $this->redirect('/sa/coaches/create');
} }
// Parse NID for DOB/gender
if ($nationalId !== '' && strlen($nationalId) === 14) { if ($nationalId !== '' && strlen($nationalId) === 14) {
$parsed = NationalIdParser::parse($nationalId); $parsed = NationalIdParser::parse($nationalId);
if ($parsed['is_valid']) { if ($parsed['is_valid']) {
$dateOfBirth = $parsed['dob']; if ($parsed['dob']) $dateOfBirth = $parsed['dob'];
$gender = $parsed['gender']; if ($parsed['gender']) $gender = $parsed['gender'];
} }
} }
...@@ -175,16 +191,15 @@ class CoachController extends Controller ...@@ -175,16 +191,15 @@ class CoachController extends Controller
'hourly_rate' => $coachType === 'academy' ? null : ($hourlyRate !== '' ? (float) $hourlyRate : null), 'hourly_rate' => $coachType === 'academy' ? null : ($hourlyRate !== '' ? (float) $hourlyRate : null),
'session_rate' => $coachType === 'academy' ? null : ($sessionRate !== '' ? (float) $sessionRate : null), 'session_rate' => $coachType === 'academy' ? null : ($sessionRate !== '' ? (float) $sessionRate : null),
'monthly_rate' => $coachType === 'academy' ? null : ($monthlyRate !== '' ? (float) $monthlyRate : null), 'monthly_rate' => $coachType === 'academy' ? null : ($monthlyRate !== '' ? (float) $monthlyRate : null),
'max_groups' => $maxGroups !== '' ? (int) $maxGroups : 10, 'max_groups' => $maxGroups !== '' ? max(1, (int) $maxGroups) : 10,
'bio_ar' => $bioAr ?: null, 'bio_ar' => $bioAr ?: null,
'is_active' => 1, 'is_active' => 1,
'branch_id' => App::getInstance()->session()->get('branch_id', 1), 'branch_id' => App::getInstance()->session()->get('branch_id', 1),
]); ]);
// Sync disciplines
$this->syncDisciplines($db, (int) $coach->id, $request); $this->syncDisciplines($db, (int) $coach->id, $request);
return $this->redirect('/sa/coaches')->withSuccess('تم إضافة المدرب بنجاح'); return $this->redirect('/sa/coaches')->withSuccess('تم إضافة المدرب "' . $fullNameAr . '" بنجاح');
} }
public function show(Request $request, string $id): Response public function show(Request $request, string $id): Response
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<?php $__template->section('content'); ?> <?php $__template->section('content'); ?>
<form method="POST" action="/sa/coaches" id="coachForm"> <form method="POST" action="/sa/coaches" id="coachForm" novalidate>
<?= csrf_field() ?> <?= csrf_field() ?>
<!-- Basic Information --> <!-- Basic Information -->
...@@ -20,11 +20,11 @@ ...@@ -20,11 +20,11 @@
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;"> <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;">
<div class="form-group"> <div class="form-group">
<label class="form-label">كود المدرب <span style="color:#DC2626;">*</span></label> <label class="form-label">كود المدرب <span style="color:#DC2626;">*</span></label>
<input type="text" name="code" value="<?= e(old('code')) ?>" class="form-input" required maxlength="30" placeholder="مثال: COACH-001" style="direction:ltr;text-align:left;text-transform:uppercase;"> <input type="text" name="code" value="<?= e(old('code')) ?>" class="form-input" maxlength="30" placeholder="مثال: COACH-001" style="direction:ltr;text-align:left;text-transform:uppercase;">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">الاسم بالعربي <span style="color:#DC2626;">*</span></label> <label class="form-label">الاسم بالعربي <span style="color:#DC2626;">*</span></label>
<input type="text" name="full_name_ar" value="<?= e(old('full_name_ar')) ?>" class="form-input" required maxlength="300" placeholder="الاسم الكامل بالعربي"> <input type="text" name="full_name_ar" value="<?= e(old('full_name_ar')) ?>" class="form-input" maxlength="300" placeholder="الاسم الكامل بالعربي">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">الاسم بالإنجليزي</label> <label class="form-label">الاسم بالإنجليزي</label>
...@@ -34,33 +34,34 @@ ...@@ -34,33 +34,34 @@
<div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:20px;margin-top:15px;"> <div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:20px;margin-top:15px;">
<div class="form-group"> <div class="form-group">
<label class="form-label">الرقم القومي</label> <label class="form-label">الرقم القومي</label>
<input type="text" name="national_id" id="nidInput" value="<?= e(old('national_id')) ?>" class="form-input" placeholder="14 رقم" maxlength="14" style="direction:ltr;text-align:left;"> <input type="text" name="national_id" id="nidInput" value="<?= e(old('national_id')) ?>" class="form-input" placeholder="14 رقم" maxlength="14" inputmode="numeric" style="direction:ltr;text-align:left;letter-spacing:1px;">
<small id="nidStatus" style="display:none;margin-top:4px;font-size:11px;"></small> <div id="nidStatus" style="display:none;margin-top:6px;padding:8px 12px;border-radius:6px;font-size:12px;font-weight:600;"></div>
</div>
<div class="form-group">
<label class="form-label">الهاتف</label>
<input type="text" name="phone" value="<?= e(old('phone')) ?>" class="form-input" maxlength="30" placeholder="01xxxxxxxxx" style="direction:ltr;text-align:left;">
</div>
<div class="form-group">
<label class="form-label">البريد الإلكتروني</label>
<input type="email" name="email" value="<?= e(old('email')) ?>" class="form-input" maxlength="200" placeholder="email@example.com" style="direction:ltr;text-align:left;">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">تاريخ الميلاد</label> <label class="form-label">تاريخ الميلاد</label>
<input type="date" name="date_of_birth" id="dobInput" value="<?= e(old('date_of_birth')) ?>" class="form-input" readonly style="direction:ltr;text-align:left;background:#F9FAFB;"> <input type="date" name="date_of_birth" id="dobInput" value="<?= e(old('date_of_birth')) ?>" class="form-input" style="direction:ltr;text-align:left;">
<div id="ageDisplay" style="font-size:11px;color:#6B7280;margin-top:4px;"></div>
</div> </div>
</div>
<div style="display:grid;grid-template-columns:1fr 3fr;gap:20px;margin-top:15px;">
<div class="form-group"> <div class="form-group">
<label class="form-label">النوع</label> <label class="form-label">النوع</label>
<select id="genderSelect" class="form-select" disabled> <select name="gender" id="genderSelect" class="form-select">
<option value="">-- اختر --</option> <option value="">— اختر —</option>
<option value="male" <?= old('gender') === 'male' ? 'selected' : '' ?>>ذكر</option> <option value="male" <?= old('gender') === 'male' ? 'selected' : '' ?>>ذكر</option>
<option value="female" <?= old('gender') === 'female' ? 'selected' : '' ?>>أنثى</option> <option value="female" <?= old('gender') === 'female' ? 'selected' : '' ?>>أنثى</option>
</select> </select>
<input type="hidden" name="gender" id="genderHidden" value="<?= e(old('gender')) ?>"> </div>
<div class="form-group">
<label class="form-label">الهاتف</label>
<input type="text" name="phone" value="<?= e(old('phone')) ?>" class="form-input" maxlength="30" placeholder="01xxxxxxxxx" inputmode="tel" style="direction:ltr;text-align:left;">
</div> </div>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 2fr;gap:20px;margin-top:15px;">
<div class="form-group">
<label class="form-label">البريد الإلكتروني</label>
<input type="email" name="email" value="<?= e(old('email')) ?>" class="form-input" maxlength="200" placeholder="email@example.com" style="direction:ltr;text-align:left;">
</div>
<div></div>
</div>
</div> </div>
</div> </div>
...@@ -74,7 +75,7 @@ ...@@ -74,7 +75,7 @@
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;"> <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;">
<div class="form-group"> <div class="form-group">
<label class="form-label">نوع المدرب <span style="color:#DC2626;">*</span></label> <label class="form-label">نوع المدرب <span style="color:#DC2626;">*</span></label>
<select name="coach_type" id="coachTypeSelect" class="form-select" required> <select name="coach_type" id="coachTypeSelect" class="form-select">
<?php foreach ($coachTypes as $key => $label): ?> <?php foreach ($coachTypes as $key => $label): ?>
<option value="<?= e($key) ?>" <?= (old('coach_type') ?? 'independent') === $key ? 'selected' : '' ?>><?= e($label) ?></option> <option value="<?= e($key) ?>" <?= (old('coach_type') ?? 'independent') === $key ? 'selected' : '' ?>><?= e($label) ?></option>
<?php endforeach; ?> <?php endforeach; ?>
...@@ -82,7 +83,7 @@ ...@@ -82,7 +83,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">نوع التوظيف <span style="color:#DC2626;">*</span></label> <label class="form-label">نوع التوظيف <span style="color:#DC2626;">*</span></label>
<select name="employment_type" class="form-select" required> <select name="employment_type" id="employmentTypeSelect" class="form-select">
<option value="">-- اختر --</option> <option value="">-- اختر --</option>
<?php foreach ($employmentTypes as $key => $label): ?> <?php foreach ($employmentTypes as $key => $label): ?>
<option value="<?= e($key) ?>" <?= old('employment_type') === $key ? 'selected' : '' ?>><?= e($label) ?></option> <option value="<?= e($key) ?>" <?= old('employment_type') === $key ? 'selected' : '' ?>><?= e($label) ?></option>
...@@ -91,7 +92,7 @@ ...@@ -91,7 +92,7 @@
</div> </div>
<div class="form-group" id="paymentModelField"> <div class="form-group" id="paymentModelField">
<label class="form-label">نموذج الدفع <span style="color:#DC2626;">*</span></label> <label class="form-label">نموذج الدفع <span style="color:#DC2626;">*</span></label>
<select name="payment_model" id="paymentModelSelect" class="form-select" required> <select name="payment_model" id="paymentModelSelect" class="form-select">
<option value="">-- اختر --</option> <option value="">-- اختر --</option>
<?php foreach ($paymentModels as $key => $label): ?> <?php foreach ($paymentModels as $key => $label): ?>
<option value="<?= e($key) ?>" <?= old('payment_model') === $key ? 'selected' : '' ?>><?= e($label) ?></option> <option value="<?= e($key) ?>" <?= old('payment_model') === $key ? 'selected' : '' ?>><?= e($label) ?></option>
...@@ -99,7 +100,7 @@ ...@@ -99,7 +100,7 @@
</select> </select>
</div> </div>
</div> </div>
<!-- Academy selector (shown when coach_type = academy) --> <!-- Academy selector -->
<div id="academyFields" style="display:none;margin-top:15px;padding:15px;background:#F5F3FF;border:1px solid #8B5CF630;border-radius:10px;"> <div id="academyFields" style="display:none;margin-top:15px;padding:15px;background:#F5F3FF;border:1px solid #8B5CF630;border-radius:10px;">
<div style="font-size:13px;font-weight:700;color:#7C3AED;margin-bottom:10px;"><i data-lucide="building-2" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i> الأكاديمية</div> <div style="font-size:13px;font-weight:700;color:#7C3AED;margin-bottom:10px;"><i data-lucide="building-2" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i> الأكاديمية</div>
<div class="form-group" style="margin:0;"> <div class="form-group" style="margin:0;">
...@@ -110,6 +111,7 @@ ...@@ -110,6 +111,7 @@
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</div> </div>
<div style="margin-top:8px;font-size:11px;color:#7C3AED;">مدرب أكاديمية لا يحتاج نموذج دفع — يتبع نظام رواتب الأكاديمية</div>
</div> </div>
<div id="financialFields" style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:20px;margin-top:15px;"> <div id="financialFields" style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:20px;margin-top:15px;">
<div class="form-group"> <div class="form-group">
...@@ -126,7 +128,7 @@ ...@@ -126,7 +128,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">أقصى عدد مجموعات</label> <label class="form-label">أقصى عدد مجموعات</label>
<input type="number" name="max_groups" value="<?= e(old('max_groups')) ?>" class="form-input" min="0" step="1" placeholder="مثال: 5" style="direction:ltr;text-align:left;"> <input type="number" name="max_groups" value="<?= e(old('max_groups') ?? '10') ?>" class="form-input" min="1" step="1" style="direction:ltr;text-align:left;">
</div> </div>
</div> </div>
</div> </div>
...@@ -166,7 +168,7 @@ ...@@ -166,7 +168,7 @@
<!-- Submit --> <!-- Submit -->
<div style="display:flex;gap:10px;justify-content:flex-start;"> <div style="display:flex;gap:10px;justify-content:flex-start;">
<button type="submit" class="btn btn-primary"><i data-lucide="check" style="width:16px;height:16px;vertical-align:middle;margin-left:4px;"></i> حفظ المدرب</button> <button type="submit" class="btn btn-primary" id="submitBtn"><i data-lucide="check" style="width:16px;height:16px;vertical-align:middle;margin-left:4px;"></i> حفظ المدرب</button>
<a href="/sa/coaches" class="btn btn-outline">إلغاء</a> <a href="/sa/coaches" class="btn btn-outline">إلغاء</a>
</div> </div>
</form> </form>
...@@ -197,102 +199,236 @@ ...@@ -197,102 +199,236 @@
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') { if (typeof lucide !== 'undefined') lucide.createIcons();
lucide.createIcons();
// === NID Parser (client-side, no API needed) ===
var nid = document.getElementById('nidInput');
var dobInput = document.getElementById('dobInput');
var genderSelect = document.getElementById('genderSelect');
var nidStatus = document.getElementById('nidStatus');
var ageDisplay = document.getElementById('ageDisplay');
var governorates = {'01':'القاهرة','02':'الإسكندرية','03':'بورسعيد','04':'السويس','11':'دمياط','12':'الدقهلية','13':'الشرقية','14':'القليوبية','15':'كفر الشيخ','16':'الغربية','17':'المنوفية','18':'البحيرة','19':'الإسماعيلية','21':'الجيزة','22':'بني سويف','23':'الفيوم','24':'المنيا','25':'أسيوط','26':'سوهاج','27':'قنا','28':'أسوان','29':'الأقصر','31':'البحر الأحمر','32':'الوادي الجديد','33':'مطروح','34':'شمال سيناء','35':'جنوب سيناء','88':'خارج مصر'};
function parseNidLocal(val) {
val = val.replace(/\D/g, '');
nid.value = val;
if (val.length < 14) {
nidStatus.style.display = 'none';
dobInput.readOnly = false;
dobInput.style.background = '';
genderSelect.disabled = false;
return;
}
if (val.length > 14) { val = val.substring(0, 14); nid.value = val; }
var century = val[0] === '2' ? '19' : (val[0] === '3' ? '20' : null);
if (!century) {
showNidError('الرقم القومي غير صحيح — يجب أن يبدأ بـ 2 أو 3');
return;
}
var year = century + val.substring(1, 3);
var month = val.substring(3, 5);
var day = val.substring(5, 7);
var govCode = val.substring(7, 9);
var genderDigit = parseInt(val[12]);
var y = parseInt(year), m = parseInt(month), d = parseInt(day);
if (m < 1 || m > 12 || d < 1 || d > 31) {
showNidError('تاريخ الميلاد في الرقم القومي غير صحيح');
return;
}
var dob = year + '-' + month + '-' + day;
var gender = (genderDigit % 2 === 0) ? 'female' : 'male';
var govName = governorates[govCode] || 'غير معروف';
// Calculate age
var birthDate = new Date(y, m - 1, d);
var today = new Date();
var age = today.getFullYear() - birthDate.getFullYear();
var mDiff = today.getMonth() - birthDate.getMonth();
if (mDiff < 0 || (mDiff === 0 && today.getDate() < birthDate.getDate())) age--;
// Set fields
dobInput.value = dob;
dobInput.readOnly = true;
dobInput.style.background = '#F0FDF4';
genderSelect.value = gender;
genderSelect.disabled = true;
ageDisplay.textContent = age + ' سنة';
// Show success
nidStatus.style.display = 'block';
nidStatus.style.background = '#ECFDF5';
nidStatus.style.color = '#059669';
nidStatus.innerHTML = '<strong>✓</strong> ' + govName + ' — ' + (gender === 'male' ? 'ذكر' : 'أنثى') + ' — ' + age + ' سنة';
}
function showNidError(msg) {
nidStatus.style.display = 'block';
nidStatus.style.background = '#FEF2F2';
nidStatus.style.color = '#DC2626';
nidStatus.textContent = msg;
dobInput.readOnly = false;
dobInput.style.background = '';
genderSelect.disabled = false;
} }
// Coach type toggle nid.addEventListener('input', function() { parseNidLocal(this.value); });
if (nid.value.length === 14) parseNidLocal(nid.value);
// DOB manual change -> show age
dobInput.addEventListener('change', function() {
if (!this.value) { ageDisplay.textContent = ''; return; }
var parts = this.value.split('-');
var birth = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
var today = new Date();
var age = today.getFullYear() - birth.getFullYear();
var m = today.getMonth() - birth.getMonth();
if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) age--;
ageDisplay.textContent = age + ' سنة';
});
// === Coach Type Toggle ===
var coachTypeSelect = document.getElementById('coachTypeSelect'); var coachTypeSelect = document.getElementById('coachTypeSelect');
var academyFields = document.getElementById('academyFields'); var academyFields = document.getElementById('academyFields');
var financialFields = document.getElementById('financialFields'); var financialFields = document.getElementById('financialFields');
var paymentModelField = document.getElementById('paymentModelField'); var paymentModelField = document.getElementById('paymentModelField');
var paymentModelSelect = document.getElementById('paymentModelSelect'); var paymentModelSelect = document.getElementById('paymentModelSelect');
function toggleAcademyFields() { var academySelect = document.getElementById('academySelect');
function toggleCoachType() {
var isAcademy = coachTypeSelect.value === 'academy'; var isAcademy = coachTypeSelect.value === 'academy';
academyFields.style.display = isAcademy ? 'block' : 'none'; academyFields.style.display = isAcademy ? 'block' : 'none';
financialFields.style.display = isAcademy ? 'none' : 'grid'; financialFields.style.display = isAcademy ? 'none' : 'grid';
paymentModelField.style.display = isAcademy ? 'none' : ''; paymentModelField.style.display = isAcademy ? 'none' : '';
var acadSelect = document.getElementById('academySelect');
if (isAcademy) { if (isAcademy) {
acadSelect.setAttribute('required', 'required'); paymentModelSelect.value = '';
paymentModelSelect.removeAttribute('required');
} else {
acadSelect.removeAttribute('required');
paymentModelSelect.setAttribute('required', 'required');
} }
} }
coachTypeSelect.addEventListener('change', toggleAcademyFields); coachTypeSelect.addEventListener('change', toggleCoachType);
toggleAcademyFields(); toggleCoachType();
// === Disciplines ===
var container = document.getElementById('disciplinesContainer'); var container = document.getElementById('disciplinesContainer');
var template = document.getElementById('disciplineRowTemplate'); var template = document.getElementById('disciplineRowTemplate');
var addBtn = document.getElementById('addDisciplineBtn'); var addBtn = document.getElementById('addDisciplineBtn');
var noMsg = document.getElementById('noDisciplinesMsg'); var noMsg = document.getElementById('noDisciplinesMsg');
function updateVisibility() { function updateDisciplineVisibility() {
var rows = container.querySelectorAll('.discipline-row'); noMsg.style.display = container.querySelectorAll('.discipline-row').length === 0 ? 'block' : 'none';
noMsg.style.display = rows.length === 0 ? 'block' : 'none';
} }
addBtn.addEventListener('click', function() { addBtn.addEventListener('click', function() {
var clone = template.content.cloneNode(true); var clone = template.content.cloneNode(true);
container.appendChild(clone); container.appendChild(clone);
updateVisibility(); updateDisciplineVisibility();
if (typeof lucide !== 'undefined') { if (typeof lucide !== 'undefined') lucide.createIcons();
lucide.createIcons();
}
}); });
container.addEventListener('click', function(e) { container.addEventListener('click', function(e) {
var btn = e.target.closest('.removeDisciplineBtn'); var btn = e.target.closest('.removeDisciplineBtn');
if (btn) { if (btn) { btn.closest('.discipline-row').remove(); updateDisciplineVisibility(); }
btn.closest('.discipline-row').remove();
updateVisibility();
}
}); });
updateDisciplineVisibility();
updateVisibility(); // === FORM VALIDATION ===
function markInvalid(el, msg) {
// NID auto-parse el.style.border = '2px solid #EF4444';
var nid = document.getElementById('nidInput'); el.style.background = '#FEF2F2';
var dob = document.getElementById('dobInput'); var hint = el.parentNode.querySelector('.val-hint');
var genderSelect = document.getElementById('genderSelect'); if (!hint) {
var genderHidden = document.getElementById('genderHidden'); hint = document.createElement('div');
var nidStatus = document.getElementById('nidStatus'); hint.className = 'val-hint';
hint.style.cssText = 'font-size:11px;color:#DC2626;margin-top:3px;font-weight:600;';
el.parentNode.appendChild(hint);
}
hint.textContent = msg;
}
function parseNid(val) { function clearAllValidation() {
if (val.length !== 14) { nidStatus.style.display = 'none'; dob.readOnly = false; dob.style.background = ''; genderSelect.disabled = false; return; } document.querySelectorAll('.val-hint').forEach(function(h) { h.remove(); });
var formData = new FormData(); document.querySelectorAll('[style*="2px solid"]').forEach(function(el) { el.style.border = ''; el.style.background = ''; });
formData.append('national_id', val);
var csrfToken = document.querySelector('input[name="_csrf_token"]');
if (csrfToken) formData.append('_csrf_token', csrfToken.value);
fetch('/api/members/parse-nid', {method: 'POST', body: formData})
.then(function(r){ return r.json(); })
.then(function(d){
var p = d.parsed;
if (p && p.is_valid) {
dob.value = p.dob || '';
dob.readOnly = true;
dob.style.background = '#F9FAFB';
var g = p.gender || '';
genderSelect.value = g;
genderSelect.disabled = true;
genderHidden.value = g;
nidStatus.style.display = 'block';
nidStatus.style.color = '#059669';
nidStatus.textContent = (p.governorate_name_ar || '') + ' — ' + (p.age_years || '') + ' سنة';
} else {
nidStatus.style.display = 'block';
nidStatus.style.color = '#DC2626';
nidStatus.textContent = (p && p.errors) ? p.errors[0] : 'رقم قومي غير صحيح';
}
}).catch(function(){});
} }
nid.addEventListener('input', function(){ parseNid(this.value.trim()); }); document.getElementById('coachForm').addEventListener('submit', function(e) {
genderSelect.addEventListener('change', function(){ genderHidden.value = this.value; }); clearAllValidation();
if (nid.value.trim().length === 14) parseNid(nid.value.trim()); var valid = true;
function check(name, msg) {
var el = document.querySelector('[name="' + name + '"]');
if (!el) return;
if (!el.value || !el.value.trim()) { markInvalid(el, msg); valid = false; }
}
// Required fields
check('code', 'كود المدرب مطلوب');
check('full_name_ar', 'الاسم بالعربي مطلوب');
check('employment_type', 'نوع التوظيف مطلوب');
var isAcademy = coachTypeSelect.value === 'academy';
if (!isAcademy) {
check('payment_model', 'نموذج الدفع مطلوب للمدربين غير الأكاديميين');
} else {
if (!academySelect.value) { markInvalid(academySelect, 'اختر الأكاديمية'); valid = false; }
}
// NID validation (if entered must be 14 digits)
var nidVal = nid.value.trim();
if (nidVal && nidVal.length !== 14) {
markInvalid(nid, 'الرقم القومي يجب أن يكون 14 رقم');
valid = false;
}
// Email format
var emailEl = document.querySelector('[name="email"]');
if (emailEl.value.trim() && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailEl.value.trim())) {
markInvalid(emailEl, 'صيغة البريد الإلكتروني غير صحيحة');
valid = false;
}
// Phone format
var phoneEl = document.querySelector('[name="phone"]');
if (phoneEl.value.trim() && !/^[0-9+\-\s()]{7,30}$/.test(phoneEl.value.trim())) {
markInvalid(phoneEl, 'رقم الهاتف غير صحيح');
valid = false;
}
if (!valid) {
e.preventDefault();
var firstBad = document.querySelector('[style*="2px solid"]');
if (firstBad) firstBad.scrollIntoView({behavior: 'smooth', block: 'center'});
}
});
// Clear validation on input
document.getElementById('coachForm').addEventListener('input', function(ev) {
var el = ev.target;
if (el.style.border && el.style.border.includes('solid')) {
el.style.border = '';
el.style.background = '';
var hint = el.parentNode.querySelector('.val-hint');
if (hint) hint.remove();
}
});
document.getElementById('coachForm').addEventListener('change', function(ev) {
var el = ev.target;
if (el.style.border && el.style.border.includes('solid')) {
el.style.border = '';
el.style.background = '';
var hint = el.parentNode.querySelector('.val-hint');
if (hint) hint.remove();
}
});
}); });
</script> </script>
<style>
.val-hint { animation: valShake 0.3s ease; }
@keyframes valShake { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-4px)} 75%{transform:translateX(4px)} }
</style>
<?php $__template->endSection(); ?> <?php $__template->endSection(); ?>
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