Commit 85a4f4fb authored by Mahmoud Aglan's avatar Mahmoud Aglan

Sports module overhaul: fix bugs, NID parsing, photo upload, cascading forms, auto-codes

- Fix route URL mismatches in player show.php (card/activate, card/suspend, card/revoke, enroll/drop)
- Fix Sports module permissions (was using temp.view/temp.add, now sports.view/sports.add/sports.convert)
- Add CSRF middleware to Sports POST route
- Fix AcademyEnrollment::getForPlayer() to JOIN academy/level names
- Add NID auto-parsing API and JS to player create/edit (deduces DOB, age, gender, governorate)
- Replace raw ID inputs in enrollment form with cascading dropdowns (academy→level→schedule)
- Add profile photo upload to player create/edit/show with live preview
- Add full player history timeline (enrollments, payments, medical, attendance)
- Add sport→facility cascade filter to reservation create form
- Make all code fields optional with auto-generation (Disciplines, Academies, Facilities, Levels)
- Overhaul Sports create form with discipline dropdown and competitive level select
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent e42b17a1
...@@ -62,14 +62,19 @@ class AcademyController extends Controller ...@@ -62,14 +62,19 @@ class AcademyController extends Controller
$descriptionAr = trim((string) $request->post('description_ar', '')); $descriptionAr = trim((string) $request->post('description_ar', ''));
$sortOrder = (int) $request->post('sort_order', 0); $sortOrder = (int) $request->post('sort_order', 0);
// Auto-generate code if empty
if ($code === '') {
$base = $nameEn ?: $nameAr;
$code = strtoupper(preg_replace('/[^A-Z0-9]/i', '_', $base));
$code = substr($code, 0, 25) . '_' . time() % 10000;
}
$code = strtoupper($code);
// Validation // Validation
$errors = []; $errors = [];
if ($nameAr === '' || mb_strlen($nameAr) < 2) { if ($nameAr === '' || mb_strlen($nameAr) < 2) {
$errors[] = 'الاسم بالعربي مطلوب (حرفان على الأقل)'; $errors[] = 'الاسم بالعربي مطلوب (حرفان على الأقل)';
} }
if ($code === '') {
$errors[] = 'كود الأكاديمية مطلوب';
}
if ($disciplineId <= 0) { if ($disciplineId <= 0) {
$errors[] = 'النشاط الرياضي مطلوب'; $errors[] = 'النشاط الرياضي مطلوب';
} else { } else {
...@@ -83,13 +88,11 @@ class AcademyController extends Controller ...@@ -83,13 +88,11 @@ class AcademyController extends Controller
} }
// Check unique code // Check unique code
if ($code !== '') {
$existing = Academy::query() $existing = Academy::query()
->where('code', '=', $code) ->where('code', '=', $code)
->first(); ->first();
if ($existing) { if ($existing) {
$errors[] = 'كود الأكاديمية مستخدم بالفعل'; $code = $code . '_' . bin2hex(random_bytes(2));
}
} }
if (!empty($errors)) { if (!empty($errors)) {
...@@ -178,14 +181,19 @@ class AcademyController extends Controller ...@@ -178,14 +181,19 @@ class AcademyController extends Controller
$descriptionAr = trim((string) $request->post('description_ar', '')); $descriptionAr = trim((string) $request->post('description_ar', ''));
$sortOrder = (int) $request->post('sort_order', 0); $sortOrder = (int) $request->post('sort_order', 0);
// Auto-generate code if empty
if ($code === '') {
$base = $nameEn ?: $nameAr;
$code = strtoupper(preg_replace('/[^A-Z0-9]/i', '_', $base));
$code = substr($code, 0, 25) . '_' . time() % 10000;
}
$code = strtoupper($code);
// Validation // Validation
$errors = []; $errors = [];
if ($nameAr === '' || mb_strlen($nameAr) < 2) { if ($nameAr === '' || mb_strlen($nameAr) < 2) {
$errors[] = 'الاسم بالعربي مطلوب (حرفان على الأقل)'; $errors[] = 'الاسم بالعربي مطلوب (حرفان على الأقل)';
} }
if ($code === '') {
$errors[] = 'كود الأكاديمية مطلوب';
}
if ($disciplineId <= 0) { if ($disciplineId <= 0) {
$errors[] = 'النشاط الرياضي مطلوب'; $errors[] = 'النشاط الرياضي مطلوب';
} else { } else {
...@@ -199,14 +207,12 @@ class AcademyController extends Controller ...@@ -199,14 +207,12 @@ class AcademyController extends Controller
} }
// Check unique code (exclude current) // Check unique code (exclude current)
if ($code !== '') {
$existing = Academy::query() $existing = Academy::query()
->where('code', '=', $code) ->where('code', '=', $code)
->whereRaw('`id` != ?', [(int) $id]) ->whereRaw('`id` != ?', [(int) $id])
->first(); ->first();
if ($existing) { if ($existing) {
$errors[] = 'كود الأكاديمية مستخدم بالفعل'; $errors[] = 'كود الأكاديمية مستخدم بالفعل — جرب كود مختلف';
}
} }
if (!empty($errors)) { if (!empty($errors)) {
...@@ -268,24 +274,25 @@ class AcademyController extends Controller ...@@ -268,24 +274,25 @@ class AcademyController extends Controller
$ageMax = (int) $request->post('age_max', 99); $ageMax = (int) $request->post('age_max', 99);
$maxCapacity = (int) $request->post('max_capacity', 0); $maxCapacity = (int) $request->post('max_capacity', 0);
// Auto-generate code if empty
if ($code === '') {
$code = 'LVL_' . strtoupper(substr(preg_replace('/[^A-Z0-9]/i', '', $nameEn ?: $nameAr), 0, 10)) . '_' . $levelOrder;
}
$code = strtoupper($code);
// Validation // Validation
$errors = []; $errors = [];
if ($nameAr === '' || mb_strlen($nameAr) < 2) { if ($nameAr === '' || mb_strlen($nameAr) < 2) {
$errors[] = 'اسم المستوى بالعربي مطلوب (حرفان على الأقل)'; $errors[] = 'اسم المستوى بالعربي مطلوب (حرفان على الأقل)';
} }
if ($code === '') {
$errors[] = 'كود المستوى مطلوب';
}
// Check unique code within this academy // Ensure unique code within this academy
if ($code !== '') {
$existing = AcademyLevel::query() $existing = AcademyLevel::query()
->where('academy_id', '=', (int) $id) ->where('academy_id', '=', (int) $id)
->where('code', '=', $code) ->where('code', '=', $code)
->first(); ->first();
if ($existing) { if ($existing) {
$errors[] = 'كود المستوى مستخدم بالفعل في هذه الأكاديمية'; $code = $code . '_' . bin2hex(random_bytes(2));
}
} }
if (!empty($errors)) { if (!empty($errors)) {
......
...@@ -29,9 +29,9 @@ ...@@ -29,9 +29,9 @@
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;margin-top:15px;"> <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;margin-top:15px;">
<div class="form-group"> <div class="form-group">
<label class="form-label">كود الأكاديمية <span style="color:#DC2626;">*</span></label> <label class="form-label">كود الأكاديمية</label>
<input type="text" name="code" value="<?= e(old('code')) ?>" class="form-input" id="codeInput" required placeholder="مثال: ACAD_FOOTBALL" style="direction:ltr;text-align:left;text-transform:uppercase;"> <input type="text" name="code" value="<?= e(old('code')) ?>" class="form-input" id="codeInput" placeholder="يتم توليده تلقائياً" style="direction:ltr;text-align:left;text-transform:uppercase;">
<small style="color:#9CA3AF;font-size:11px;">يتم توليده تلقائيا من الاسم الإنجليزي</small> <small style="color:#9CA3AF;font-size:11px;">اختياري — يتم توليده تلقائياً إن ترك فارغاً</small>
</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>
......
...@@ -36,8 +36,8 @@ $capacity = $config['capacity'] ?? ''; ...@@ -36,8 +36,8 @@ $capacity = $config['capacity'] ?? '';
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;margin-top:15px;"> <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;margin-top:15px;">
<div class="form-group"> <div class="form-group">
<label class="form-label">كود الأكاديمية <span style="color:#DC2626;">*</span></label> <label class="form-label">كود الأكاديمية</label>
<input type="text" name="code" value="<?= e(old('code') ?: $academy->code) ?>" class="form-input" id="codeInput" required style="direction:ltr;text-align:left;text-transform:uppercase;"> <input type="text" name="code" value="<?= e(old('code') ?: $academy->code) ?>" class="form-input" id="codeInput" 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>
......
...@@ -55,26 +55,29 @@ class DisciplineController extends Controller ...@@ -55,26 +55,29 @@ class DisciplineController extends Controller
$descriptionAr = trim((string) $request->post('description_ar', '')); $descriptionAr = trim((string) $request->post('description_ar', ''));
$sortOrder = (int) $request->post('sort_order', 0); $sortOrder = (int) $request->post('sort_order', 0);
// Auto-generate code if empty
if ($code === '') {
$base = $nameEn ?: $nameAr;
$code = strtoupper(preg_replace('/[^A-Z0-9]/i', '_', $base));
$code = substr($code, 0, 25) . '_' . time() % 10000;
}
$code = strtoupper($code);
// Validation // Validation
$errors = []; $errors = [];
if ($nameAr === '' || mb_strlen($nameAr) < 2) { if ($nameAr === '' || mb_strlen($nameAr) < 2) {
$errors[] = 'الاسم بالعربي مطلوب (حرفان على الأقل)'; $errors[] = 'الاسم بالعربي مطلوب (حرفان على الأقل)';
} }
if ($code === '') {
$errors[] = 'كود النشاط مطلوب';
}
if (!array_key_exists($category, SportDiscipline::getCategories())) { if (!array_key_exists($category, SportDiscipline::getCategories())) {
$errors[] = 'فئة النشاط غير صالحة'; $errors[] = 'فئة النشاط غير صالحة';
} }
// Check unique code // Ensure unique code
if ($code !== '') {
$existing = SportDiscipline::query() $existing = SportDiscipline::query()
->where('code', '=', $code) ->where('code', '=', $code)
->first(); ->first();
if ($existing) { if ($existing) {
$errors[] = 'كود النشاط مستخدم بالفعل'; $code = $code . '_' . bin2hex(random_bytes(2));
}
} }
if (!empty($errors)) { if (!empty($errors)) {
...@@ -154,14 +157,19 @@ class DisciplineController extends Controller ...@@ -154,14 +157,19 @@ class DisciplineController extends Controller
$descriptionAr = trim((string) $request->post('description_ar', '')); $descriptionAr = trim((string) $request->post('description_ar', ''));
$sortOrder = (int) $request->post('sort_order', 0); $sortOrder = (int) $request->post('sort_order', 0);
// Auto-generate code if empty
if ($code === '') {
$base = $nameEn ?: $nameAr;
$code = strtoupper(preg_replace('/[^A-Z0-9]/i', '_', $base));
$code = substr($code, 0, 25) . '_' . time() % 10000;
}
$code = strtoupper($code);
// Validation // Validation
$errors = []; $errors = [];
if ($nameAr === '' || mb_strlen($nameAr) < 2) { if ($nameAr === '' || mb_strlen($nameAr) < 2) {
$errors[] = 'الاسم بالعربي مطلوب (حرفان على الأقل)'; $errors[] = 'الاسم بالعربي مطلوب (حرفان على الأقل)';
} }
if ($code === '') {
$errors[] = 'كود النشاط مطلوب';
}
if (!array_key_exists($category, SportDiscipline::getCategories())) { if (!array_key_exists($category, SportDiscipline::getCategories())) {
$errors[] = 'فئة النشاط غير صالحة'; $errors[] = 'فئة النشاط غير صالحة';
} }
...@@ -173,7 +181,7 @@ class DisciplineController extends Controller ...@@ -173,7 +181,7 @@ class DisciplineController extends Controller
->whereRaw('`id` != ?', [(int) $id]) ->whereRaw('`id` != ?', [(int) $id])
->first(); ->first();
if ($existing) { if ($existing) {
$errors[] = 'كود النشاط مستخدم بالفعل'; $errors[] = 'كود النشاط مستخدم بالفعل — جرب كود مختلف';
} }
} }
......
...@@ -29,9 +29,9 @@ ...@@ -29,9 +29,9 @@
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;margin-top:15px;"> <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;margin-top:15px;">
<div class="form-group"> <div class="form-group">
<label class="form-label">كود النشاط <span style="color:#DC2626;">*</span></label> <label class="form-label">كود النشاط</label>
<input type="text" name="code" value="<?= e(old('code')) ?>" class="form-input" id="codeInput" required placeholder="مثال: FOOTBALL" style="direction:ltr;text-align:left;text-transform:uppercase;"> <input type="text" name="code" value="<?= e(old('code')) ?>" class="form-input" id="codeInput" placeholder="يتم توليده تلقائياً" style="direction:ltr;text-align:left;text-transform:uppercase;">
<small style="color:#9CA3AF;font-size:11px;">يتم توليده تلقائيا من الاسم الإنجليزي</small> <small style="color:#9CA3AF;font-size:11px;">اختياري — يتم توليده تلقائياً إن ترك فارغاً</small>
</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>
......
...@@ -36,8 +36,8 @@ $maxPlayers = $config['max_players_per_session'] ?? ''; ...@@ -36,8 +36,8 @@ $maxPlayers = $config['max_players_per_session'] ?? '';
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;margin-top:15px;"> <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px;margin-top:15px;">
<div class="form-group"> <div class="form-group">
<label class="form-label">كود النشاط <span style="color:#DC2626;">*</span></label> <label class="form-label">كود النشاط</label>
<input type="text" name="code" value="<?= e(old('code') ?: $discipline->code) ?>" class="form-input" id="codeInput" required style="direction:ltr;text-align:left;text-transform:uppercase;"> <input type="text" name="code" value="<?= e(old('code') ?: $discipline->code) ?>" class="form-input" id="codeInput" 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>
......
...@@ -69,14 +69,18 @@ class FacilityController extends Controller ...@@ -69,14 +69,18 @@ class FacilityController extends Controller
$disciplineId = $request->post('linked_discipline_id', ''); $disciplineId = $request->post('linked_discipline_id', '');
$disciplineId = $disciplineId !== '' ? (int) $disciplineId : null; $disciplineId = $disciplineId !== '' ? (int) $disciplineId : null;
// Auto-generate code if empty
if ($code === '') {
$typePrefix = strtoupper(substr($facilityType ?: 'FAC', 0, 5));
$code = $typePrefix . '_' . strtoupper(substr(preg_replace('/[^A-Z0-9]/i', '', $nameEn ?: $nameAr), 0, 10)) . '_' . time() % 10000;
}
$code = strtoupper($code);
// Validation // Validation
$errors = []; $errors = [];
if ($nameAr === '' || mb_strlen($nameAr) < 2) { if ($nameAr === '' || mb_strlen($nameAr) < 2) {
$errors[] = 'اسم المرفق بالعربي مطلوب (حرفان على الأقل)'; $errors[] = 'اسم المرفق بالعربي مطلوب (حرفان على الأقل)';
} }
if ($code === '') {
$errors[] = 'كود المرفق مطلوب';
}
if (!array_key_exists($facilityType, Facility::getTypes())) { if (!array_key_exists($facilityType, Facility::getTypes())) {
$errors[] = 'نوع المرفق غير صالح'; $errors[] = 'نوع المرفق غير صالح';
} }
...@@ -84,14 +88,12 @@ class FacilityController extends Controller ...@@ -84,14 +88,12 @@ class FacilityController extends Controller
$errors[] = 'الموقع غير صالح'; $errors[] = 'الموقع غير صالح';
} }
// Check unique code // Ensure unique code
if ($code !== '') {
$existing = Facility::query() $existing = Facility::query()
->where('code', '=', $code) ->where('code', '=', $code)
->first(); ->first();
if ($existing) { if ($existing) {
$errors[] = 'كود المرفق مستخدم بالفعل'; $code = $code . '_' . bin2hex(random_bytes(2));
}
} }
if (!empty($errors)) { if (!empty($errors)) {
...@@ -197,14 +199,18 @@ class FacilityController extends Controller ...@@ -197,14 +199,18 @@ class FacilityController extends Controller
$disciplineId = $request->post('linked_discipline_id', ''); $disciplineId = $request->post('linked_discipline_id', '');
$disciplineId = $disciplineId !== '' ? (int) $disciplineId : null; $disciplineId = $disciplineId !== '' ? (int) $disciplineId : null;
// Auto-generate code if empty
if ($code === '') {
$typePrefix = strtoupper(substr($facilityType ?: 'FAC', 0, 5));
$code = $typePrefix . '_' . strtoupper(substr(preg_replace('/[^A-Z0-9]/i', '', $nameEn ?: $nameAr), 0, 10)) . '_' . time() % 10000;
}
$code = strtoupper($code);
// Validation // Validation
$errors = []; $errors = [];
if ($nameAr === '' || mb_strlen($nameAr) < 2) { if ($nameAr === '' || mb_strlen($nameAr) < 2) {
$errors[] = 'اسم المرفق بالعربي مطلوب (حرفان على الأقل)'; $errors[] = 'اسم المرفق بالعربي مطلوب (حرفان على الأقل)';
} }
if ($code === '') {
$errors[] = 'كود المرفق مطلوب';
}
if (!array_key_exists($facilityType, Facility::getTypes())) { if (!array_key_exists($facilityType, Facility::getTypes())) {
$errors[] = 'نوع المرفق غير صالح'; $errors[] = 'نوع المرفق غير صالح';
} }
...@@ -219,7 +225,7 @@ class FacilityController extends Controller ...@@ -219,7 +225,7 @@ class FacilityController extends Controller
->whereRaw('`id` != ?', [(int) $id]) ->whereRaw('`id` != ?', [(int) $id])
->first(); ->first();
if ($existing) { if ($existing) {
$errors[] = 'كود المرفق مستخدم بالفعل'; $errors[] = 'كود المرفق مستخدم بالفعل — جرب كود مختلف';
} }
} }
......
...@@ -19,8 +19,9 @@ ...@@ -19,8 +19,9 @@
<div style="padding:20px;"> <div style="padding:20px;">
<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">كود المرفق</label>
<input type="text" name="code" value="<?= e(old('code')) ?>" class="form-input" required placeholder="مثال: PITCH-01" style="direction:ltr;text-align:left;text-transform:uppercase;"> <input type="text" name="code" value="<?= e(old('code')) ?>" class="form-input" placeholder="يتم توليده تلقائياً" style="direction:ltr;text-align:left;text-transform:uppercase;">
<small style="color:#9CA3AF;font-size:11px;">اختياري — يتم توليده تلقائياً</small>
</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>
......
...@@ -20,8 +20,8 @@ ...@@ -20,8 +20,8 @@
<div style="padding:20px;"> <div style="padding:20px;">
<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">كود المرفق</label>
<input type="text" name="code" value="<?= e(old('code') ?: $facility->code) ?>" class="form-input" required style="direction:ltr;text-align:left;text-transform:uppercase;"> <input type="text" name="code" value="<?= e(old('code') ?: $facility->code) ?>" class="form-input" 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>
......
...@@ -13,6 +13,7 @@ use App\Modules\PlayerAffairs\Models\PlayerMedicalRecord; ...@@ -13,6 +13,7 @@ use App\Modules\PlayerAffairs\Models\PlayerMedicalRecord;
use App\Modules\PlayerAffairs\Services\PlayerRegistrationService; use App\Modules\PlayerAffairs\Services\PlayerRegistrationService;
use App\Modules\PlayerAffairs\Services\PlayerCardService; use App\Modules\PlayerAffairs\Services\PlayerCardService;
use App\Modules\PlayerAffairs\Services\MedicalRecordService; use App\Modules\PlayerAffairs\Services\MedicalRecordService;
use App\Modules\Members\Services\NationalIdParser;
class PlayerController extends Controller class PlayerController extends Controller
{ {
...@@ -118,6 +119,12 @@ class PlayerController extends Controller ...@@ -118,6 +119,12 @@ class PlayerController extends Controller
'notes' => $notes ?: null, 'notes' => $notes ?: null,
]; ];
// Handle photo upload
$photoPath = self::handlePhotoUpload();
if ($photoPath) {
$data['photo_path'] = $photoPath;
}
try { try {
if ($playerType === 'member' && $memberId) { if ($playerType === 'member' && $memberId) {
$player = PlayerRegistrationService::registerMemberPlayer((int) $memberId, $data); $player = PlayerRegistrationService::registerMemberPlayer((int) $memberId, $data);
...@@ -144,7 +151,6 @@ class PlayerController extends Controller ...@@ -144,7 +151,6 @@ class PlayerController extends Controller
return $this->redirect('/players')->withError('اللاعب غير موجود'); return $this->redirect('/players')->withError('اللاعب غير موجود');
} }
// Derive disciplines from active enrollments (not the redundant player_disciplines table)
$db = App::getInstance()->db(); $db = App::getInstance()->db();
$disciplines = $db->select( $disciplines = $db->select(
"SELECT DISTINCT sd.id, sd.name_ar AS discipline_name, sd.icon, "SELECT DISTINCT sd.id, sd.name_ar AS discipline_name, sd.icon,
...@@ -159,11 +165,14 @@ class PlayerController extends Controller ...@@ -159,11 +165,14 @@ class PlayerController extends Controller
$enrollments = AcademyEnrollment::getForPlayer((int) $id); $enrollments = AcademyEnrollment::getForPlayer((int) $id);
$medicalRecords = PlayerMedicalRecord::getForPlayer((int) $id); $medicalRecords = PlayerMedicalRecord::getForPlayer((int) $id);
$history = self::buildPlayerHistory($db, (int) $id);
return $this->view('PlayerAffairs.Views.show', [ return $this->view('PlayerAffairs.Views.show', [
'player' => $player, 'player' => $player,
'disciplines' => $disciplines, 'disciplines' => $disciplines,
'enrollments' => $enrollments, 'enrollments' => $enrollments,
'medicalRecords' => $medicalRecords, 'medicalRecords' => $medicalRecords,
'history' => $history,
'cardStatuses' => Player::getCardStatuses(), 'cardStatuses' => Player::getCardStatuses(),
'medicalStatuses' => Player::getMedicalStatuses(), 'medicalStatuses' => Player::getMedicalStatuses(),
'playerTypes' => Player::getPlayerTypes(), 'playerTypes' => Player::getPlayerTypes(),
...@@ -232,7 +241,7 @@ class PlayerController extends Controller ...@@ -232,7 +241,7 @@ class PlayerController extends Controller
return $this->redirect('/players/' . $id . '/edit'); return $this->redirect('/players/' . $id . '/edit');
} }
$player->update([ $updateData = [
'full_name_ar' => $fullNameAr, 'full_name_ar' => $fullNameAr,
'full_name_en' => $fullNameEn ?: null, 'full_name_en' => $fullNameEn ?: null,
'national_id' => $nationalId ?: null, 'national_id' => $nationalId ?: null,
...@@ -249,7 +258,14 @@ class PlayerController extends Controller ...@@ -249,7 +258,14 @@ class PlayerController extends Controller
'school_name' => $schoolName ?: null, 'school_name' => $schoolName ?: null,
'school_grade' => $schoolGrade ?: null, 'school_grade' => $schoolGrade ?: null,
'notes' => $notes ?: null, 'notes' => $notes ?: null,
]); ];
$photoPath = self::handlePhotoUpload();
if ($photoPath) {
$updateData['photo_path'] = $photoPath;
}
$player->update($updateData);
return $this->redirect('/players/' . $id)->withSuccess('تم تحديث بيانات اللاعب بنجاح'); return $this->redirect('/players/' . $id)->withSuccess('تم تحديث بيانات اللاعب بنجاح');
} }
...@@ -403,4 +419,246 @@ class PlayerController extends Controller ...@@ -403,4 +419,246 @@ class PlayerController extends Controller
return $this->redirect('/players/' . $id)->withSuccess('تم انسحاب اللاعب من الأكاديمية'); return $this->redirect('/players/' . $id)->withSuccess('تم انسحاب اللاعب من الأكاديمية');
} }
/**
* Parse a National ID and return extracted data (DOB, age, gender, governorate).
*/
public function parseNid(Request $request): Response
{
$nid = trim((string) $request->post('national_id', ''));
if ($nid === '') {
return $this->json(['is_valid' => false, 'errors' => ['الرقم القومي مطلوب']]);
}
$parsed = NationalIdParser::parse($nid);
if ($parsed['is_valid']) {
$db = App::getInstance()->db();
$dup = $db->selectOne(
"SELECT id, full_name_ar, registration_serial FROM players WHERE national_id = ? AND is_archived = 0",
[$nid]
);
if ($dup) {
$parsed['duplicate'] = [
'type' => 'player',
'id' => (int) $dup['id'],
'name' => $dup['full_name_ar'],
'serial' => $dup['registration_serial'] ?? '',
];
}
}
return $this->json($parsed);
}
/**
* Build a unified timeline of all player activity.
*/
private static function buildPlayerHistory(\App\Core\Database $db, int $playerId): array
{
$events = [];
// Registration
$player = $db->selectOne("SELECT created_at, registration_serial FROM players WHERE id = ?", [$playerId]);
if ($player && $player['created_at']) {
$events[] = [
'date' => $player['created_at'],
'type' => 'registration',
'icon' => 'user-plus',
'color' => '#059669',
'title' => 'تسجيل اللاعب',
'desc' => 'تم تسجيل اللاعب بمسلسل: ' . ($player['registration_serial'] ?? '—'),
];
}
// Enrollments
$enrollments = $db->select(
"SELECT ae.enrolled_at, ae.dropped_at, ae.status, ae.dropped_reason, a.name_ar AS academy_name
FROM academy_enrollments ae
LEFT JOIN academies a ON a.id = ae.academy_id
WHERE ae.player_id = ?
ORDER BY ae.enrolled_at DESC",
[$playerId]
);
foreach ($enrollments as $e) {
$events[] = [
'date' => $e['enrolled_at'],
'type' => 'enrollment',
'icon' => 'book-open',
'color' => '#0284C7',
'title' => 'تسجيل في أكاديمية: ' . ($e['academy_name'] ?? '—'),
'desc' => '',
];
if ($e['dropped_at']) {
$events[] = [
'date' => $e['dropped_at'],
'type' => 'drop',
'icon' => 'log-out',
'color' => '#DC2626',
'title' => 'انسحاب من: ' . ($e['academy_name'] ?? '—'),
'desc' => $e['dropped_reason'] ?? '',
];
}
}
// Subscriptions (payments)
$subs = $db->select(
"SELECT asub.subscription_month, asub.total_amount, asub.status, asub.paid_at,
sd.name_ar AS discipline_name
FROM activity_subscriptions asub
LEFT JOIN sport_disciplines sd ON sd.id = asub.discipline_id
WHERE asub.player_id = ? AND asub.status = 'paid'
ORDER BY asub.paid_at DESC
LIMIT 20",
[$playerId]
);
foreach ($subs as $s) {
$events[] = [
'date' => $s['paid_at'] ?? $s['subscription_month'] . '-01',
'type' => 'payment',
'icon' => 'credit-card',
'color' => '#059669',
'title' => 'سداد اشتراك ' . ($s['discipline_name'] ?? '') . ' — ' . ($s['subscription_month'] ?? ''),
'desc' => money($s['total_amount'] ?? '0'),
];
}
// Medical records
$medicals = $db->select(
"SELECT exam_date, record_type, result, doctor_name FROM player_medical_records WHERE player_id = ? ORDER BY exam_date DESC LIMIT 10",
[$playerId]
);
$resultLabels = ['fit' => 'لائق', 'conditional' => 'لائق بشروط', 'unfit' => 'غير لائق'];
foreach ($medicals as $m) {
$events[] = [
'date' => $m['exam_date'] . ' 00:00:00',
'type' => 'medical',
'icon' => 'heart-pulse',
'color' => '#7C3AED',
'title' => 'فحص طبي — ' . ($resultLabels[$m['result']] ?? $m['result'] ?? ''),
'desc' => $m['doctor_name'] ?? '',
];
}
// Attendance summary (last 5 recorded days)
$attendance = $db->select(
"SELECT pa.attendance_date, pa.status, a.name_ar AS academy_name
FROM player_attendance pa
LEFT JOIN academy_enrollments ae ON ae.id = pa.enrollment_id
LEFT JOIN academies a ON a.id = ae.academy_id
WHERE pa.player_id = ?
ORDER BY pa.attendance_date DESC
LIMIT 10",
[$playerId]
);
foreach ($attendance as $att) {
$statusLabels = ['present' => 'حضور', 'absent' => 'غياب', 'late' => 'تأخير', 'excused' => 'إذن'];
$events[] = [
'date' => $att['attendance_date'] . ' 00:00:00',
'type' => 'attendance',
'icon' => 'calendar-check',
'color' => $att['status'] === 'present' ? '#059669' : '#D97706',
'title' => ($statusLabels[$att['status']] ?? $att['status']) . ' — ' . ($att['academy_name'] ?? ''),
'desc' => '',
];
}
usort($events, fn($a, $b) => strcmp($b['date'], $a['date']));
return array_slice($events, 0, 30);
}
/**
* Handle profile photo upload. Returns relative path or null.
*/
private static function handlePhotoUpload(): ?string
{
if (empty($_FILES['photo']) || $_FILES['photo']['error'] !== UPLOAD_ERR_OK) {
return null;
}
$file = $_FILES['photo'];
$allowed = ['image/jpeg', 'image/png', 'image/webp'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mime, $allowed)) {
return null;
}
if ($file['size'] > 5 * 1024 * 1024) {
return null;
}
$ext = match ($mime) {
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/webp' => 'webp',
default => 'jpg',
};
$uploadDir = App::getInstance()->basePath() . '/storage/uploads/players/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$filename = 'player_' . time() . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
if (move_uploaded_file($file['tmp_name'], $uploadDir . $filename)) {
return 'storage/uploads/players/' . $filename;
}
return null;
}
/**
* API: Get academies with their levels and schedules for cascading dropdowns.
*/
public function apiAcademies(Request $request): Response
{
$db = App::getInstance()->db();
$disciplineId = $request->get('discipline_id') ? (int) $request->get('discipline_id') : null;
$where = 'a.is_active = 1';
$params = [];
if ($disciplineId) {
$where .= ' AND a.discipline_id = ?';
$params[] = $disciplineId;
}
$academies = $db->select(
"SELECT a.id, a.name_ar, a.discipline_id, sd.name_ar AS discipline_name
FROM academies a
LEFT JOIN sport_disciplines sd ON sd.id = a.discipline_id
WHERE {$where}
ORDER BY sd.name_ar, a.name_ar",
$params
);
$levels = $db->select(
"SELECT al.id, al.academy_id, al.name_ar
FROM academy_levels al
JOIN academies a ON a.id = al.academy_id
WHERE a.is_active = 1
ORDER BY al.sort_order, al.name_ar"
);
$schedules = $db->select(
"SELECT acs.id, acs.academy_id, acs.level_id, acs.day_of_week, acs.start_time, acs.end_time,
f.name_ar AS facility_name
FROM academy_schedules acs
LEFT JOIN facilities f ON f.id = acs.facility_id
JOIN academies a ON a.id = acs.academy_id
WHERE a.is_active = 1
ORDER BY acs.day_of_week, acs.start_time"
);
return $this->json([
'academies' => $academies,
'levels' => $levels,
'schedules' => $schedules,
]);
}
} }
...@@ -66,14 +66,20 @@ class AcademyEnrollment extends Model ...@@ -66,14 +66,20 @@ class AcademyEnrollment extends Model
} }
/** /**
* Get all enrollments for a specific player. * Get all enrollments for a specific player with academy and level names.
*/ */
public static function getForPlayer(int $playerId): array public static function getForPlayer(int $playerId): array
{ {
return static::query() $db = \App\Core\App::getInstance()->db();
->where('player_id', '=', $playerId) return $db->select(
->orderBy('created_at', 'DESC') "SELECT ae.*, a.name_ar AS academy_name, al.name_ar AS level_name
->get(); FROM academy_enrollments ae
LEFT JOIN academies a ON a.id = ae.academy_id
LEFT JOIN academy_levels al ON al.id = ae.level_id
WHERE ae.player_id = ?
ORDER BY ae.created_at DESC",
[$playerId]
);
} }
/** /**
......
...@@ -15,6 +15,10 @@ return [ ...@@ -15,6 +15,10 @@ return [
['POST', '/players/{id:\d+}/enroll', 'PlayerAffairs\Controllers\PlayerController@enroll', ['auth', 'csrf'], 'academy.enroll'], ['POST', '/players/{id:\d+}/enroll', 'PlayerAffairs\Controllers\PlayerController@enroll', ['auth', 'csrf'], 'academy.enroll'],
['POST', '/players/{id:\d+}/enroll/drop', 'PlayerAffairs\Controllers\PlayerController@dropEnrollment', ['auth', 'csrf'], 'academy.enroll'], ['POST', '/players/{id:\d+}/enroll/drop', 'PlayerAffairs\Controllers\PlayerController@dropEnrollment', ['auth', 'csrf'], 'academy.enroll'],
// API
['POST', '/api/players/parse-nid', 'PlayerAffairs\Controllers\PlayerController@parseNid', ['auth'], 'player.register'],
['GET', '/api/players/academies', 'PlayerAffairs\Controllers\PlayerController@apiAcademies', ['auth'], 'player.view'],
// Attendance // Attendance
['GET', '/attendance', 'PlayerAffairs\Controllers\AttendanceController@index', ['auth'], 'player.view'], ['GET', '/attendance', 'PlayerAffairs\Controllers\AttendanceController@index', ['auth'], 'player.view'],
['POST', '/attendance/record', 'PlayerAffairs\Controllers\AttendanceController@record', ['auth', 'csrf'], 'player.edit'], ['POST', '/attendance/record', 'PlayerAffairs\Controllers\AttendanceController@record', ['auth', 'csrf'], 'player.edit'],
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<?php $__template->section('content'); ?> <?php $__template->section('content'); ?>
<form method="POST" action="/players" id="playerForm"> <form method="POST" action="/players" id="playerForm" enctype="multipart/form-data">
<?= csrf_field() ?> <?= csrf_field() ?>
<!-- Section 1: Basic Data --> <!-- Section 1: Basic Data -->
...@@ -31,6 +31,14 @@ ...@@ -31,6 +31,14 @@
<label class="form-label">رقم العضوية</label> <label class="form-label">رقم العضوية</label>
<input type="number" name="member_id" value="<?= e(old('member_id')) ?>" class="form-input" min="1" style="direction:ltr;text-align:left;" placeholder="رقم العضوية في النادي"> <input type="number" name="member_id" value="<?= e(old('member_id')) ?>" class="form-input" min="1" style="direction:ltr;text-align:left;" placeholder="رقم العضوية في النادي">
</div> </div>
<div class="form-group" style="text-align:center;">
<label class="form-label">الصورة الشخصية</label>
<div style="width:100px;height:100px;border-radius:50%;background:#F3F4F6;margin:0 auto 8px;display:flex;align-items:center;justify-content:center;overflow:hidden;border:2px dashed #D1D5DB;cursor:pointer;" onclick="document.getElementById('photoInput').click();" id="photoPreview">
<i data-lucide="camera" style="width:28px;height:28px;color:#9CA3AF;"></i>
</div>
<input type="file" name="photo" id="photoInput" accept="image/jpeg,image/png,image/webp" style="display:none;">
<small style="color:#9CA3AF;font-size:11px;">JPG, PNG أو WebP (حد أقصى 5MB)</small>
</div>
<div class="form-group" style="grid-column:1/-1;"> <div class="form-group" style="grid-column:1/-1;">
<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 placeholder="الاسم رباعي بالعربي" style="font-size:16px;"> <input type="text" name="full_name_ar" value="<?= e(old('full_name_ar')) ?>" class="form-input" required placeholder="الاسم رباعي بالعربي" style="font-size:16px;">
...@@ -41,7 +49,8 @@ ...@@ -41,7 +49,8 @@
</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="national_id" value="<?= e(old('national_id')) ?>" class="form-input" maxlength="14" style="direction:ltr;text-align:left;font-size:16px;letter-spacing:1px;" placeholder="أدخل 14 رقم"> <input type="text" name="national_id" id="nidInput" value="<?= e(old('national_id')) ?>" class="form-input" maxlength="14" style="direction:ltr;text-align:left;font-size:16px;letter-spacing:1px;" placeholder="أدخل 14 رقم">
<div id="nidFeedback" style="margin-top:4px;font-size:12px;display:none;"></div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">رقم جواز السفر</label> <label class="form-label">رقم جواز السفر</label>
...@@ -49,17 +58,25 @@ ...@@ -49,17 +58,25 @@
</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="date" name="date_of_birth" value="<?= e(old('date_of_birth')) ?>" class="form-input" required> <input type="date" name="date_of_birth" id="dobInput" value="<?= e(old('date_of_birth')) ?>" class="form-input" required>
</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="gender" class="form-input" required> <select name="gender" id="genderInput" class="form-input" required>
<option value="">-- اختر --</option> <option value="">-- اختر --</option>
<?php foreach ($genders as $key => $label): ?> <?php foreach ($genders as $key => $label): ?>
<option value="<?= e($key) ?>" <?= old('gender') === $key ? 'selected' : '' ?>><?= e($label) ?></option> <option value="<?= e($key) ?>" <?= old('gender') === $key ? 'selected' : '' ?>><?= e($label) ?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</div> </div>
<div class="form-group">
<label class="form-label">العمر</label>
<input type="text" id="ageDisplay" class="form-input" readonly style="background:#F3F4F6;cursor:not-allowed;" placeholder="يتم حسابه تلقائياً">
</div>
<div class="form-group">
<label class="form-label">المحافظة</label>
<input type="text" id="govDisplay" class="form-input" readonly style="background:#F3F4F6;cursor:not-allowed;" placeholder="تُستنتج من الرقم القومي">
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -160,6 +177,77 @@ document.addEventListener('DOMContentLoaded', function() { ...@@ -160,6 +177,77 @@ document.addEventListener('DOMContentLoaded', function() {
playerTypeSelect.addEventListener('change', toggleMemberId); playerTypeSelect.addEventListener('change', toggleMemberId);
toggleMemberId(); toggleMemberId();
// Photo preview
var photoInput = document.getElementById('photoInput');
var photoPreview = document.getElementById('photoPreview');
photoInput.addEventListener('change', function() {
if (this.files && this.files[0]) {
var reader = new FileReader();
reader.onload = function(e) {
photoPreview.innerHTML = '<img src="' + e.target.result + '" style="width:100%;height:100%;object-fit:cover;">';
};
reader.readAsDataURL(this.files[0]);
}
});
// NID Auto-Parse
var nidInput = document.getElementById('nidInput');
var nidFeedback = document.getElementById('nidFeedback');
var dobInput = document.getElementById('dobInput');
var genderInput = document.getElementById('genderInput');
var ageDisplay = document.getElementById('ageDisplay');
var govDisplay = document.getElementById('govDisplay');
var nidTimer = null;
nidInput.addEventListener('input', function() {
clearTimeout(nidTimer);
var val = this.value.replace(/\D/g, '');
this.value = val;
nidFeedback.style.display = 'none';
if (val.length === 14) {
nidTimer = setTimeout(function() { parseNid(val); }, 200);
} else {
nidInput.style.borderColor = '';
}
});
function parseNid(nid) {
var formData = new FormData();
formData.append('national_id', nid);
formData.append('_csrf_token', document.querySelector('input[name="_csrf_token"]').value);
fetch('/api/players/parse-nid', {method: 'POST', body: formData})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.is_valid) {
nidInput.style.borderColor = '#059669';
nidFeedback.style.display = 'block';
nidFeedback.style.color = '#059669';
nidFeedback.textContent = 'رقم قومي صالح';
if (data.dob) dobInput.value = data.dob;
if (data.gender) genderInput.value = data.gender;
if (data.age_years !== null) ageDisplay.value = data.age_years + ' سنة' + (data.age_months ? ' و ' + data.age_months + ' شهر' : '');
if (data.governorate_name_ar) govDisplay.value = data.governorate_name_ar;
if (data.duplicate) {
nidFeedback.style.color = '#D97706';
nidFeedback.innerHTML = '<strong>تحذير:</strong> هذا الرقم القومي مسجل بالفعل للاعب: ' +
data.duplicate.name + ' (مسلسل: ' + data.duplicate.serial + ')';
}
} else {
nidInput.style.borderColor = '#DC2626';
nidFeedback.style.display = 'block';
nidFeedback.style.color = '#DC2626';
nidFeedback.textContent = (data.errors && data.errors[0]) || 'رقم قومي غير صالح';
}
})
.catch(function() {
nidInput.style.borderColor = '';
});
}
}); });
</script> </script>
<?php $__template->endSection(); ?> <?php $__template->endSection(); ?>
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<?php $__template->section('content'); ?> <?php $__template->section('content'); ?>
<form method="POST" action="/players/<?= (int) $player->id ?>" id="playerEditForm"> <form method="POST" action="/players/<?= (int) $player->id ?>" id="playerEditForm" enctype="multipart/form-data">
<?= csrf_field() ?> <?= csrf_field() ?>
<!-- Section 1: Basic Data --> <!-- Section 1: Basic Data -->
...@@ -28,6 +28,18 @@ ...@@ -28,6 +28,18 @@
<input type="hidden" name="player_type" value="<?= e($player->player_type ?? '') ?>"> <input type="hidden" name="player_type" value="<?= e($player->player_type ?? '') ?>">
<small style="color:#9CA3AF;font-size:11px;">لا يمكن تغيير نوع اللاعب بعد التسجيل</small> <small style="color:#9CA3AF;font-size:11px;">لا يمكن تغيير نوع اللاعب بعد التسجيل</small>
</div> </div>
<div class="form-group" style="text-align:center;">
<label class="form-label">الصورة الشخصية</label>
<div style="width:100px;height:100px;border-radius:50%;background:#F3F4F6;margin:0 auto 8px;display:flex;align-items:center;justify-content:center;overflow:hidden;border:2px dashed #D1D5DB;cursor:pointer;" onclick="document.getElementById('photoInput').click();" id="photoPreview">
<?php if (!empty($player->photo_path)): ?>
<img src="/<?= e($player->photo_path) ?>" style="width:100%;height:100%;object-fit:cover;">
<?php else: ?>
<i data-lucide="camera" style="width:28px;height:28px;color:#9CA3AF;"></i>
<?php endif; ?>
</div>
<input type="file" name="photo" id="photoInput" accept="image/jpeg,image/png,image/webp" style="display:none;">
<small style="color:#9CA3AF;font-size:11px;">JPG, PNG أو WebP (حد أقصى 5MB)</small>
</div>
<?php if (($player->player_type ?? '') === 'member'): ?> <?php if (($player->player_type ?? '') === 'member'): ?>
<div class="form-group"> <div class="form-group">
<label class="form-label">رقم العضوية</label> <label class="form-label">رقم العضوية</label>
...@@ -44,7 +56,8 @@ ...@@ -44,7 +56,8 @@
</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="national_id" value="<?= e(old('national_id') ?: ($player->national_id ?? '')) ?>" class="form-input" maxlength="14" style="direction:ltr;text-align:left;font-size:16px;letter-spacing:1px;" placeholder="أدخل 14 رقم"> <input type="text" name="national_id" id="nidInput" value="<?= e(old('national_id') ?: ($player->national_id ?? '')) ?>" class="form-input" maxlength="14" style="direction:ltr;text-align:left;font-size:16px;letter-spacing:1px;" placeholder="أدخل 14 رقم">
<div id="nidFeedback" style="margin-top:4px;font-size:12px;display:none;"></div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">رقم جواز السفر</label> <label class="form-label">رقم جواز السفر</label>
...@@ -52,17 +65,25 @@ ...@@ -52,17 +65,25 @@
</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="date" name="date_of_birth" value="<?= e(old('date_of_birth') ?: ($player->date_of_birth ?? '')) ?>" class="form-input" required> <input type="date" name="date_of_birth" id="dobInput" value="<?= e(old('date_of_birth') ?: ($player->date_of_birth ?? '')) ?>" class="form-input" required>
</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="gender" class="form-input" required> <select name="gender" id="genderInput" class="form-input" required>
<option value="">-- اختر --</option> <option value="">-- اختر --</option>
<?php foreach ($genders as $key => $label): ?> <?php foreach ($genders as $key => $label): ?>
<option value="<?= e($key) ?>" <?= (old('gender') ?: ($player->gender ?? '')) === $key ? 'selected' : '' ?>><?= e($label) ?></option> <option value="<?= e($key) ?>" <?= (old('gender') ?: ($player->gender ?? '')) === $key ? 'selected' : '' ?>><?= e($label) ?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</div> </div>
<div class="form-group">
<label class="form-label">العمر</label>
<input type="text" id="ageDisplay" class="form-input" readonly style="background:#F3F4F6;cursor:not-allowed;" value="<?= $player->date_of_birth ? ((new \DateTime($player->date_of_birth))->diff(new \DateTime())->y . ' سنة') : '' ?>">
</div>
<div class="form-group">
<label class="form-label">المحافظة</label>
<input type="text" id="govDisplay" class="form-input" readonly style="background:#F3F4F6;cursor:not-allowed;" placeholder="تُستنتج من الرقم القومي">
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -149,6 +170,76 @@ ...@@ -149,6 +170,76 @@
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') lucide.createIcons(); if (typeof lucide !== 'undefined') lucide.createIcons();
// Photo preview
var photoInput = document.getElementById('photoInput');
var photoPreview = document.getElementById('photoPreview');
photoInput.addEventListener('change', function() {
if (this.files && this.files[0]) {
var reader = new FileReader();
reader.onload = function(e) {
photoPreview.innerHTML = '<img src="' + e.target.result + '" style="width:100%;height:100%;object-fit:cover;">';
};
reader.readAsDataURL(this.files[0]);
}
});
// NID Auto-Parse
var nidInput = document.getElementById('nidInput');
var nidFeedback = document.getElementById('nidFeedback');
var dobInput = document.getElementById('dobInput');
var genderInput = document.getElementById('genderInput');
var ageDisplay = document.getElementById('ageDisplay');
var govDisplay = document.getElementById('govDisplay');
var nidTimer = null;
// Parse existing NID on load to show governorate
if (nidInput.value.length === 14) {
parseNid(nidInput.value);
}
nidInput.addEventListener('input', function() {
clearTimeout(nidTimer);
var val = this.value.replace(/\D/g, '');
this.value = val;
nidFeedback.style.display = 'none';
if (val.length === 14) {
nidTimer = setTimeout(function() { parseNid(val); }, 200);
} else {
nidInput.style.borderColor = '';
}
});
function parseNid(nid) {
var formData = new FormData();
formData.append('national_id', nid);
formData.append('_csrf_token', document.querySelector('input[name="_csrf_token"]').value);
fetch('/api/players/parse-nid', {method: 'POST', body: formData})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.is_valid) {
nidInput.style.borderColor = '#059669';
nidFeedback.style.display = 'block';
nidFeedback.style.color = '#059669';
nidFeedback.textContent = 'رقم قومي صالح';
if (data.dob) dobInput.value = data.dob;
if (data.gender) genderInput.value = data.gender;
if (data.age_years !== null) ageDisplay.value = data.age_years + ' سنة' + (data.age_months ? ' و ' + data.age_months + ' شهر' : '');
if (data.governorate_name_ar) govDisplay.value = data.governorate_name_ar;
} else {
nidInput.style.borderColor = '#DC2626';
nidFeedback.style.display = 'block';
nidFeedback.style.color = '#DC2626';
nidFeedback.textContent = (data.errors && data.errors[0]) || 'رقم قومي غير صالح';
}
})
.catch(function() {
nidInput.style.borderColor = '';
});
}
}); });
</script> </script>
<?php $__template->endSection(); ?> <?php $__template->endSection(); ?>
...@@ -32,6 +32,14 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses(); ...@@ -32,6 +32,14 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses();
<!-- Header Card --> <!-- Header Card -->
<div class="card" style="margin-bottom:20px;padding:25px;"> <div class="card" style="margin-bottom:20px;padding:25px;">
<div style="display:flex;justify-content:space-between;align-items:start;"> <div style="display:flex;justify-content:space-between;align-items:start;">
<div style="display:flex;gap:20px;align-items:start;">
<div style="width:80px;height:80px;border-radius:50%;background:#F3F4F6;display:flex;align-items:center;justify-content:center;overflow:hidden;flex-shrink:0;border:3px solid <?= $cardColor ?>;">
<?php if (!empty($player->photo_path)): ?>
<img src="/<?= e($player->photo_path) ?>" style="width:100%;height:100%;object-fit:cover;">
<?php else: ?>
<i data-lucide="user" style="width:32px;height:32px;color:#9CA3AF;"></i>
<?php endif; ?>
</div>
<div> <div>
<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px;flex-wrap:wrap;"> <div style="display:flex;align-items:center;gap:12px;margin-bottom:8px;flex-wrap:wrap;">
<h2 style="margin:0;font-size:22px;font-weight:700;color:#1A1A2E;"><?= e($player->full_name_ar) ?></h2> <h2 style="margin:0;font-size:22px;font-weight:700;color:#1A1A2E;"><?= e($player->full_name_ar) ?></h2>
...@@ -56,6 +64,7 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses(); ...@@ -56,6 +64,7 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses();
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
</div>
<div style="text-align:left;"> <div style="text-align:left;">
<span style="display:inline-block;padding:4px 12px;border-radius:10px;font-size:12px;font-weight:600;background:<?= $medicalColor ?>15;color:<?= $medicalColor ?>;"><?= e($medicalLabel) ?></span> <span style="display:inline-block;padding:4px 12px;border-radius:10px;font-size:12px;font-weight:600;background:<?= $medicalColor ?>15;color:<?= $medicalColor ?>;"><?= e($medicalLabel) ?></span>
<?php if ($player->medical_expiry_date): ?> <?php if ($player->medical_expiry_date): ?>
...@@ -186,7 +195,7 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses(); ...@@ -186,7 +195,7 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses();
<div style="display:flex;gap:10px;flex-wrap:wrap;"> <div style="display:flex;gap:10px;flex-wrap:wrap;">
<?php if (in_array($cardStatus, ['inactive', 'suspended', 'revoked'])): ?> <?php if (in_array($cardStatus, ['inactive', 'suspended', 'revoked'])): ?>
<form method="POST" action="/players/<?= (int) $player->id ?>/activate-card" style="display:inline;"> <form method="POST" action="/players/<?= (int) $player->id ?>/card/activate" style="display:inline;">
<?= csrf_field() ?> <?= csrf_field() ?>
<button type="submit" class="btn btn-primary" style="font-size:13px;" onclick="return confirm('هل أنت متأكد من تفعيل الكارنيه؟')"> <button type="submit" class="btn btn-primary" style="font-size:13px;" onclick="return confirm('هل أنت متأكد من تفعيل الكارنيه؟')">
<i data-lucide="check-circle" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i> تفعيل الكارنيه <i data-lucide="check-circle" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i> تفعيل الكارنيه
...@@ -198,7 +207,7 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses(); ...@@ -198,7 +207,7 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses();
<button type="button" class="btn btn-outline" style="font-size:13px;color:#D97706;border-color:#D97706;" onclick="document.getElementById('suspendCardForm').style.display = document.getElementById('suspendCardForm').style.display === 'none' ? 'block' : 'none';"> <button type="button" class="btn btn-outline" style="font-size:13px;color:#D97706;border-color:#D97706;" onclick="document.getElementById('suspendCardForm').style.display = document.getElementById('suspendCardForm').style.display === 'none' ? 'block' : 'none';">
<i data-lucide="pause-circle" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i> إيقاف الكارنيه <i data-lucide="pause-circle" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i> إيقاف الكارنيه
</button> </button>
<form method="POST" action="/players/<?= (int) $player->id ?>/revoke-card" style="display:inline;"> <form method="POST" action="/players/<?= (int) $player->id ?>/card/revoke" style="display:inline;">
<?= csrf_field() ?> <?= csrf_field() ?>
<button type="submit" class="btn btn-danger" style="font-size:13px;" onclick="return confirm('هل أنت متأكد من إلغاء الكارنيه؟ هذا الإجراء خطير.')"> <button type="submit" class="btn btn-danger" style="font-size:13px;" onclick="return confirm('هل أنت متأكد من إلغاء الكارنيه؟ هذا الإجراء خطير.')">
<i data-lucide="x-circle" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i> إلغاء الكارنيه <i data-lucide="x-circle" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i> إلغاء الكارنيه
...@@ -209,7 +218,7 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses(); ...@@ -209,7 +218,7 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses();
<?php if ($cardStatus === 'active'): ?> <?php if ($cardStatus === 'active'): ?>
<div id="suspendCardForm" style="display:none;margin-top:15px;padding:15px;background:#FFF7ED;border:1px solid #FDE68A;border-radius:8px;"> <div id="suspendCardForm" style="display:none;margin-top:15px;padding:15px;background:#FFF7ED;border:1px solid #FDE68A;border-radius:8px;">
<form method="POST" action="/players/<?= (int) $player->id ?>/suspend-card"> <form method="POST" action="/players/<?= (int) $player->id ?>/card/suspend">
<?= csrf_field() ?> <?= csrf_field() ?>
<div class="form-group" style="margin-bottom:10px;"> <div class="form-group" style="margin-bottom:10px;">
<label class="form-label" style="font-size:12px;">سبب الإيقاف</label> <label class="form-label" style="font-size:12px;">سبب الإيقاف</label>
...@@ -283,22 +292,28 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses(); ...@@ -283,22 +292,28 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses();
<div id="enrollForm" style="display:none;padding:20px;background:#F9FAFB;border-bottom:1px solid #E5E7EB;"> <div id="enrollForm" style="display:none;padding:20px;background:#F9FAFB;border-bottom:1px solid #E5E7EB;">
<form method="POST" action="/players/<?= (int) $player->id ?>/enroll"> <form method="POST" action="/players/<?= (int) $player->id ?>/enroll">
<?= csrf_field() ?> <?= csrf_field() ?>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:15px;"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:15px;">
<div class="form-group" style="margin:0;"> <div class="form-group" style="margin:0;">
<label class="form-label" style="font-size:12px;">رقم الأكاديمية <span style="color:#DC2626;">*</span></label> <label class="form-label" style="font-size:12px;">الأكاديمية <span style="color:#DC2626;">*</span></label>
<input type="number" name="academy_id" class="form-input" required min="1" style="direction:ltr;text-align:left;" placeholder="رقم الأكاديمية"> <select name="academy_id" id="enrollAcademy" class="form-input" required>
<option value="">-- اختر الأكاديمية --</option>
</select>
</div> </div>
<div class="form-group" style="margin:0;"> <div class="form-group" style="margin:0;">
<label class="form-label" style="font-size:12px;">رقم المستوى <span style="color:#DC2626;">*</span></label> <label class="form-label" style="font-size:12px;">المستوى <span style="color:#DC2626;">*</span></label>
<input type="number" name="level_id" class="form-input" required min="1" style="direction:ltr;text-align:left;" placeholder="رقم المستوى"> <select name="level_id" id="enrollLevel" class="form-input" required>
<option value="">-- اختر الأكاديمية أولاً --</option>
</select>
</div> </div>
<div class="form-group" style="margin:0;"> <div class="form-group" style="margin:0;">
<label class="form-label" style="font-size:12px;">رقم الجدول</label> <label class="form-label" style="font-size:12px;">الجدول / الموعد</label>
<input type="number" name="schedule_id" class="form-input" min="1" style="direction:ltr;text-align:left;" placeholder="رقم الجدول"> <select name="schedule_id" id="enrollSchedule" class="form-input">
<option value="">-- اختياري --</option>
</select>
</div> </div>
<div class="form-group" style="margin:0;"> <div class="form-group" style="margin:0;">
<label class="form-label" style="font-size:12px;">الموسم</label> <label class="form-label" style="font-size:12px;">الموسم</label>
<input type="text" name="season" class="form-input" placeholder="مثال: 2025-2026" style="direction:ltr;text-align:left;"> <input type="text" name="season" class="form-input" value="<?= date('Y') . '-' . (date('Y') + 1) ?>" style="direction:ltr;text-align:left;">
</div> </div>
</div> </div>
<div style="margin-top:15px;display:flex;gap:8px;"> <div style="margin-top:15px;display:flex;gap:8px;">
...@@ -343,7 +358,7 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses(); ...@@ -343,7 +358,7 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses();
<i data-lucide="log-out" style="width:13px;height:13px;vertical-align:middle;"></i> انسحاب <i data-lucide="log-out" style="width:13px;height:13px;vertical-align:middle;"></i> انسحاب
</button> </button>
<div id="dropForm-<?= (int) ($enroll['id'] ?? 0) ?>" style="display:none;margin-top:8px;"> <div id="dropForm-<?= (int) ($enroll['id'] ?? 0) ?>" style="display:none;margin-top:8px;">
<form method="POST" action="/players/<?= (int) $player->id ?>/drop-enrollment"> <form method="POST" action="/players/<?= (int) $player->id ?>/enroll/drop">
<?= csrf_field() ?> <?= csrf_field() ?>
<input type="hidden" name="enrollment_id" value="<?= (int) ($enroll['id'] ?? 0) ?>"> <input type="hidden" name="enrollment_id" value="<?= (int) ($enroll['id'] ?? 0) ?>">
<textarea name="dropped_reason" class="form-input" rows="2" placeholder="سبب الانسحاب..." style="font-size:12px;margin-bottom:6px;"></textarea> <textarea name="dropped_reason" class="form-input" rows="2" placeholder="سبب الانسحاب..." style="font-size:12px;margin-bottom:6px;"></textarea>
...@@ -479,9 +494,90 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses(); ...@@ -479,9 +494,90 @@ $enrollmentStatuses = AcademyEnrollment::getStatuses();
<?php endif; ?> <?php endif; ?>
</div> </div>
<!-- Player History Timeline -->
<div class="card" style="margin-bottom:20px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:8px;">
<i data-lucide="clock" style="width:18px;height:18px;color:#1A1A2E;"></i>
<h3 style="margin:0;color:#1A1A2E;font-size:15px;">السجل التاريخي</h3>
</div>
<?php if (!empty($history)): ?>
<div style="padding:20px;">
<div style="position:relative;padding-right:25px;">
<div style="position:absolute;right:8px;top:0;bottom:0;width:2px;background:#E5E7EB;"></div>
<?php foreach ($history as $event): ?>
<div style="position:relative;margin-bottom:20px;padding-right:30px;">
<div style="position:absolute;right:0;top:2px;width:18px;height:18px;border-radius:50%;background:<?= $event['color'] ?>15;display:flex;align-items:center;justify-content:center;border:2px solid <?= $event['color'] ?>;">
<i data-lucide="<?= e($event['icon']) ?>" style="width:10px;height:10px;color:<?= $event['color'] ?>;"></i>
</div>
<div>
<div style="font-size:13px;font-weight:600;color:#374151;margin-bottom:2px;"><?= e($event['title']) ?></div>
<?php if (!empty($event['desc'])): ?>
<div style="font-size:12px;color:#6B7280;"><?= e($event['desc']) ?></div>
<?php endif; ?>
<div style="font-size:11px;color:#9CA3AF;margin-top:3px;"><?= e(substr($event['date'], 0, 10)) ?></div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php else: ?>
<div style="padding:30px;text-align:center;color:#9CA3AF;font-size:14px;">
<i data-lucide="info" style="width:20px;height:20px;margin-bottom:8px;"></i>
<div>لا يوجد سجل تاريخي بعد</div>
</div>
<?php endif; ?>
</div>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') lucide.createIcons(); if (typeof lucide !== 'undefined') lucide.createIcons();
// Load academies data for cascading dropdowns
var academyData = null;
var enrollAcademy = document.getElementById('enrollAcademy');
var enrollLevel = document.getElementById('enrollLevel');
var enrollSchedule = document.getElementById('enrollSchedule');
var dayNames = {0:'الأحد',1:'الإثنين',2:'الثلاثاء',3:'الأربعاء',4:'الخميس',5:'الجمعة',6:'السبت'};
fetch('/api/players/academies')
.then(function(r) { return r.json(); })
.then(function(data) {
academyData = data;
enrollAcademy.innerHTML = '<option value="">-- اختر الأكاديمية --</option>';
data.academies.forEach(function(a) {
enrollAcademy.innerHTML += '<option value="' + a.id + '">' + a.discipline_name + ' — ' + a.name_ar + '</option>';
});
});
enrollAcademy.addEventListener('change', function() {
var aid = parseInt(this.value);
enrollLevel.innerHTML = '<option value="">-- اختر المستوى --</option>';
enrollSchedule.innerHTML = '<option value="">-- اختياري --</option>';
if (!aid || !academyData) return;
academyData.levels.filter(function(l) { return l.academy_id == aid; }).forEach(function(l) {
enrollLevel.innerHTML += '<option value="' + l.id + '">' + l.name_ar + '</option>';
});
academyData.schedules.filter(function(s) { return s.academy_id == aid; }).forEach(function(s) {
var label = (dayNames[s.day_of_week] || '') + ' ' + s.start_time + '-' + s.end_time + (s.facility_name ? ' (' + s.facility_name + ')' : '');
enrollSchedule.innerHTML += '<option value="' + s.id + '">' + label + '</option>';
});
});
enrollLevel.addEventListener('change', function() {
var lid = parseInt(this.value);
var aid = parseInt(enrollAcademy.value);
enrollSchedule.innerHTML = '<option value="">-- اختياري --</option>';
if (!academyData) return;
academyData.schedules.filter(function(s) {
return s.academy_id == aid && (!s.level_id || s.level_id == lid);
}).forEach(function(s) {
var label = (dayNames[s.day_of_week] || '') + ' ' + s.start_time + '-' + s.end_time + (s.facility_name ? ' (' + s.facility_name + ')' : '');
enrollSchedule.innerHTML += '<option value="' + s.id + '">' + label + '</option>';
});
});
}); });
function toggleEnrollForm() { function toggleEnrollForm() {
......
...@@ -44,8 +44,14 @@ class ReservationController extends Controller ...@@ -44,8 +44,14 @@ class ReservationController extends Controller
*/ */
public function create(Request $request): Response public function create(Request $request): Response
{ {
$db = App::getInstance()->db();
$disciplines = $db->select(
"SELECT id, name_ar FROM sport_disciplines WHERE is_active = 1 ORDER BY sort_order, name_ar"
);
return $this->view('Reservations.Views.create', [ return $this->view('Reservations.Views.create', [
'facilities' => Facility::allActive(), 'facilities' => Facility::allActive(),
'disciplines' => $disciplines,
'bookerTypes' => Reservation::getBookerTypes(), 'bookerTypes' => Reservation::getBookerTypes(),
]); ]);
} }
......
<?php <?php $__template->layout('Layout.main'); ?>
use App\Modules\Reservations\Models\Reservation;
$__template->layout('Layout.main');
?>
<?php $__template->section('title'); ?>حجز جديد<?php $__template->endSection(); ?> <?php $__template->section('title'); ?>حجز جديد<?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?> <?php $__template->section('page_actions'); ?>
...@@ -20,13 +17,25 @@ $__template->layout('Layout.main'); ...@@ -20,13 +17,25 @@ $__template->layout('Layout.main');
</div> </div>
<div style="padding:20px;display:grid;grid-template-columns:1fr 1fr;gap:15px;"> <div style="padding:20px;display:grid;grid-template-columns:1fr 1fr;gap:15px;">
<!-- Sport Discipline Filter -->
<div>
<label class="form-label">اللعبة / النشاط</label>
<select id="disciplineFilter" class="form-input">
<option value="">-- جميع الألعاب --</option>
<?php foreach ($disciplines as $d): ?>
<option value="<?= (int) $d['id'] ?>"><?= e($d['name_ar']) ?></option>
<?php endforeach; ?>
</select>
<small style="color:#9CA3AF;font-size:11px;">فلتر اختياري لتضييق المرافق المعروضة</small>
</div>
<!-- Facility --> <!-- Facility -->
<div> <div>
<label class="form-label">المرفق <span style="color:#DC2626;">*</span></label> <label class="form-label">المرفق <span style="color:#DC2626;">*</span></label>
<select name="facility_id" class="form-input" required> <select name="facility_id" id="facilitySelect" class="form-input" required>
<option value="">-- اختر المرفق --</option> <option value="">-- اختر المرفق --</option>
<?php foreach ($facilities as $f): ?> <?php foreach ($facilities as $f): ?>
<option value="<?= (int) $f['id'] ?>" <?= old('facility_id') == $f['id'] ? 'selected' : '' ?>><?= e($f['name_ar']) ?></option> <option value="<?= (int) $f['id'] ?>" data-discipline="<?= (int) ($f['linked_discipline_id'] ?? 0) ?>" <?= old('facility_id') == $f['id'] ? 'selected' : '' ?>><?= e($f['name_ar']) ?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</div> </div>
...@@ -103,6 +112,21 @@ $__template->layout('Layout.main'); ...@@ -103,6 +112,21 @@ $__template->layout('Layout.main');
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') lucide.createIcons(); if (typeof lucide !== 'undefined') lucide.createIcons();
var disciplineFilter = document.getElementById('disciplineFilter');
var facilitySelect = document.getElementById('facilitySelect');
var allOptions = Array.from(facilitySelect.querySelectorAll('option[value]'));
disciplineFilter.addEventListener('change', function() {
var discId = this.value;
facilitySelect.innerHTML = '<option value="">-- اختر المرفق --</option>';
allOptions.forEach(function(opt) {
if (!discId || opt.getAttribute('data-discipline') === discId || opt.getAttribute('data-discipline') === '0') {
facilitySelect.appendChild(opt.cloneNode(true));
}
});
});
}); });
</script> </script>
<?php $__template->endSection(); ?> <?php $__template->endSection(); ?>
...@@ -28,7 +28,11 @@ class SportsController extends Controller ...@@ -28,7 +28,11 @@ class SportsController extends Controller
$existing = SportsMember::getForMember((int) $memberId); $existing = SportsMember::getForMember((int) $memberId);
if ($existing) return $this->redirect("/members/{$memberId}")->withError('العضو لديه سجل رياضي بالفعل'); if ($existing) return $this->redirect("/members/{$memberId}")->withError('العضو لديه سجل رياضي بالفعل');
return $this->view('Sports.Views.create', ['member' => $member]); $disciplines = $db->select(
"SELECT id, name_ar FROM sport_disciplines WHERE is_active = 1 ORDER BY sort_order, name_ar"
);
return $this->view('Sports.Views.create', ['member' => $member, 'disciplines' => $disciplines]);
} }
public function store(Request $request, string $memberId): Response public function store(Request $request, string $memberId): Response
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
declare(strict_types=1); declare(strict_types=1);
return [ return [
['GET', '/sports', 'Sports\Controllers\SportsController@index', ['auth'], 'temp.view'], ['GET', '/sports', 'Sports\Controllers\SportsController@index', ['auth'], 'sports.view'],
['GET', '/members/{memberId}/sports/create', 'Sports\Controllers\SportsController@create', ['auth'], 'temp.add'], ['GET', '/members/{memberId}/sports/create', 'Sports\Controllers\SportsController@create', ['auth'], 'sports.add'],
['POST', '/members/{memberId}/sports', 'Sports\Controllers\SportsController@store', ['auth'], 'temp.add'], ['POST', '/members/{memberId}/sports', 'Sports\Controllers\SportsController@store', ['auth', 'csrf'], 'sports.add'],
['POST', '/members/{memberId}/sports/check-conversion', 'Sports\Controllers\SportsController@checkConversion', ['auth'], 'temp.view'], ['POST', '/members/{memberId}/sports/check-conversion', 'Sports\Controllers\SportsController@checkConversion', ['auth'], 'sports.convert'],
]; ];
\ No newline at end of file
...@@ -5,13 +5,47 @@ ...@@ -5,13 +5,47 @@
<?= csrf_field() ?> <?= csrf_field() ?>
<div class="card" style="padding:20px;margin-bottom:20px;"> <div class="card" style="padding:20px;margin-bottom:20px;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:15px;"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:15px;">
<div class="form-group"><label class="form-label">اسم الرياضة <span style="color:#DC2626;">*</span></label><input type="text" name="sport_name" value="<?= e(old('sport_name')) ?>" class="form-input" required></div> <div class="form-group">
<div class="form-group"><label class="form-label">اسم الاتحاد</label><input type="text" name="federation_name" value="<?= e(old('federation_name')) ?>" class="form-input"></div> <label class="form-label">النشاط الرياضي <span style="color:#DC2626;">*</span></label>
<div class="form-group"><label class="form-label">رقم تسجيل الاتحاد</label><input type="text" name="federation_registration" value="<?= e(old('federation_registration')) ?>" class="form-input"></div> <select name="sport_name" class="form-input" required>
<div class="form-group"><label class="form-label">تاريخ التسجيل</label><input type="date" name="registration_date" value="<?= e(old('registration_date')) ?>" class="form-input"></div> <option value="">-- اختر النشاط --</option>
<div class="form-group"><label class="form-label">سنوات الخدمة <span style="color:#DC2626;">*</span></label><input type="number" name="years_of_service" value="<?= e(old('years_of_service')) ?>" class="form-input" min="0" max="50" required><small style="color:#6B7280;">الحد الأدنى للتحويل لعضو عامل: 8 سنوات</small></div> <?php foreach ($disciplines as $disc): ?>
<div class="form-group"><label class="form-label">أعلى مستوى تنافسي</label><input type="text" name="highest_competitive_level" value="<?= e(old('highest_competitive_level')) ?>" class="form-input"></div> <option value="<?= e($disc['name_ar']) ?>" <?= old('sport_name') === $disc['name_ar'] ? 'selected' : '' ?>><?= e($disc['name_ar']) ?></option>
<div class="form-group" style="grid-column:1/-1;"><label class="form-label">ملاحظات</label><textarea name="notes" class="form-textarea" rows="2"><?= e(old('notes')) ?></textarea></div> <?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label class="form-label">اسم الاتحاد</label>
<input type="text" name="federation_name" value="<?= e(old('federation_name')) ?>" class="form-input" placeholder="مثال: الاتحاد المصري لكرة القدم">
</div>
<div class="form-group">
<label class="form-label">رقم تسجيل الاتحاد</label>
<input type="text" name="federation_registration" value="<?= e(old('federation_registration')) ?>" class="form-input" placeholder="رقم التسجيل في الاتحاد" style="direction:ltr;text-align:left;">
</div>
<div class="form-group">
<label class="form-label">تاريخ التسجيل</label>
<input type="date" name="registration_date" value="<?= e(old('registration_date')) ?>" class="form-input">
</div>
<div class="form-group">
<label class="form-label">سنوات الخدمة <span style="color:#DC2626;">*</span></label>
<input type="number" name="years_of_service" value="<?= e(old('years_of_service')) ?>" class="form-input" min="0" max="50" required style="direction:ltr;text-align:left;">
<small style="color:#6B7280;">الحد الأدنى للتحويل لعضو عامل: 8 سنوات</small>
</div>
<div class="form-group">
<label class="form-label">أعلى مستوى تنافسي</label>
<select name="highest_competitive_level" class="form-input">
<option value="">-- اختر --</option>
<option value="محلي" <?= old('highest_competitive_level') === 'محلي' ? 'selected' : '' ?>>محلي</option>
<option value="إقليمي" <?= old('highest_competitive_level') === 'إقليمي' ? 'selected' : '' ?>>إقليمي</option>
<option value="قومي" <?= old('highest_competitive_level') === 'قومي' ? 'selected' : '' ?>>قومي (منتخب)</option>
<option value="دولي" <?= old('highest_competitive_level') === 'دولي' ? 'selected' : '' ?>>دولي</option>
<option value="أولمبي" <?= old('highest_competitive_level') === 'أولمبي' ? 'selected' : '' ?>>أولمبي</option>
</select>
</div>
<div class="form-group" style="grid-column:1/-1;">
<label class="form-label">ملاحظات</label>
<textarea name="notes" class="form-textarea" rows="2"><?= e(old('notes')) ?></textarea>
</div>
</div> </div>
</div> </div>
<button type="submit" class="btn btn-primary">تسجيل العضوية الرياضية</button> <button type="submit" class="btn btn-primary">تسجيل العضوية الرياضية</button>
......
<?php <?php
declare(strict_types=1); declare(strict_types=1);
// Menu registered centrally via Members/bootstrap.php under "membership" parent. use App\Core\Registries\PermissionRegistry;
\ No newline at end of file
PermissionRegistry::register('sports', [
'sports.view' => ['ar' => 'عرض العضوية الرياضية', 'en' => 'View Sports Membership'],
'sports.add' => ['ar' => 'تسجيل عضوية رياضية', 'en' => 'Register Sports Membership'],
'sports.convert' => ['ar' => 'تحويل عضو رياضي', 'en' => 'Convert Sports Member'],
]);
\ No newline at end of file
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