Commit 140ee3da authored by Fares's avatar Fares

feat(sa): redesign player assignment with trainer flow and add game pricing type

Redesign the player assignment page with a 3-step guided workflow
(Sport → Program → Trainer) supporting both trainer-based and
program-based assignments. Add multi-coach support for groups via
sa_group_coaches pivot table. Add per-hour/per-game pricing type
to recreational games.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 46f03bbf
...@@ -47,6 +47,10 @@ class GameController extends Controller ...@@ -47,6 +47,10 @@ class GameController extends Controller
return $this->redirect('/sa/games/create')->withError('اسم اللعبة مطلوب'); return $this->redirect('/sa/games/create')->withError('اسم اللعبة مطلوب');
} }
$pricingType = in_array($request->post('pricing_type', ''), ['per_game', 'per_hour'], true)
? $request->post('pricing_type')
: 'per_game';
$db->insert('sa_games', [ $db->insert('sa_games', [
'name_ar' => $name, 'name_ar' => $name,
'name_en' => trim((string) $request->post('name_en', '')) ?: null, 'name_en' => trim((string) $request->post('name_en', '')) ?: null,
...@@ -57,6 +61,7 @@ class GameController extends Controller ...@@ -57,6 +61,7 @@ class GameController extends Controller
'duration_minutes' => max(15, (int) $request->post('duration_minutes', 60)), 'duration_minutes' => max(15, (int) $request->post('duration_minutes', 60)),
'fee_member' => max(0, (float) $request->post('fee_member', 0)), 'fee_member' => max(0, (float) $request->post('fee_member', 0)),
'fee_nonmember' => max(0, (float) $request->post('fee_nonmember', 0)), 'fee_nonmember' => max(0, (float) $request->post('fee_nonmember', 0)),
'pricing_type' => $pricingType,
'is_active' => 1, 'is_active' => 1,
'created_by' => (int) (App::getInstance()->session()->get('employee_id') ?? 0), 'created_by' => (int) (App::getInstance()->session()->get('employee_id') ?? 0),
'created_at' => date('Y-m-d H:i:s'), 'created_at' => date('Y-m-d H:i:s'),
...@@ -184,11 +189,19 @@ class GameController extends Controller ...@@ -184,11 +189,19 @@ class GameController extends Controller
return $this->redirect("/sa/games/{$id}/sessions/{$sessionId}")->withError('يجب تحديد اللاعب أو إدخال اسم'); return $this->redirect("/sa/games/{$id}/sessions/{$sessionId}")->withError('يجب تحديد اللاعب أو إدخال اسم');
} }
$fee = match ($playerType) { $baseFee = match ($playerType) {
'member' => (float) ($game['fee_member'] ?? 0), 'member' => (float) ($game['fee_member'] ?? 0),
default => (float) ($game['fee_nonmember'] ?? 0), default => (float) ($game['fee_nonmember'] ?? 0),
}; };
$fee = $baseFee;
if (($game['pricing_type'] ?? 'per_game') === 'per_hour') {
$startDt = new \DateTime($session['session_date'] . ' ' . $session['start_time']);
$endDt = new \DateTime($session['session_date'] . ' ' . $session['end_time']);
$hours = max(1, (int) ceil(($endDt->getTimestamp() - $startDt->getTimestamp()) / 3600));
$fee = $baseFee * $hours;
}
$ticketNumber = 'GT-' . strtoupper(substr(uniqid(), -8)); $ticketNumber = 'GT-' . strtoupper(substr(uniqid(), -8));
$db->beginTransaction(); $db->beginTransaction();
......
...@@ -172,6 +172,9 @@ class GroupController extends Controller ...@@ -172,6 +172,9 @@ class GroupController extends Controller
'status' => 'active', 'status' => 'active',
]); ]);
// Save coaches to pivot table
$this->syncGroupCoaches($db, (int) $group->id, $coachId, $request->post('additional_coaches', []));
return $this->redirect('/sa/groups/' . $group->id)->withSuccess('تم إضافة المجموعة بنجاح'); return $this->redirect('/sa/groups/' . $group->id)->withSuccess('تم إضافة المجموعة بنجاح');
} }
...@@ -207,12 +210,19 @@ class GroupController extends Controller ...@@ -207,12 +210,19 @@ class GroupController extends Controller
[] []
); );
// Additional coaches from pivot
$additionalCoaches = $db->select(
"SELECT c.id, c.full_name_ar FROM sa_group_coaches gc JOIN sa_coaches c ON c.id = gc.coach_id WHERE gc.group_id = ? AND gc.role != 'primary' ORDER BY c.full_name_ar",
[(int) $id]
);
return $this->view('SportsActivity.Views.groups.show', [ return $this->view('SportsActivity.Views.groups.show', [
'group' => $group, 'group' => $group,
'players' => $players, 'players' => $players,
'schedule' => $schedule, 'schedule' => $schedule,
'facilityUnits' => $facilityUnits, 'facilityUnits' => $facilityUnits,
'statuses' => Group::getStatusOptions(), 'statuses' => Group::getStatusOptions(),
'additionalCoaches' => $additionalCoaches,
]); ]);
} }
...@@ -236,12 +246,18 @@ class GroupController extends Controller ...@@ -236,12 +246,18 @@ class GroupController extends Controller
[(int) $group->program_id] [(int) $group->program_id]
); );
$groupCoachIds = array_column(
$db->select("SELECT coach_id FROM sa_group_coaches WHERE group_id = ? AND role != 'primary'", [(int) $id]),
'coach_id'
);
return $this->view('SportsActivity.Views.groups.edit', [ return $this->view('SportsActivity.Views.groups.edit', [
'group' => $group, 'group' => $group,
'disciplines' => $disciplines, 'disciplines' => $disciplines,
'programs' => $programs, 'programs' => $programs,
'coaches' => $coaches, 'coaches' => $coaches,
'disciplineId' => $disciplineId ? (int) $disciplineId['discipline_id'] : null, 'disciplineId' => $disciplineId ? (int) $disciplineId['discipline_id'] : null,
'groupCoachIds' => $groupCoachIds,
]); ]);
} }
...@@ -312,6 +328,9 @@ class GroupController extends Controller ...@@ -312,6 +328,9 @@ class GroupController extends Controller
'season_end' => $seasonEnd ?: null, 'season_end' => $seasonEnd ?: null,
]); ]);
// Sync coaches pivot table
$this->syncGroupCoaches($db, (int) $id, $coachId, $request->post('additional_coaches', []));
return $this->redirect('/sa/groups/' . $id)->withSuccess('تم تحديث المجموعة بنجاح'); return $this->redirect('/sa/groups/' . $id)->withSuccess('تم تحديث المجموعة بنجاح');
} }
...@@ -496,6 +515,33 @@ class GroupController extends Controller ...@@ -496,6 +515,33 @@ class GroupController extends Controller
return $this->redirect('/sa/groups/' . $id)->withError($result['error']); return $this->redirect('/sa/groups/' . $id)->withError($result['error']);
} }
private function syncGroupCoaches(\App\Core\Database $db, int $groupId, int $primaryCoachId, mixed $additionalCoachIds): void
{
if (!is_array($additionalCoachIds)) {
$additionalCoachIds = [];
}
$additionalCoachIds = array_map('intval', $additionalCoachIds);
$additionalCoachIds = array_filter($additionalCoachIds, fn(int $id) => $id > 0 && $id !== $primaryCoachId);
$db->delete('sa_group_coaches', 'group_id = ?', [$groupId]);
$db->insert('sa_group_coaches', [
'group_id' => $groupId,
'coach_id' => $primaryCoachId,
'role' => 'primary',
'created_at' => date('Y-m-d H:i:s'),
]);
foreach ($additionalCoachIds as $coachId) {
$db->insert('sa_group_coaches', [
'group_id' => $groupId,
'coach_id' => $coachId,
'role' => 'assistant',
'created_at' => date('Y-m-d H:i:s'),
]);
}
}
public function generateSessions(Request $request, string $id): Response public function generateSessions(Request $request, string $id): Response
{ {
$group = Group::find((int) $id); $group = Group::find((int) $id);
......
...@@ -16,6 +16,7 @@ class PlayerAssignmentController extends Controller ...@@ -16,6 +16,7 @@ class PlayerAssignmentController extends Controller
$db = App::getInstance()->db(); $db = App::getInstance()->db();
$disciplineId = trim((string) $request->get('discipline_id', '')); $disciplineId = trim((string) $request->get('discipline_id', ''));
$programSearch = trim((string) $request->get('program', ''));
$status = trim((string) $request->get('status', 'unassigned')); $status = trim((string) $request->get('status', 'unassigned'));
$disciplines = $db->select( $disciplines = $db->select(
...@@ -42,6 +43,15 @@ class PlayerAssignmentController extends Controller ...@@ -42,6 +43,15 @@ class PlayerAssignmentController extends Controller
} }
} }
if ($programSearch !== '') {
$where .= " AND EXISTS (
SELECT 1 FROM sa_programs prg
JOIN sa_disciplines disc ON disc.id = prg.discipline_id
WHERE prg.name_ar LIKE ? AND prg.is_archived = 0
)";
$params[] = "%{$programSearch}%";
}
$players = $db->select(" $players = $db->select("
SELECT pl.id, pl.full_name_ar, pl.date_of_birth, pl.gender, pl.phone, SELECT pl.id, pl.full_name_ar, pl.date_of_birth, pl.gender, pl.phone,
pl.medical_status, pl.medical_expiry_date, pl.photo_path, pl.player_type, pl.medical_status, pl.medical_expiry_date, pl.photo_path, pl.player_type,
...@@ -60,7 +70,7 @@ class PlayerAssignmentController extends Controller ...@@ -60,7 +70,7 @@ class PlayerAssignmentController extends Controller
return $this->view('SportsActivity.Views.assignments.index', [ return $this->view('SportsActivity.Views.assignments.index', [
'players' => $players, 'players' => $players,
'disciplines' => $disciplines, 'disciplines' => $disciplines,
'filters' => ['discipline_id' => $disciplineId, 'status' => $status], 'filters' => ['discipline_id' => $disciplineId, 'status' => $status, 'program' => $programSearch],
]); ]);
} }
...@@ -74,28 +84,69 @@ class PlayerAssignmentController extends Controller ...@@ -74,28 +84,69 @@ class PlayerAssignmentController extends Controller
} }
$disciplineId = trim((string) $request->get('discipline_id', '')); $disciplineId = trim((string) $request->get('discipline_id', ''));
$programId = trim((string) $request->get('program_id', ''));
$programSearch = trim((string) $request->get('program_search', ''));
$groupWhere = "g.status = 'active' AND g.is_archived = 0"; // All disciplines
$groupParams = []; $disciplines = $db->select(
"SELECT id, name_ar, code FROM sa_disciplines WHERE is_archived = 0 ORDER BY name_ar"
);
// Programs filtered by discipline + search
$programWhere = "p.is_archived = 0";
$programParams = [];
if ($disciplineId !== '') { if ($disciplineId !== '') {
$groupWhere .= " AND p.discipline_id = ?"; $programWhere .= " AND p.discipline_id = ?";
$groupParams[] = (int) $disciplineId; $programParams[] = (int) $disciplineId;
}
if ($programSearch !== '') {
$programWhere .= " AND p.name_ar LIKE ?";
$programParams[] = "%{$programSearch}%";
} }
$groups = $db->select(" $programs = $db->select("
SELECT g.id, g.name_ar, g.current_count, g.max_capacity, g.min_age, g.max_age, SELECT p.id, p.name_ar, p.discipline_id, p.monthly_fee_member, p.monthly_fee_nonmember,
p.name_ar as program_name, p.monthly_fee_member, p.monthly_fee_nonmember, p.max_capacity, p.age_from, p.age_to,
d.name_ar as discipline_name, d.name_ar as discipline_name,
c.full_name_ar as coach_name (SELECT COUNT(*) FROM sa_groups g WHERE g.program_id = p.id AND g.status = 'active' AND g.is_archived = 0 AND g.is_full = 0) as available_groups
FROM sa_groups g FROM sa_programs p
JOIN sa_programs p ON p.id = g.program_id
JOIN sa_disciplines d ON d.id = p.discipline_id JOIN sa_disciplines d ON d.id = p.discipline_id
LEFT JOIN sa_coaches c ON c.id = g.coach_id WHERE {$programWhere}
WHERE {$groupWhere} ORDER BY d.name_ar, p.name_ar
ORDER BY d.name_ar, p.name_ar, g.name_ar ", $programParams);
", $groupParams);
// Coaches for selected program (or all if no program selected)
$coaches = [];
if ($programId !== '') {
$coaches = $db->select("
SELECT DISTINCT c.id, c.full_name_ar, c.phone, c.photo_path,
(SELECT COUNT(*) FROM sa_groups g2
WHERE g2.program_id = ? AND g2.status = 'active' AND g2.is_archived = 0 AND g2.is_full = 0
AND (g2.coach_id = c.id OR EXISTS (SELECT 1 FROM sa_group_coaches gc WHERE gc.group_id = g2.id AND gc.coach_id = c.id))
) as available_groups,
(SELECT SUM(g3.current_count) FROM sa_groups g3
WHERE g3.program_id = ? AND g3.status = 'active' AND g3.is_archived = 0
AND (g3.coach_id = c.id OR EXISTS (SELECT 1 FROM sa_group_coaches gc2 WHERE gc2.group_id = g3.id AND gc2.coach_id = c.id))
) as total_players
FROM sa_coaches c
WHERE c.is_archived = 0 AND c.is_active = 1
AND (
EXISTS (SELECT 1 FROM sa_coach_disciplines cd WHERE cd.coach_id = c.id AND cd.discipline_id = (SELECT discipline_id FROM sa_programs WHERE id = ?))
OR EXISTS (SELECT 1 FROM sa_groups g4 WHERE g4.program_id = ? AND (g4.coach_id = c.id OR EXISTS (SELECT 1 FROM sa_group_coaches gc3 WHERE gc3.group_id = g4.id AND gc3.coach_id = c.id)))
)
ORDER BY c.full_name_ar
", [(int) $programId, (int) $programId, (int) $programId, (int) $programId]);
} elseif ($disciplineId !== '') {
$coaches = $db->select("
SELECT c.id, c.full_name_ar, c.phone, c.photo_path
FROM sa_coaches c
WHERE c.is_archived = 0 AND c.is_active = 1
AND EXISTS (SELECT 1 FROM sa_coach_disciplines cd WHERE cd.coach_id = c.id AND cd.discipline_id = ?)
ORDER BY c.full_name_ar
", [(int) $disciplineId]);
}
// Current enrollments
$currentEnrollments = $db->select(" $currentEnrollments = $db->select("
SELECT gp.*, g.name_ar as group_name, p.name_ar as program_name SELECT gp.*, g.name_ar as group_name, p.name_ar as program_name
FROM sa_group_players gp FROM sa_group_players gp
...@@ -105,11 +156,29 @@ class PlayerAssignmentController extends Controller ...@@ -105,11 +156,29 @@ class PlayerAssignmentController extends Controller
ORDER BY gp.enrolled_at DESC ORDER BY gp.enrolled_at DESC
", [(int) $playerId]); ", [(int) $playerId]);
// Best available group for selected program (least crowded)
$bestGroupId = null;
if ($programId !== '') {
$bestGroup = $db->selectOne("
SELECT id FROM sa_groups
WHERE program_id = ? AND status = 'active' AND is_archived = 0 AND is_full = 0
ORDER BY current_count ASC LIMIT 1
", [(int) $programId]);
$bestGroupId = $bestGroup ? (int) $bestGroup['id'] : null;
}
return $this->view('SportsActivity.Views.assignments.show', [ return $this->view('SportsActivity.Views.assignments.show', [
'player' => $player, 'player' => $player,
'groups' => $groups, 'disciplines' => $disciplines,
'programs' => $programs,
'coaches' => $coaches,
'currentEnrollments' => $currentEnrollments, 'currentEnrollments' => $currentEnrollments,
'filters' => ['discipline_id' => $disciplineId], 'bestGroupId' => $bestGroupId,
'filters' => [
'discipline_id' => $disciplineId,
'program_id' => $programId,
'program_search' => $programSearch,
],
]); ]);
} }
...@@ -142,4 +211,33 @@ class PlayerAssignmentController extends Controller ...@@ -142,4 +211,33 @@ class PlayerAssignmentController extends Controller
return $this->redirect('/sa/assignments/' . $playerId)->withSuccess($msg); return $this->redirect('/sa/assignments/' . $playerId)->withSuccess($msg);
} }
public function assignByCoach(Request $request, string $playerId): Response
{
$programId = (int) $request->post('program_id', 0);
$coachId = (int) $request->post('coach_id', 0);
if (!$programId) {
return $this->redirect('/sa/assignments/' . $playerId)->withError('يجب اختيار البرنامج');
}
if (!$coachId) {
return $this->redirect('/sa/assignments/' . $playerId)->withError('يجب اختيار المدرب');
}
$result = EnrollmentService::enrollByCoach($programId, $coachId, (int) $playerId);
if (!$result['success']) {
return $this->redirect('/sa/assignments/' . $playerId . '?program_id=' . $programId)->withError($result['error']);
}
$msg = 'تم تسجيل اللاعب بنجاح مع المدرب المحدد';
if (!empty($result['medical_warning'])) {
$msg .= ' — ' . $result['medical_warning'];
}
if (!empty($result['request_number'])) {
$msg .= ' — رقم الطلب: ' . $result['request_number'];
}
return $this->redirect('/sa/assignments/' . $playerId)->withSuccess($msg);
}
} }
...@@ -66,6 +66,7 @@ return [ ...@@ -66,6 +66,7 @@ return [
['GET', '/sa/assignments', 'SportsActivity\Controllers\PlayerAssignmentController@index', ['auth'], 'sa.player.assign'], ['GET', '/sa/assignments', 'SportsActivity\Controllers\PlayerAssignmentController@index', ['auth'], 'sa.player.assign'],
['GET', '/sa/assignments/{id:\d+}', 'SportsActivity\Controllers\PlayerAssignmentController@show', ['auth'], 'sa.player.assign'], ['GET', '/sa/assignments/{id:\d+}', 'SportsActivity\Controllers\PlayerAssignmentController@show', ['auth'], 'sa.player.assign'],
['POST', '/sa/assignments/{id:\d+}/assign', 'SportsActivity\Controllers\PlayerAssignmentController@assign', ['auth', 'csrf'], 'sa.player.assign'], ['POST', '/sa/assignments/{id:\d+}/assign', 'SportsActivity\Controllers\PlayerAssignmentController@assign', ['auth', 'csrf'], 'sa.player.assign'],
['POST', '/sa/assignments/{id:\d+}/assign-coach', 'SportsActivity\Controllers\PlayerAssignmentController@assignByCoach', ['auth', 'csrf'], 'sa.player.assign'],
// Academies // Academies
['GET', '/sa/academies', 'SportsActivity\Controllers\AcademyController@index', ['auth'], 'sa.academy.view'], ['GET', '/sa/academies', 'SportsActivity\Controllers\AcademyController@index', ['auth'], 'sa.academy.view'],
......
...@@ -426,6 +426,107 @@ final class EnrollmentService ...@@ -426,6 +426,107 @@ final class EnrollmentService
return $name . ' 2'; return $name . ' 2';
} }
public static function enrollByCoach(int $programId, int $coachId, int $playerId): array
{
$db = App::getInstance()->db();
$program = $db->selectOne(
"SELECT * FROM sa_programs WHERE id = ? AND is_archived = 0",
[$programId]
);
if (!$program) {
return ['success' => false, 'error' => 'البرنامج غير موجود'];
}
// Find a group under this program with this coach that has capacity
$group = $db->selectOne("
SELECT g.* FROM sa_groups g
WHERE g.program_id = ? AND g.status = 'active' AND g.is_archived = 0 AND g.is_full = 0
AND (
g.coach_id = ?
OR EXISTS (SELECT 1 FROM sa_group_coaches gc WHERE gc.group_id = g.id AND gc.coach_id = ?)
)
ORDER BY g.current_count ASC
LIMIT 1
", [$programId, $coachId, $coachId]);
if ($group) {
return self::enroll((int) $group['id'], $playerId);
}
// No available group — auto-create one
$sourceGroup = $db->selectOne(
"SELECT * FROM sa_groups WHERE program_id = ? AND is_archived = 0 ORDER BY id DESC LIMIT 1",
[$programId]
);
if (!$sourceGroup) {
return ['success' => false, 'error' => 'لا توجد مجموعات مرجعية لإنشاء مجموعة جديدة'];
}
$coach = $db->selectOne("SELECT full_name_ar FROM sa_coaches WHERE id = ?", [$coachId]);
$coachName = $coach['full_name_ar'] ?? 'مدرب';
$newName = $program['name_ar'] . ' - ' . $coachName;
$employeeId = (int) (App::getInstance()->session()->get('employee_id') ?? 0);
$db->beginTransaction();
try {
$newGroupId = $db->insert('sa_groups', [
'name_ar' => $newName,
'program_id' => $programId,
'facility_unit_id' => $sourceGroup['facility_unit_id'],
'coach_id' => $coachId,
'monthly_fee_member' => 0,
'monthly_fee_nonmember' => 0,
'min_age' => $sourceGroup['min_age'],
'max_age' => $sourceGroup['max_age'],
'max_capacity' => (int) ($program['max_capacity'] ?? 20),
'current_count' => 0,
'is_full' => 0,
'status' => 'active',
'branch_id' => $sourceGroup['branch_id'] ?? null,
'created_by' => $employeeId,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
// Add coach to pivot table
$db->insert('sa_group_coaches', [
'group_id' => $newGroupId,
'coach_id' => $coachId,
'role' => 'primary',
'created_at' => date('Y-m-d H:i:s'),
]);
// Copy schedule from source
$schedules = $db->select(
"SELECT * FROM sa_group_schedule WHERE group_id = ? AND is_active = 1",
[(int) $sourceGroup['id']]
);
foreach ($schedules as $sch) {
$db->insert('sa_group_schedule', [
'group_id' => $newGroupId,
'facility_unit_id' => $sch['facility_unit_id'],
'day_of_week' => $sch['day_of_week'],
'start_time' => $sch['start_time'],
'end_time' => $sch['end_time'],
'is_active' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
$db->commit();
} catch (\Throwable $e) {
$db->rollBack();
return ['success' => false, 'error' => 'فشل إنشاء المجموعة: ' . $e->getMessage()];
}
// Now enroll the player in the newly created group
return self::enroll($newGroupId, $playerId);
}
public static function deactivateEnrollment(int $enrollmentId): bool public static function deactivateEnrollment(int $enrollmentId): bool
{ {
$db = App::getInstance()->db(); $db = App::getInstance()->db();
......
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
<!-- Filters --> <!-- Filters -->
<div class="card" style="padding:15px;margin-bottom:20px;"> <div class="card" style="padding:15px;margin-bottom:20px;">
<form method="GET" action="/sa/assignments" style="display:flex;gap:12px;align-items:end;flex-wrap:wrap;"> <form method="GET" action="/sa/assignments" style="display:flex;gap:12px;align-items:end;flex-wrap:wrap;">
<div class="form-group" style="margin:0;min-width:180px;"> <div class="form-group" style="margin:0;min-width:160px;">
<label class="form-label" style="font-size:12px;">النشاط</label> <label class="form-label" style="font-size:12px;">النشاط الرياضي</label>
<select name="discipline_id" class="form-input" style="font-size:13px;"> <select name="discipline_id" class="form-input" style="font-size:13px;">
<option value="">الكل</option> <option value="">الكل</option>
<?php foreach ($disciplines as $d): ?> <?php foreach ($disciplines as $d): ?>
...@@ -15,7 +15,11 @@ ...@@ -15,7 +15,11 @@
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</div> </div>
<div class="form-group" style="margin:0;min-width:150px;"> <div class="form-group" style="margin:0;min-width:180px;">
<label class="form-label" style="font-size:12px;">بحث برنامج</label>
<input type="text" name="program" class="form-input" value="<?= e($filters['program'] ?? '') ?>" placeholder="اسم البرنامج..." style="font-size:13px;">
</div>
<div class="form-group" style="margin:0;min-width:130px;">
<label class="form-label" style="font-size:12px;">الحالة</label> <label class="form-label" style="font-size:12px;">الحالة</label>
<select name="status" class="form-input" style="font-size:13px;"> <select name="status" class="form-input" style="font-size:13px;">
<option value="unassigned" <?= ($filters['status'] ?? '') === 'unassigned' ? 'selected' : '' ?>>غير معينين</option> <option value="unassigned" <?= ($filters['status'] ?? '') === 'unassigned' ? 'selected' : '' ?>>غير معينين</option>
......
...@@ -7,6 +7,17 @@ ...@@ -7,6 +7,17 @@
<?php $__template->section('content'); ?> <?php $__template->section('content'); ?>
<?php
$isMember = ($player['player_type'] ?? '') === 'member';
$playerAge = null;
if (!empty($player['date_of_birth'])) {
$playerAge = (int) date_diff(date_create($player['date_of_birth']), date_create())->y;
}
$selectedDiscipline = $filters['discipline_id'] ?? '';
$selectedProgram = $filters['program_id'] ?? '';
$programSearch = $filters['program_search'] ?? '';
?>
<!-- Player Info --> <!-- Player Info -->
<div class="card" style="padding:20px;margin-bottom:20px;"> <div class="card" style="padding:20px;margin-bottom:20px;">
<div style="display:flex;gap:20px;align-items:center;"> <div style="display:flex;gap:20px;align-items:center;">
...@@ -20,9 +31,9 @@ ...@@ -20,9 +31,9 @@
<div> <div>
<div style="font-size:16px;font-weight:700;"><?= e($player['full_name_ar']) ?></div> <div style="font-size:16px;font-weight:700;"><?= e($player['full_name_ar']) ?></div>
<div style="display:flex;gap:12px;margin-top:4px;font-size:12px;color:#6B7280;"> <div style="display:flex;gap:12px;margin-top:4px;font-size:12px;color:#6B7280;">
<span><?= $player['player_type'] === 'member' ? 'عضو' : 'غير عضو' ?></span> <span><?= $isMember ? 'عضو' : 'غير عضو' ?></span>
<?php if ($player['date_of_birth']): ?> <?php if ($playerAge !== null): ?>
<span>العمر: <?= (int) date_diff(date_create($player['date_of_birth']), date_create())->y ?> سنة</span> <span>العمر: <?= $playerAge ?> سنة</span>
<?php endif; ?> <?php endif; ?>
<?php if ($player['phone']): ?> <?php if ($player['phone']): ?>
<span><?= e($player['phone']) ?></span> <span><?= e($player['phone']) ?></span>
...@@ -57,93 +68,153 @@ ...@@ -57,93 +68,153 @@
</div> </div>
<?php endif; ?> <?php endif; ?>
<!-- Available Groups --> <!-- Step 1: Choose Sport -->
<div class="card"> <div class="card" style="margin-bottom:20px;">
<div style="padding:12px 20px;border-bottom:1px solid #E5E7EB;"> <div style="padding:12px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:14px;color:#374151;">المجموعات المتاحة</h3> <h3 style="margin:0;font-size:14px;color:#0D7377;">
<span style="display:inline-block;width:22px;height:22px;background:#0D7377;color:#fff;border-radius:50%;text-align:center;line-height:22px;font-size:12px;margin-left:8px;">1</span>
اختر النشاط الرياضي
</h3>
</div> </div>
<div class="table-responsive"> <div style="padding:15px 20px;">
<table class="data-table"> <div style="display:flex;flex-wrap:wrap;gap:8px;">
<thead> <a href="/sa/assignments/<?= (int) $player['id'] ?>"
<tr> class="btn <?= $selectedDiscipline === '' ? 'btn-primary' : 'btn-outline' ?>" style="font-size:13px;padding:6px 14px;">
<th>المجموعة</th> الكل
<th>البرنامج</th> </a>
<th>النشاط</th> <?php foreach ($disciplines as $d): ?>
<th>المدرب</th> <a href="/sa/assignments/<?= (int) $player['id'] ?>?discipline_id=<?= (int) $d['id'] ?>"
<th>السعة</th> class="btn <?= $selectedDiscipline == $d['id'] ? 'btn-primary' : 'btn-outline' ?>" style="font-size:13px;padding:6px 14px;">
<th>الرسوم</th> <?= e($d['name_ar']) ?>
<th style="width:120px;">إجراء</th> </a>
</tr> <?php endforeach; ?>
</thead> </div>
<tbody> </div>
<?php if (empty($groups)): ?> </div>
<tr><td colspan="7" style="text-align:center;padding:30px;color:#6B7280;">لا توجد مجموعات متاحة</td></tr>
<?php else: ?>
<?php
$isMember = ($player['player_type'] ?? '') === 'member';
$playerAge = null;
if (!empty($player['date_of_birth'])) {
$playerAge = (int) date_diff(date_create($player['date_of_birth']), date_create())->y;
}
?>
<?php foreach ($groups as $g):
$current = (int) $g['current_count'];
$max = (int) $g['max_capacity'];
$isFull = $current >= $max;
$pct = $max > 0 ? round(($current / $max) * 100) : 0;
$barColor = $pct >= 90 ? '#DC2626' : ($pct >= 70 ? '#D97706' : '#059669');
$fee = $isMember ? (float) $g['monthly_fee_member'] : (float) $g['monthly_fee_nonmember'];
$ageOk = true; <!-- Step 2: Choose Program -->
if ($playerAge !== null) { <div class="card" style="margin-bottom:20px;">
if (!empty($g['min_age']) && $playerAge < (int) $g['min_age']) $ageOk = false; <div style="padding:12px 20px;border-bottom:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center;">
if (!empty($g['max_age']) && $playerAge > (int) $g['max_age']) $ageOk = false; <h3 style="margin:0;font-size:14px;color:#0D7377;">
} <span style="display:inline-block;width:22px;height:22px;background:#0D7377;color:#fff;border-radius:50%;text-align:center;line-height:22px;font-size:12px;margin-left:8px;">2</span>
?> اختر البرنامج
<tr style="<?= !$ageOk ? 'opacity:0.5;' : '' ?>"> </h3>
<td style="font-weight:600;font-size:13px;"><?= e($g['name_ar']) ?></td> <form method="GET" action="/sa/assignments/<?= (int) $player['id'] ?>" style="display:flex;gap:8px;">
<td style="font-size:12px;"><?= e($g['program_name']) ?></td> <input type="hidden" name="discipline_id" value="<?= e($selectedDiscipline) ?>">
<td style="font-size:12px;"><?= e($g['discipline_name']) ?></td> <input type="text" name="program_search" class="form-input" value="<?= e($programSearch) ?>" placeholder="بحث عن برنامج..." style="font-size:12px;padding:5px 10px;width:180px;">
<td style="font-size:12px;"><?= e($g['coach_name'] ?? '—') ?></td> <button type="submit" class="btn btn-sm btn-outline" style="font-size:12px;padding:5px 10px;">بحث</button>
<td> </form>
<div style="display:flex;align-items:center;gap:6px;"> </div>
<div style="flex:1;height:6px;background:#E5E7EB;border-radius:3px;min-width:50px;"> <div style="padding:15px 20px;">
<div style="height:100%;width:<?= $pct ?>%;background:<?= $barColor ?>;border-radius:3px;"></div> <?php if (empty($programs)): ?>
</div> <p style="color:#6B7280;text-align:center;padding:15px;font-size:13px;">
<span style="font-size:11px;color:#6B7280;"><?= $current ?>/<?= $max ?></span> <?= $selectedDiscipline ? 'لا توجد برامج لهذا النشاط' : 'اختر نشاط رياضي أولاً أو اعرض الكل' ?>
</p>
<?php else: ?>
<div style="display:grid;grid-template-columns:repeat(auto-fill, minmax(280px, 1fr));gap:12px;">
<?php foreach ($programs as $prog):
$isSelected = ($selectedProgram == $prog['id']);
$ageNote = '';
if (!empty($prog['age_from']) || !empty($prog['age_to'])) {
$ageNote = ($prog['age_from'] ?? '?') . '-' . ($prog['age_to'] ?? '?') . ' سنة';
}
$fee = $isMember ? (float) $prog['monthly_fee_member'] : (float) $prog['monthly_fee_nonmember'];
?>
<a href="/sa/assignments/<?= (int) $player['id'] ?>?discipline_id=<?= e($selectedDiscipline) ?>&program_id=<?= (int) $prog['id'] ?>"
style="display:block;padding:14px;border:2px solid <?= $isSelected ? '#0D7377' : '#E5E7EB' ?>;border-radius:8px;text-decoration:none;color:inherit;background:<?= $isSelected ? '#F0FDFA' : '#fff' ?>;transition:border-color 0.2s;">
<div style="display:flex;justify-content:space-between;align-items:start;">
<div>
<div style="font-weight:700;font-size:14px;color:#1A1A2E;margin-bottom:4px;"><?= e($prog['name_ar']) ?></div>
<div style="font-size:11px;color:#6B7280;">
<?= e($prog['discipline_name']) ?>
<?php if ($ageNote): ?><?= $ageNote ?><?php endif; ?>
</div> </div>
</td> </div>
<td style="font-weight:700;color:#0D7377;font-size:13px;"><?= money($fee) ?></td> <div style="text-align:left;">
<td> <div style="font-size:13px;font-weight:700;color:#0D7377;"><?= money($fee) ?></div>
<?php if (!$ageOk): ?> <?php if ((int) $prog['available_groups'] > 0): ?>
<span style="font-size:11px;color:#DC2626;">عمر غير مناسب</span> <div style="font-size:10px;color:#059669;margin-top:2px;"><?= (int) $prog['available_groups'] ?> مجموعة متاحة</div>
<?php elseif ($isFull): ?>
<form method="POST" action="/sa/assignments/<?= (int) $player['id'] ?>/assign" style="display:inline;">
<?= csrf_field() ?>
<input type="hidden" name="group_id" value="<?= (int) $g['id'] ?>">
<input type="hidden" name="force" value="1">
<button type="submit" class="btn btn-sm" style="font-size:11px;padding:4px 10px;background:#FEF3C7;color:#D97706;border:1px solid #FCD34D;border-radius:4px;" onclick="return confirm('المجموعة ممتلئة. تعيين بالقوة؟')">
تعيين (ممتلئة)
</button>
</form>
<?php else: ?> <?php else: ?>
<form method="POST" action="/sa/assignments/<?= (int) $player['id'] ?>/assign" style="display:inline;"> <div style="font-size:10px;color:#D97706;margin-top:2px;">سيتم إنشاء مجموعة</div>
<?= csrf_field() ?>
<input type="hidden" name="group_id" value="<?= (int) $g['id'] ?>">
<button type="submit" class="btn btn-sm btn-primary" style="font-size:11px;padding:4px 10px;">
<i data-lucide="plus" style="width:12px;height:12px;vertical-align:middle;margin-left:3px;"></i> تعيين
</button>
</form>
<?php endif; ?> <?php endif; ?>
</td> </div>
</tr> </div>
<?php endforeach; ?> </a>
<?php endif; ?> <?php endforeach; ?>
</tbody> </div>
</table> <?php endif; ?>
</div> </div>
</div> </div>
<!-- Step 3: Choose Trainer -->
<?php if ($selectedProgram !== ''): ?>
<div class="card" style="margin-bottom:20px;">
<div style="padding:12px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:14px;color:#0D7377;">
<span style="display:inline-block;width:22px;height:22px;background:#0D7377;color:#fff;border-radius:50%;text-align:center;line-height:22px;font-size:12px;margin-left:8px;">3</span>
اختر المدرب
</h3>
</div>
<div style="padding:15px 20px;">
<?php if (empty($coaches)): ?>
<p style="color:#6B7280;text-align:center;padding:15px;font-size:13px;">لا يوجد مدربين مسجلين لهذا البرنامج</p>
<?php else: ?>
<div style="display:grid;grid-template-columns:repeat(auto-fill, minmax(250px, 1fr));gap:12px;">
<?php foreach ($coaches as $coach): ?>
<div style="padding:14px;border:1px solid #E5E7EB;border-radius:8px;display:flex;align-items:center;gap:12px;">
<div style="width:44px;height:44px;border-radius:50%;background:#F3F4F6;display:flex;align-items:center;justify-content:center;overflow:hidden;flex-shrink:0;">
<?php if (!empty($coach['photo_path'])): ?>
<img src="/<?= e($coach['photo_path']) ?>" style="width:100%;height:100%;object-fit:cover;">
<?php else: ?>
<i data-lucide="user" style="width:20px;height:20px;color:#9CA3AF;"></i>
<?php endif; ?>
</div>
<div style="flex:1;min-width:0;">
<div style="font-weight:600;font-size:13px;"><?= e($coach['full_name_ar']) ?></div>
<div style="font-size:11px;color:#6B7280;">
<?php if (isset($coach['available_groups'])): ?>
<?php if ((int) $coach['available_groups'] > 0): ?>
<span style="color:#059669;"><?= (int) $coach['available_groups'] ?> مجموعة متاحة</span>
<?php else: ?>
<span style="color:#D97706;">سيتم إنشاء مجموعة</span>
<?php endif; ?>
<?php if (isset($coach['total_players'])): ?>
<?= (int) $coach['total_players'] ?> لاعب
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<form method="POST" action="/sa/assignments/<?= (int) $player['id'] ?>/assign-coach" style="margin:0;">
<?= csrf_field() ?>
<input type="hidden" name="program_id" value="<?= (int) $selectedProgram ?>">
<input type="hidden" name="coach_id" value="<?= (int) $coach['id'] ?>">
<button type="submit" class="btn btn-sm btn-primary" style="font-size:11px;padding:5px 12px;white-space:nowrap;">
تعيين
</button>
</form>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<!-- Any available coach option -->
<div style="margin-top:15px;padding-top:15px;border-top:1px solid #F3F4F6;text-align:center;">
<?php if (!empty($bestGroupId)): ?>
<form method="POST" action="/sa/assignments/<?= (int) $player['id'] ?>/assign" style="display:inline;">
<?= csrf_field() ?>
<input type="hidden" name="group_id" value="<?= (int) $bestGroupId ?>">
<button type="submit" class="btn btn-outline" style="font-size:13px;padding:8px 20px;color:#0D7377;border-color:#0D7377;">
<i data-lucide="shuffle" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i> أي مدرب متاح (أقل مجموعة ازدحاماً)
</button>
</form>
<?php else: ?>
<span style="font-size:12px;color:#6B7280;">لا توجد مجموعات متاحة — اختر مدرب لإنشاء مجموعة جديدة</span>
<?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') lucide.createIcons(); if (typeof lucide !== 'undefined') lucide.createIcons();
......
...@@ -49,12 +49,19 @@ ...@@ -49,12 +49,19 @@
<input type="number" name="duration_minutes" class="form-input" value="60" min="15" max="480"> <input type="number" name="duration_minutes" class="form-input" value="60" min="15" max="480">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">رسوم العضو (ج.م)</label> <label class="form-label">نوع التسعير <span style="color:#DC2626;">*</span></label>
<input type="number" name="fee_member" class="form-input" value="0" min="0" step="0.01"> <select name="pricing_type" id="pricing_type" class="form-input">
<option value="per_game" <?= old('pricing_type') === 'per_game' ? 'selected' : '' ?>>لكل لعبة</option>
<option value="per_hour" <?= old('pricing_type') === 'per_hour' ? 'selected' : '' ?>>بالساعة</option>
</select>
</div>
<div class="form-group">
<label class="form-label" id="fee_member_label">رسوم العضو (ج.م)</label>
<input type="number" name="fee_member" class="form-input" value="<?= e(old('fee_member', '0')) ?>" min="0" step="0.01">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">رسوم غير العضو (ج.م)</label> <label class="form-label" id="fee_nonmember_label">رسوم غير العضو (ج.م)</label>
<input type="number" name="fee_nonmember" class="form-input" value="0" min="0" step="0.01"> <input type="number" name="fee_nonmember" class="form-input" value="<?= e(old('fee_nonmember', '0')) ?>" min="0" step="0.01">
</div> </div>
</div> </div>
...@@ -73,6 +80,20 @@ ...@@ -73,6 +80,20 @@
</div> </div>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { if (typeof lucide !== 'undefined') lucide.createIcons(); }); document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') lucide.createIcons();
var pricingType = document.getElementById('pricing_type');
var feeMemberLabel = document.getElementById('fee_member_label');
var feeNonmemberLabel = document.getElementById('fee_nonmember_label');
function updateFeeLabels() {
var suffix = pricingType.value === 'per_hour' ? ' / ساعة' : ' / لعبة';
feeMemberLabel.textContent = 'رسوم العضو (ج.م)' + suffix;
feeNonmemberLabel.textContent = 'رسوم غير العضو (ج.م)' + suffix;
}
pricingType.addEventListener('change', updateFeeLabels);
updateFeeLabels();
});
</script> </script>
<?php $__template->endSection(); ?> <?php $__template->endSection(); ?>
...@@ -36,9 +36,10 @@ ...@@ -36,9 +36,10 @@
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
<div style="margin-top:12px;display:flex;gap:15px;font-size:13px;"> <div style="margin-top:12px;display:flex;gap:15px;font-size:13px;align-items:center;">
<span style="color:#0D7377;font-weight:600;">عضو: <?= money((float) $g['fee_member']) ?></span> <?php $pricingSuffix = ($g['pricing_type'] ?? 'per_game') === 'per_hour' ? '/ساعة' : '/لعبة'; ?>
<span style="color:#6B7280;">غير عضو: <?= money((float) $g['fee_nonmember']) ?></span> <span style="color:#0D7377;font-weight:600;">عضو: <?= money((float) $g['fee_member']) ?> <?= $pricingSuffix ?></span>
<span style="color:#6B7280;">غير عضو: <?= money((float) $g['fee_nonmember']) ?> <?= $pricingSuffix ?></span>
</div> </div>
</a> </a>
<?php endforeach; ?> <?php endforeach; ?>
......
...@@ -30,11 +30,20 @@ ...@@ -30,11 +30,20 @@
<div style="font-weight:600;"><?= (int) $game['max_players'] ?> لاعب</div> <div style="font-weight:600;"><?= (int) $game['max_players'] ?> لاعب</div>
</div> </div>
<div> <div>
<div style="color:#6B7280;font-size:12px;margin-bottom:3px;">رسوم العضو</div> <div style="color:#6B7280;font-size:12px;margin-bottom:3px;">نوع التسعير</div>
<div style="font-weight:600;">
<?php $pricingLabels = ['per_game' => 'لكل لعبة', 'per_hour' => 'بالساعة']; ?>
<span style="padding:2px 8px;border-radius:6px;background:#EFF6FF;color:#2563EB;font-size:11px;">
<?= e($pricingLabels[$game['pricing_type'] ?? 'per_game'] ?? 'لكل لعبة') ?>
</span>
</div>
</div>
<div>
<div style="color:#6B7280;font-size:12px;margin-bottom:3px;">رسوم العضو<?= ($game['pricing_type'] ?? '') === 'per_hour' ? ' / ساعة' : '' ?></div>
<div style="font-weight:700;color:#0D7377;"><?= money((float) $game['fee_member']) ?></div> <div style="font-weight:700;color:#0D7377;"><?= money((float) $game['fee_member']) ?></div>
</div> </div>
<div> <div>
<div style="color:#6B7280;font-size:12px;margin-bottom:3px;">رسوم غير العضو</div> <div style="color:#6B7280;font-size:12px;margin-bottom:3px;">رسوم غير العضو<?= ($game['pricing_type'] ?? '') === 'per_hour' ? ' / ساعة' : '' ?></div>
<div style="font-weight:700;color:#0D7377;"><?= money((float) $game['fee_nonmember']) ?></div> <div style="font-weight:700;color:#0D7377;"><?= money((float) $game['fee_nonmember']) ?></div>
</div> </div>
</div> </div>
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
</select> </select>
</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="coach_id" id="coach_id" class="form-select" required data-searchable="true" data-placeholder="-- اختر المدرب --"> <select name="coach_id" id="coach_id" class="form-select" required data-searchable="true" data-placeholder="-- اختر المدرب --">
<option value="">-- اختر المدرب --</option> <option value="">-- اختر المدرب --</option>
<?php foreach ($coaches as $c): ?> <?php foreach ($coaches as $c): ?>
...@@ -61,6 +61,24 @@ ...@@ -61,6 +61,24 @@
</select> </select>
</div> </div>
</div> </div>
<div style="margin-top:15px;">
<div class="form-group">
<label class="form-label">مدربين إضافيين</label>
<div id="additional-coaches" style="display:flex;flex-wrap:wrap;gap:8px;margin-top:6px;">
<?php
$oldAdditional = old('additional_coaches', []);
if (!is_array($oldAdditional)) $oldAdditional = [];
?>
<?php foreach ($coaches as $c): ?>
<label style="display:inline-flex;align-items:center;gap:4px;padding:5px 10px;border:1px solid #E5E7EB;border-radius:6px;font-size:12px;cursor:pointer;background:#fff;">
<input type="checkbox" name="additional_coaches[]" value="<?= (int) $c['id'] ?>" <?= in_array($c['id'], $oldAdditional) ? 'checked' : '' ?> style="accent-color:#0D7377;">
<?= e($c['name_ar']) ?>
</label>
<?php endforeach; ?>
</div>
<small style="color:#6B7280;font-size:11px;margin-top:4px;display:block;">اختياري — يمكن إضافة أكثر من مدرب للمجموعة</small>
</div>
</div>
</div> </div>
</div> </div>
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
</select> </select>
</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="coach_id" id="coach_id" class="form-select" required data-searchable="true" data-placeholder="-- اختر المدرب --"> <select name="coach_id" id="coach_id" class="form-select" required data-searchable="true" data-placeholder="-- اختر المدرب --">
<option value="">-- اختر المدرب --</option> <option value="">-- اختر المدرب --</option>
<?php foreach ($coaches as $c): ?> <?php foreach ($coaches as $c): ?>
...@@ -61,6 +61,24 @@ ...@@ -61,6 +61,24 @@
</select> </select>
</div> </div>
</div> </div>
<div style="margin-top:15px;">
<div class="form-group">
<label class="form-label">مدربين إضافيين</label>
<div id="additional-coaches" style="display:flex;flex-wrap:wrap;gap:8px;margin-top:6px;">
<?php
$oldAdditional = old('additional_coaches', $groupCoachIds ?? []);
if (!is_array($oldAdditional)) $oldAdditional = [];
?>
<?php foreach ($coaches as $c): ?>
<label style="display:inline-flex;align-items:center;gap:4px;padding:5px 10px;border:1px solid #E5E7EB;border-radius:6px;font-size:12px;cursor:pointer;background:#fff;">
<input type="checkbox" name="additional_coaches[]" value="<?= (int) $c['id'] ?>" <?= in_array($c['id'], $oldAdditional) ? 'checked' : '' ?> style="accent-color:#0D7377;">
<?= e($c['name_ar']) ?>
</label>
<?php endforeach; ?>
</div>
<small style="color:#6B7280;font-size:11px;margin-top:4px;display:block;">اختياري — يمكن إضافة أكثر من مدرب للمجموعة</small>
</div>
</div>
</div> </div>
</div> </div>
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
<?php $__template->section('content'); ?> <?php $__template->section('content'); ?>
<?php <?php
$additionalCoaches = $additionalCoaches ?? [];
$dayNames = ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت']; $dayNames = ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'];
$statusColors = ['active' => '#059669', 'paused' => '#D97706', 'completed' => '#6B7280', 'cancelled' => '#DC2626']; $statusColors = ['active' => '#059669', 'paused' => '#D97706', 'completed' => '#6B7280', 'cancelled' => '#DC2626'];
$statusBgs = ['active' => '#ECFDF5', 'paused' => '#FEF3C7', 'completed' => '#F3F4F6', 'cancelled' => '#FEE2E2']; $statusBgs = ['active' => '#ECFDF5', 'paused' => '#FEF3C7', 'completed' => '#F3F4F6', 'cancelled' => '#FEE2E2'];
...@@ -33,8 +34,15 @@ $st = $group['status'] ?? 'active'; ...@@ -33,8 +34,15 @@ $st = $group['status'] ?? 'active';
<div style="font-weight:600;"><?= e($group['program_name'] ?? '—') ?></div> <div style="font-weight:600;"><?= e($group['program_name'] ?? '—') ?></div>
</div> </div>
<div> <div>
<div style="font-size:12px;color:#6B7280;margin-bottom:4px;">المدرب</div> <div style="font-size:12px;color:#6B7280;margin-bottom:4px;">المدربين</div>
<div style="font-weight:600;"><?= e($group['coach_name'] ?? '—') ?></div> <div style="font-weight:600;">
<?= e($group['coach_name'] ?? '—') ?>
<?php if (!empty($additionalCoaches)): ?>
<?php foreach ($additionalCoaches as $ac): ?>
<span style="display:inline-block;margin-right:6px;padding:2px 8px;background:#F0FDFA;border:1px solid #99F6E4;border-radius:6px;font-size:11px;font-weight:500;"><?= e($ac['full_name_ar']) ?></span>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div> </div>
<div> <div>
<div style="font-size:12px;color:#6B7280;margin-bottom:4px;">الحالة</div> <div style="font-size:12px;color:#6B7280;margin-bottom:4px;">الحالة</div>
......
<?php
declare(strict_types=1);
return function (\App\Core\Database $db): void {
$exists = $db->selectOne(
"SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'sa_group_coaches'"
);
if (!$exists) {
$db->raw("
CREATE TABLE `sa_group_coaches` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`group_id` BIGINT UNSIGNED NOT NULL,
`coach_id` BIGINT UNSIGNED NOT NULL,
`role` VARCHAR(20) NOT NULL DEFAULT 'primary' COMMENT 'primary, assistant',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY `uq_sa_gc_group_coach` (`group_id`, `coach_id`),
INDEX `idx_sa_gc_coach` (`coach_id`),
CONSTRAINT `fk_sa_gc_group` FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE CASCADE,
CONSTRAINT `fk_sa_gc_coach` FOREIGN KEY (`coach_id`) REFERENCES `sa_coaches`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
");
// Backfill from existing sa_groups.coach_id
$db->raw("
INSERT IGNORE INTO sa_group_coaches (group_id, coach_id, role, created_at)
SELECT id, coach_id, 'primary', NOW()
FROM sa_groups
WHERE coach_id IS NOT NULL AND coach_id > 0 AND is_archived = 0
");
}
};
<?php
declare(strict_types=1);
return function (\App\Core\Database $db): void {
$exists = $db->selectOne(
"SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'sa_games' AND column_name = 'pricing_type'"
);
if (!$exists) {
$db->raw("ALTER TABLE `sa_games` ADD COLUMN `pricing_type` VARCHAR(20) NOT NULL DEFAULT 'per_game' COMMENT 'per_game, per_hour' AFTER `fee_nonmember`");
}
};
...@@ -315,6 +315,17 @@ app/Modules/SportsActivity/ ...@@ -315,6 +315,17 @@ app/Modules/SportsActivity/
| payment_request_id | bigint unsigned | | FK to payment_requests | | payment_request_id | bigint unsigned | | FK to payment_requests |
| activated_by_payment_id | bigint unsigned | | FK to payments | | activated_by_payment_id | bigint unsigned | | FK to payments |
#### `sa_group_coaches` (multi-coach pivot)
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| group_id | bigint unsigned | MUL | FK to sa_groups |
| coach_id | bigint unsigned | MUL | FK to sa_coaches |
| role | varchar(20) | | primary/assistant |
| created_at | datetime | | |
**Note:** `sa_groups.coach_id` is kept as backward-compatible "primary coach" shortcut. The pivot table is the truth for all coaches. `EnrollmentService::enrollByCoach()` checks both fields when finding groups.
#### `sa_group_schedule` #### `sa_group_schedule`
| Column | Type | Key | Notes | | Column | Type | Key | Notes |
|--------|------|-----|-------| |--------|------|-----|-------|
......
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