Commit 641ec646 authored by Fares's avatar Fares

feat(sa): add player assignment page and recreational games module

Player Assignment Page (/sa/assignments):
- General-purpose assignment flow for all disciplines (not just swimming)
- Filter by discipline and unassigned status
- Shows player medical status, type, current groups
- Assign with capacity check, age validation, force-enroll option
- Registered in sidebar menu with sa.player.assign permission

Recreational Games (/sa/games):
- Full CRUD for games with type/fee/duration/facility config
- Game sessions with open/full/in_progress/completed lifecycle
- Ticket issuance per session with player name, type, fee tracking
- Session detail view with ticket list and payment status
- Migration creates sa_games, sa_game_sessions, sa_game_tickets tables
- Registered in sidebar with sa.game.view/manage permissions
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 2e5488e5
<?php
declare(strict_types=1);
namespace App\Modules\SportsActivity\Controllers;
use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Core\App;
class GameController extends Controller
{
public function index(Request $request): Response
{
$db = App::getInstance()->db();
$games = $db->select(
"SELECT g.*, f.name_ar as facility_name,
(SELECT COUNT(*) FROM sa_game_sessions gs WHERE gs.game_id = g.id AND gs.status IN ('open','in_progress')) as active_sessions
FROM sa_games g
LEFT JOIN sa_facilities f ON f.id = g.facility_id
WHERE g.is_active = 1
ORDER BY g.name_ar"
);
return $this->view('SportsActivity.Views.games.index', [
'games' => $games,
]);
}
public function create(Request $request): Response
{
$db = App::getInstance()->db();
$facilities = $db->select("SELECT id, name_ar FROM sa_facilities WHERE is_archived = 0 ORDER BY name_ar");
return $this->view('SportsActivity.Views.games.create', [
'facilities' => $facilities,
]);
}
public function store(Request $request): Response
{
$db = App::getInstance()->db();
$name = trim((string) $request->post('name_ar', ''));
if ($name === '') {
return $this->redirect('/sa/games/create')->withError('اسم اللعبة مطلوب');
}
$db->insert('sa_games', [
'name_ar' => $name,
'name_en' => trim((string) $request->post('name_en', '')) ?: null,
'description' => trim((string) $request->post('description', '')) ?: null,
'game_type' => $request->post('game_type', 'free_play'),
'facility_id' => ((int) $request->post('facility_id', 0)) ?: null,
'max_players' => max(1, (int) $request->post('max_players', 20)),
'duration_minutes' => max(15, (int) $request->post('duration_minutes', 60)),
'fee_member' => max(0, (float) $request->post('fee_member', 0)),
'fee_nonmember' => max(0, (float) $request->post('fee_nonmember', 0)),
'is_active' => 1,
'created_by' => (int) (App::getInstance()->session()->get('employee_id') ?? 0),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
return $this->redirect('/sa/games')->withSuccess('تم إنشاء اللعبة بنجاح');
}
public function show(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$game = $db->selectOne("SELECT g.*, f.name_ar as facility_name FROM sa_games g LEFT JOIN sa_facilities f ON f.id = g.facility_id WHERE g.id = ?", [(int) $id]);
if (!$game) {
return $this->redirect('/sa/games')->withError('اللعبة غير موجودة');
}
$sessions = $db->select(
"SELECT gs.*,
fu.name_ar as unit_name
FROM sa_game_sessions gs
LEFT JOIN sa_facility_units fu ON fu.id = gs.facility_unit_id
WHERE gs.game_id = ?
ORDER BY gs.session_date DESC, gs.start_time DESC
LIMIT 50",
[(int) $id]
);
return $this->view('SportsActivity.Views.games.show', [
'game' => $game,
'sessions' => $sessions,
]);
}
public function createSession(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$game = $db->selectOne("SELECT * FROM sa_games WHERE id = ? AND is_active = 1", [(int) $id]);
if (!$game) {
return $this->redirect('/sa/games')->withError('اللعبة غير موجودة');
}
$sessionDate = trim((string) $request->post('session_date', ''));
$startTime = trim((string) $request->post('start_time', ''));
$endTime = trim((string) $request->post('end_time', ''));
if (!$sessionDate || !$startTime || !$endTime) {
return $this->redirect('/sa/games/' . $id)->withError('جميع الحقول مطلوبة');
}
$db->insert('sa_game_sessions', [
'game_id' => (int) $id,
'session_date' => $sessionDate,
'start_time' => $startTime,
'end_time' => $endTime,
'facility_unit_id' => ((int) $request->post('facility_unit_id', 0)) ?: null,
'max_players' => (int) ($game['max_players'] ?? 20),
'current_players' => 0,
'status' => 'open',
'notes' => trim((string) $request->post('notes', '')) ?: null,
'created_by' => (int) (App::getInstance()->session()->get('employee_id') ?? 0),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
return $this->redirect('/sa/games/' . $id)->withSuccess('تم إنشاء الجلسة بنجاح');
}
public function sessionDetail(Request $request, string $id, string $sessionId): Response
{
$db = App::getInstance()->db();
$game = $db->selectOne("SELECT * FROM sa_games WHERE id = ?", [(int) $id]);
if (!$game) {
return $this->redirect('/sa/games')->withError('اللعبة غير موجودة');
}
$session = $db->selectOne("SELECT * FROM sa_game_sessions WHERE id = ? AND game_id = ?", [(int) $sessionId, (int) $id]);
if (!$session) {
return $this->redirect('/sa/games/' . $id)->withError('الجلسة غير موجودة');
}
$tickets = $db->select(
"SELECT t.*, p.full_name_ar as sa_player_name
FROM sa_game_tickets t
LEFT JOIN sa_players p ON p.id = t.player_id
WHERE t.session_id = ?
ORDER BY t.created_at ASC",
[(int) $sessionId]
);
return $this->view('SportsActivity.Views.games.session', [
'game' => $game,
'session' => $session,
'tickets' => $tickets,
]);
}
public function issueTicket(Request $request, string $id, string $sessionId): Response
{
$db = App::getInstance()->db();
$session = $db->selectOne("SELECT * FROM sa_game_sessions WHERE id = ? AND game_id = ?", [(int) $sessionId, (int) $id]);
if (!$session) {
return $this->redirect('/sa/games/' . $id)->withError('الجلسة غير موجودة');
}
if ($session['status'] !== 'open') {
return $this->redirect("/sa/games/{$id}/sessions/{$sessionId}")->withError('الجلسة غير متاحة للتسجيل');
}
if ((int) $session['current_players'] >= (int) $session['max_players']) {
return $this->redirect("/sa/games/{$id}/sessions/{$sessionId}")->withError('الجلسة ممتلئة');
}
$game = $db->selectOne("SELECT * FROM sa_games WHERE id = ?", [(int) $id]);
$playerIdInput = (int) $request->post('player_id', 0);
$playerName = trim((string) $request->post('player_name', ''));
$playerType = $request->post('player_type', 'guest');
if (!$playerIdInput && !$playerName) {
return $this->redirect("/sa/games/{$id}/sessions/{$sessionId}")->withError('يجب تحديد اللاعب أو إدخال اسم');
}
$fee = match ($playerType) {
'member' => (float) ($game['fee_member'] ?? 0),
default => (float) ($game['fee_nonmember'] ?? 0),
};
$ticketNumber = 'GT-' . strtoupper(substr(uniqid(), -8));
$db->beginTransaction();
try {
$db->insert('sa_game_tickets', [
'session_id' => (int) $sessionId,
'ticket_number' => $ticketNumber,
'player_id' => $playerIdInput ?: null,
'player_name' => $playerName ?: null,
'player_type' => $playerType,
'fee_charged' => $fee,
'payment_status' => 'unpaid',
'status' => 'active',
'created_by' => (int) (App::getInstance()->session()->get('employee_id') ?? 0),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
$db->update('sa_game_sessions', [
'current_players' => (int) $session['current_players'] + 1,
'updated_at' => date('Y-m-d H:i:s'),
], 'id = ?', [(int) $sessionId]);
$newCount = (int) $session['current_players'] + 1;
if ($newCount >= (int) $session['max_players']) {
$db->update('sa_game_sessions', ['status' => 'full'], 'id = ?', [(int) $sessionId]);
}
$db->commit();
} catch (\Throwable $e) {
$db->rollBack();
return $this->redirect("/sa/games/{$id}/sessions/{$sessionId}")->withError('فشل إصدار التذكرة: ' . $e->getMessage());
}
return $this->redirect("/sa/games/{$id}/sessions/{$sessionId}")->withSuccess("تم إصدار تذكرة {$ticketNumber} بنجاح");
}
}
<?php
declare(strict_types=1);
namespace App\Modules\SportsActivity\Controllers;
use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Core\App;
use App\Modules\SportsActivity\Services\EnrollmentService;
class PlayerAssignmentController extends Controller
{
public function index(Request $request): Response
{
$db = App::getInstance()->db();
$disciplineId = trim((string) $request->get('discipline_id', ''));
$status = trim((string) $request->get('status', 'unassigned'));
$disciplines = $db->select(
"SELECT id, name_ar, code FROM sa_disciplines WHERE is_archived = 0 ORDER BY name_ar"
);
$where = "pl.is_archived = 0";
$params = [];
if ($status === 'unassigned') {
if ($disciplineId !== '') {
$where .= " AND NOT EXISTS (
SELECT 1 FROM sa_group_players gp
INNER JOIN sa_groups g ON g.id = gp.group_id
INNER JOIN sa_programs p ON p.id = g.program_id
WHERE p.discipline_id = ? AND gp.player_id = pl.id AND gp.status IN ('active','pending_payment')
)";
$params[] = (int) $disciplineId;
} else {
$where .= " AND NOT EXISTS (
SELECT 1 FROM sa_group_players gp
WHERE gp.player_id = pl.id AND gp.status IN ('active','pending_payment')
)";
}
}
$players = $db->select("
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.created_at,
(SELECT GROUP_CONCAT(g.name_ar SEPARATOR ', ')
FROM sa_group_players gp
INNER JOIN sa_groups g ON g.id = gp.group_id
WHERE gp.player_id = pl.id AND gp.status IN ('active','pending_payment')
) as current_groups
FROM sa_players pl
WHERE {$where}
ORDER BY pl.full_name_ar ASC
LIMIT 200
", $params);
return $this->view('SportsActivity.Views.assignments.index', [
'players' => $players,
'disciplines' => $disciplines,
'filters' => ['discipline_id' => $disciplineId, 'status' => $status],
]);
}
public function show(Request $request, string $playerId): Response
{
$db = App::getInstance()->db();
$player = $db->selectOne("SELECT * FROM sa_players WHERE id = ? AND is_archived = 0", [(int) $playerId]);
if (!$player) {
return $this->redirect('/sa/assignments')->withError('اللاعب غير موجود');
}
$disciplineId = trim((string) $request->get('discipline_id', ''));
$groupWhere = "g.status = 'active' AND g.is_archived = 0";
$groupParams = [];
if ($disciplineId !== '') {
$groupWhere .= " AND p.discipline_id = ?";
$groupParams[] = (int) $disciplineId;
}
$groups = $db->select("
SELECT g.id, g.name_ar, g.current_count, g.max_capacity, g.min_age, g.max_age,
p.name_ar as program_name, p.monthly_fee_member, p.monthly_fee_nonmember,
d.name_ar as discipline_name,
c.full_name_ar as coach_name
FROM sa_groups g
JOIN sa_programs p ON p.id = g.program_id
JOIN sa_disciplines d ON d.id = p.discipline_id
LEFT JOIN sa_coaches c ON c.id = g.coach_id
WHERE {$groupWhere}
ORDER BY d.name_ar, p.name_ar, g.name_ar
", $groupParams);
$currentEnrollments = $db->select("
SELECT gp.*, g.name_ar as group_name, p.name_ar as program_name
FROM sa_group_players gp
JOIN sa_groups g ON g.id = gp.group_id
JOIN sa_programs p ON p.id = g.program_id
WHERE gp.player_id = ? AND gp.status IN ('active','pending_payment')
ORDER BY gp.enrolled_at DESC
", [(int) $playerId]);
return $this->view('SportsActivity.Views.assignments.show', [
'player' => $player,
'groups' => $groups,
'currentEnrollments' => $currentEnrollments,
'filters' => ['discipline_id' => $disciplineId],
]);
}
public function assign(Request $request, string $playerId): Response
{
$groupId = (int) $request->post('group_id', 0);
$force = (bool) $request->post('force', false);
if (!$groupId) {
return $this->redirect('/sa/assignments/' . $playerId)->withError('يجب اختيار مجموعة');
}
if ($force) {
$result = EnrollmentService::forceEnroll($groupId, (int) $playerId);
} else {
$result = EnrollmentService::enroll($groupId, (int) $playerId);
}
if (!$result['success']) {
return $this->redirect('/sa/assignments/' . $playerId)->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);
}
}
...@@ -62,6 +62,11 @@ return [ ...@@ -62,6 +62,11 @@ return [
['POST', '/sa/players/{pid:\d+}/documents/{id:\d+}/approve-conditional', 'SportsActivity\Controllers\PlayerDocumentController@approveConditional', ['auth', 'csrf'], 'medical.board.approve'], ['POST', '/sa/players/{pid:\d+}/documents/{id:\d+}/approve-conditional', 'SportsActivity\Controllers\PlayerDocumentController@approveConditional', ['auth', 'csrf'], 'medical.board.approve'],
['POST', '/sa/players/{pid:\d+}/medical/update', 'SportsActivity\Controllers\PlayerDocumentController@updateMedical', ['auth', 'csrf'], 'medical.board.approve'], ['POST', '/sa/players/{pid:\d+}/medical/update', 'SportsActivity\Controllers\PlayerDocumentController@updateMedical', ['auth', 'csrf'], 'medical.board.approve'],
// Player Assignments
['GET', '/sa/assignments', 'SportsActivity\Controllers\PlayerAssignmentController@index', ['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'],
// Academies // Academies
['GET', '/sa/academies', 'SportsActivity\Controllers\AcademyController@index', ['auth'], 'sa.academy.view'], ['GET', '/sa/academies', 'SportsActivity\Controllers\AcademyController@index', ['auth'], 'sa.academy.view'],
['GET', '/sa/academies/create', 'SportsActivity\Controllers\AcademyController@create', ['auth'], 'sa.academy.manage'], ['GET', '/sa/academies/create', 'SportsActivity\Controllers\AcademyController@create', ['auth'], 'sa.academy.manage'],
...@@ -105,6 +110,15 @@ return [ ...@@ -105,6 +110,15 @@ return [
['GET', '/sa/schedule/weekly', 'SportsActivity\Controllers\ScheduleController@weekly', ['auth'], 'sa.schedule.view'], ['GET', '/sa/schedule/weekly', 'SportsActivity\Controllers\ScheduleController@weekly', ['auth'], 'sa.schedule.view'],
['POST', '/sa/schedule/generate', 'SportsActivity\Controllers\ScheduleController@generate', ['auth', 'csrf'], 'sa.schedule.manage'], ['POST', '/sa/schedule/generate', 'SportsActivity\Controllers\ScheduleController@generate', ['auth', 'csrf'], 'sa.schedule.manage'],
// Recreational Games
['GET', '/sa/games', 'SportsActivity\Controllers\GameController@index', ['auth'], 'sa.game.view'],
['GET', '/sa/games/create', 'SportsActivity\Controllers\GameController@create', ['auth'], 'sa.game.manage'],
['POST', '/sa/games', 'SportsActivity\Controllers\GameController@store', ['auth', 'csrf'], 'sa.game.manage'],
['GET', '/sa/games/{id:\d+}', 'SportsActivity\Controllers\GameController@show', ['auth'], 'sa.game.view'],
['POST', '/sa/games/{id:\d+}/sessions', 'SportsActivity\Controllers\GameController@createSession', ['auth', 'csrf'], 'sa.game.manage'],
['GET', '/sa/games/{id:\d+}/sessions/{sid:\d+}', 'SportsActivity\Controllers\GameController@sessionDetail', ['auth'], 'sa.game.view'],
['POST', '/sa/games/{id:\d+}/sessions/{sid:\d+}/ticket', 'SportsActivity\Controllers\GameController@issueTicket', ['auth', 'csrf'], 'sa.game.manage'],
// Booking Wizard // Booking Wizard
['GET', '/sa/booking-wizard', 'SportsActivity\Controllers\BookingWizardController@index', ['auth'], 'sa.booking_wizard.use'], ['GET', '/sa/booking-wizard', 'SportsActivity\Controllers\BookingWizardController@index', ['auth'], 'sa.booking_wizard.use'],
['POST', '/sa/booking-wizard/lookup', 'SportsActivity\Controllers\BookingWizardController@lookup', ['auth', 'csrf'], 'sa.booking_wizard.use'], ['POST', '/sa/booking-wizard/lookup', 'SportsActivity\Controllers\BookingWizardController@lookup', ['auth', 'csrf'], 'sa.booking_wizard.use'],
......
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>تعيين اللاعبين في المجموعات<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<!-- Filters -->
<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;">
<div class="form-group" style="margin:0;min-width:180px;">
<label class="form-label" style="font-size:12px;">النشاط</label>
<select name="discipline_id" class="form-input" style="font-size:13px;">
<option value="">الكل</option>
<?php foreach ($disciplines as $d): ?>
<option value="<?= (int) $d['id'] ?>" <?= ($filters['discipline_id'] ?? '') == $d['id'] ? 'selected' : '' ?>><?= e($d['name_ar']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group" style="margin:0;min-width:150px;">
<label class="form-label" style="font-size:12px;">الحالة</label>
<select name="status" class="form-input" style="font-size:13px;">
<option value="unassigned" <?= ($filters['status'] ?? '') === 'unassigned' ? 'selected' : '' ?>>غير معينين</option>
<option value="all" <?= ($filters['status'] ?? '') === 'all' ? 'selected' : '' ?>>الكل</option>
</select>
</div>
<button type="submit" class="btn btn-primary" style="font-size:13px;padding:8px 16px;">
<i data-lucide="filter" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i> تصفية
</button>
</form>
</div>
<!-- Players Table -->
<div class="card">
<div class="table-responsive">
<table class="data-table">
<thead>
<tr>
<th style="width:40px;">#</th>
<th>اللاعب</th>
<th>النوع</th>
<th>الحالة الطبية</th>
<th>المجموعات الحالية</th>
<th style="width:100px;">إجراء</th>
</tr>
</thead>
<tbody>
<?php if (empty($players)): ?>
<tr>
<td colspan="6" style="text-align:center;padding:30px;color:#6B7280;">لا يوجد لاعبون مطابقون</td>
</tr>
<?php else: ?>
<?php foreach ($players as $i => $p): ?>
<?php
$medColors = ['fit' => '#059669', 'conditional' => '#D97706', 'pending' => '#6B7280', 'expired' => '#DC2626', 'unfit' => '#DC2626'];
$medLabels = ['fit' => 'لائق', 'conditional' => 'مشروط', 'pending' => 'معلق', 'expired' => 'منتهي', 'unfit' => 'غير لائق'];
$ms = $p['medical_status'] ?? 'pending';
?>
<tr>
<td><?= $i + 1 ?></td>
<td>
<div style="display:flex;align-items:center;gap:10px;">
<div style="width:36px;height:36px;border-radius:50%;background:#F3F4F6;display:flex;align-items:center;justify-content:center;overflow:hidden;flex-shrink:0;">
<?php if (!empty($p['photo_path'])): ?>
<img src="/<?= e($p['photo_path']) ?>" style="width:100%;height:100%;object-fit:cover;">
<?php else: ?>
<i data-lucide="user" style="width:18px;height:18px;color:#9CA3AF;"></i>
<?php endif; ?>
</div>
<div>
<div style="font-weight:600;font-size:13px;"><?= e($p['full_name_ar']) ?></div>
<?php if ($p['phone']): ?>
<div style="font-size:11px;color:#6B7280;"><?= e($p['phone']) ?></div>
<?php endif; ?>
</div>
</div>
</td>
<td>
<span style="font-size:11px;padding:2px 8px;border-radius:8px;background:<?= $p['player_type'] === 'member' ? '#ECFDF5' : '#FEF3C7' ?>;color:<?= $p['player_type'] === 'member' ? '#059669' : '#D97706' ?>;">
<?= $p['player_type'] === 'member' ? 'عضو' : 'غير عضو' ?>
</span>
</td>
<td>
<span style="font-size:11px;padding:2px 8px;border-radius:8px;background:<?= $medColors[$ms] ?? '#6B7280' ?>15;color:<?= $medColors[$ms] ?? '#6B7280' ?>;">
<?= $medLabels[$ms] ?? $ms ?>
</span>
</td>
<td style="font-size:12px;color:#6B7280;"><?= e($p['current_groups'] ?? '—') ?></td>
<td>
<a href="/sa/assignments/<?= (int) $p['id'] ?>?discipline_id=<?= e($filters['discipline_id'] ?? '') ?>" class="btn btn-sm btn-primary" style="font-size:12px;padding:5px 12px;">
<i data-lucide="user-plus" style="width:13px;height:13px;vertical-align:middle;margin-left:3px;"></i> تعيين
</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') lucide.createIcons();
});
</script>
<?php $__template->endSection(); ?>
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>تعيين: <?= e($player['full_name_ar']) ?><?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<a href="/sa/assignments" class="btn btn-outline"><i data-lucide="arrow-right" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> العودة</a>
<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<!-- Player Info -->
<div class="card" style="padding:20px;margin-bottom:20px;">
<div style="display:flex;gap:20px;align-items:center;">
<div style="width:60px;height:60px;border-radius:50%;background:#F3F4F6;display:flex;align-items:center;justify-content:center;overflow:hidden;flex-shrink:0;">
<?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:28px;height:28px;color:#9CA3AF;"></i>
<?php endif; ?>
</div>
<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;">
<span><?= $player['player_type'] === 'member' ? 'عضو' : 'غير عضو' ?></span>
<?php if ($player['date_of_birth']): ?>
<span>العمر: <?= (int) date_diff(date_create($player['date_of_birth']), date_create())->y ?> سنة</span>
<?php endif; ?>
<?php if ($player['phone']): ?>
<span><?= e($player['phone']) ?></span>
<?php endif; ?>
<?php
$medColors = ['fit' => '#059669', 'conditional' => '#D97706', 'pending' => '#6B7280', 'expired' => '#DC2626'];
$medLabels = ['fit' => 'لائق', 'conditional' => 'مشروط', 'pending' => 'معلق', 'expired' => 'منتهي'];
$ms = $player['medical_status'] ?? 'pending';
?>
<span style="padding:2px 8px;border-radius:8px;background:<?= $medColors[$ms] ?? '#6B7280' ?>15;color:<?= $medColors[$ms] ?? '#6B7280' ?>;font-weight:600;"><?= $medLabels[$ms] ?? $ms ?></span>
</div>
</div>
</div>
</div>
<!-- Current Enrollments -->
<?php if (!empty($currentEnrollments)): ?>
<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:#374151;">التسجيلات الحالية</h3>
</div>
<div style="padding:15px 20px;">
<div style="display:flex;flex-wrap:wrap;gap:10px;">
<?php foreach ($currentEnrollments as $en): ?>
<div style="padding:8px 14px;background:#ECFDF5;border:1px solid #A7F3D0;border-radius:8px;font-size:12px;">
<strong><?= e($en['group_name']) ?></strong><?= e($en['program_name']) ?>
<span style="margin-right:6px;padding:2px 6px;border-radius:4px;background:<?= $en['status'] === 'active' ? '#059669' : '#D97706' ?>;color:#fff;font-size:10px;"><?= $en['status'] === 'active' ? 'نشط' : 'معلق' ?></span>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
<!-- Available Groups -->
<div class="card">
<div style="padding:12px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:14px;color:#374151;">المجموعات المتاحة</h3>
</div>
<div class="table-responsive">
<table class="data-table">
<thead>
<tr>
<th>المجموعة</th>
<th>البرنامج</th>
<th>النشاط</th>
<th>المدرب</th>
<th>السعة</th>
<th>الرسوم</th>
<th style="width:120px;">إجراء</th>
</tr>
</thead>
<tbody>
<?php if (empty($groups)): ?>
<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;
if ($playerAge !== null) {
if (!empty($g['min_age']) && $playerAge < (int) $g['min_age']) $ageOk = false;
if (!empty($g['max_age']) && $playerAge > (int) $g['max_age']) $ageOk = false;
}
?>
<tr style="<?= !$ageOk ? 'opacity:0.5;' : '' ?>">
<td style="font-weight:600;font-size:13px;"><?= e($g['name_ar']) ?></td>
<td style="font-size:12px;"><?= e($g['program_name']) ?></td>
<td style="font-size:12px;"><?= e($g['discipline_name']) ?></td>
<td style="font-size:12px;"><?= e($g['coach_name'] ?? '—') ?></td>
<td>
<div style="display:flex;align-items:center;gap:6px;">
<div style="flex:1;height:6px;background:#E5E7EB;border-radius:3px;min-width:50px;">
<div style="height:100%;width:<?= $pct ?>%;background:<?= $barColor ?>;border-radius:3px;"></div>
</div>
<span style="font-size:11px;color:#6B7280;"><?= $current ?>/<?= $max ?></span>
</div>
</td>
<td style="font-weight:700;color:#0D7377;font-size:13px;"><?= money($fee) ?></td>
<td>
<?php if (!$ageOk): ?>
<span style="font-size:11px;color:#DC2626;">عمر غير مناسب</span>
<?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: ?>
<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'] ?>">
<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; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') lucide.createIcons();
});
</script>
<?php $__template->endSection(); ?>
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>إضافة لعبة ترفيهية<?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<a href="/sa/games" class="btn btn-outline"><i data-lucide="arrow-right" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> العودة</a>
<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<div class="card" style="max-width:700px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:15px;color:#0D7377;">بيانات اللعبة</h3>
</div>
<div style="padding:20px;">
<form method="POST" action="/sa/games">
<?= csrf_field() ?>
<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="name_ar" class="form-input" required value="<?= e(old('name_ar')) ?>">
</div>
<div class="form-group">
<label class="form-label">اسم اللعبة (إنجليزي)</label>
<input type="text" name="name_en" class="form-input" value="<?= e(old('name_en')) ?>">
</div>
<div class="form-group">
<label class="form-label">نوع اللعبة</label>
<select name="game_type" class="form-input">
<option value="free_play">لعب حر</option>
<option value="session">جلسة محددة</option>
<option value="tournament">بطولة</option>
</select>
</div>
<div class="form-group">
<label class="form-label">المرفق</label>
<select name="facility_id" class="form-input">
<option value="">بدون مرفق</option>
<?php foreach ($facilities as $f): ?>
<option value="<?= (int) $f['id'] ?>"><?= e($f['name_ar']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label class="form-label">الحد الأقصى للاعبين</label>
<input type="number" name="max_players" class="form-input" value="20" min="1" max="200">
</div>
<div class="form-group">
<label class="form-label">المدة (دقيقة)</label>
<input type="number" name="duration_minutes" class="form-input" value="60" min="15" max="480">
</div>
<div class="form-group">
<label class="form-label">رسوم العضو (ج.م)</label>
<input type="number" name="fee_member" class="form-input" value="0" min="0" step="0.01">
</div>
<div class="form-group">
<label class="form-label">رسوم غير العضو (ج.م)</label>
<input type="number" name="fee_nonmember" class="form-input" value="0" min="0" step="0.01">
</div>
</div>
<div class="form-group">
<label class="form-label">الوصف</label>
<textarea name="description" class="form-input" rows="3" placeholder="وصف اختياري للعبة..."><?= e(old('description')) ?></textarea>
</div>
<div style="padding-top:15px;border-top:1px solid #F3F4F6;">
<button type="submit" class="btn btn-primary">
<i data-lucide="save" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> حفظ
</button>
</div>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() { if (typeof lucide !== 'undefined') lucide.createIcons(); });
</script>
<?php $__template->endSection(); ?>
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>الألعاب الترفيهية<?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<a href="/sa/games/create" class="btn btn-primary"><i data-lucide="plus" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> إضافة لعبة</a>
<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<?php if (empty($games)): ?>
<div class="card" style="padding:40px;text-align:center;">
<i data-lucide="gamepad-2" style="width:48px;height:48px;color:#D1D5DB;margin-bottom:12px;"></i>
<p style="color:#6B7280;">لا توجد ألعاب ترفيهية حالياً</p>
<a href="/sa/games/create" class="btn btn-primary" style="margin-top:10px;">إضافة لعبة جديدة</a>
</div>
<?php else: ?>
<div style="display:grid;grid-template-columns:repeat(auto-fill, minmax(300px, 1fr));gap:15px;">
<?php foreach ($games as $g): ?>
<a href="/sa/games/<?= (int) $g['id'] ?>" class="card" style="padding:20px;text-decoration:none;color:inherit;transition:box-shadow 0.2s;" onmouseover="this.style.boxShadow='0 4px 12px rgba(0,0,0,0.1)'" onmouseout="this.style.boxShadow=''">
<div style="display:flex;justify-content:space-between;align-items:start;">
<div>
<div style="font-size:16px;font-weight:700;color:#1A1A2E;margin-bottom:6px;"><?= e($g['name_ar']) ?></div>
<?php if ($g['facility_name']): ?>
<div style="font-size:12px;color:#6B7280;margin-bottom:4px;"><?= e($g['facility_name']) ?></div>
<?php endif; ?>
<div style="font-size:12px;color:#6B7280;">
<?php $typeLabels = ['free_play' => 'لعب حر', 'tournament' => 'بطولة', 'session' => 'جلسة']; ?>
<?= e($typeLabels[$g['game_type'] ?? ''] ?? $g['game_type']) ?>
<?= (int) $g['duration_minutes'] ?> دقيقة •
حتى <?= (int) $g['max_players'] ?> لاعب
</div>
</div>
<div style="text-align:left;">
<?php if ((int) ($g['active_sessions'] ?? 0) > 0): ?>
<span style="display:inline-block;padding:3px 8px;border-radius:8px;background:#ECFDF5;color:#059669;font-size:11px;font-weight:600;"><?= (int) $g['active_sessions'] ?> جلسة نشطة</span>
<?php endif; ?>
</div>
</div>
<div style="margin-top:12px;display:flex;gap:15px;font-size:13px;">
<span style="color:#0D7377;font-weight:600;">عضو: <?= money((float) $g['fee_member']) ?></span>
<span style="color:#6B7280;">غير عضو: <?= money((float) $g['fee_nonmember']) ?></span>
</div>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
<script>
document.addEventListener('DOMContentLoaded', function() { if (typeof lucide !== 'undefined') lucide.createIcons(); });
</script>
<?php $__template->endSection(); ?>
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?><?= e($game['name_ar']) ?> — جلسة <?= e($session['session_date']) ?><?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<a href="/sa/games/<?= (int) $game['id'] ?>" class="btn btn-outline"><i data-lucide="arrow-right" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> العودة</a>
<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<!-- Session Info -->
<div class="card" style="padding:15px 20px;margin-bottom:20px;">
<div style="display:flex;gap:20px;font-size:13px;flex-wrap:wrap;">
<div><strong>التاريخ:</strong> <?= e($session['session_date']) ?></div>
<div><strong>الوقت:</strong> <?= e(substr($session['start_time'], 0, 5)) ?> - <?= e(substr($session['end_time'], 0, 5)) ?></div>
<div><strong>اللاعبون:</strong> <?= (int) $session['current_players'] ?> / <?= (int) $session['max_players'] ?></div>
<?php
$statusLabels = ['open' => 'متاحة', 'full' => 'ممتلئة', 'in_progress' => 'جارية', 'completed' => 'مكتملة', 'cancelled' => 'ملغية'];
$statusColors = ['open' => '#059669', 'full' => '#D97706', 'in_progress' => '#2563EB', 'completed' => '#6B7280', 'cancelled' => '#DC2626'];
$ss = $session['status'] ?? 'open';
?>
<div>
<span style="padding:3px 10px;border-radius:8px;font-size:12px;font-weight:600;background:<?= $statusColors[$ss] ?? '#6B7280' ?>15;color:<?= $statusColors[$ss] ?? '#6B7280' ?>;"><?= $statusLabels[$ss] ?? $ss ?></span>
</div>
</div>
</div>
<!-- Issue Ticket -->
<?php if (in_array($session['status'], ['open', 'in_progress'])): ?>
<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:#374151;">إصدار تذكرة</h3>
</div>
<div style="padding:15px 20px;">
<form method="POST" action="/sa/games/<?= (int) $game['id'] ?>/sessions/<?= (int) $session['id'] ?>/ticket" style="display:flex;gap:12px;align-items:end;flex-wrap:wrap;">
<?= csrf_field() ?>
<div class="form-group" style="margin:0;min-width:200px;">
<label class="form-label" style="font-size:12px;">اسم اللاعب / الضيف</label>
<input type="text" name="player_name" class="form-input" placeholder="اسم اللاعب أو الضيف" style="font-size:13px;">
</div>
<div class="form-group" style="margin:0;">
<label class="form-label" style="font-size:12px;">النوع</label>
<select name="player_type" class="form-input" style="font-size:13px;">
<option value="guest">ضيف</option>
<option value="member">عضو</option>
<option value="non_member">غير عضو</option>
</select>
</div>
<button type="submit" class="btn btn-primary" style="font-size:13px;padding:8px 16px;">
<i data-lucide="ticket" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i> إصدار تذكرة
</button>
</form>
</div>
</div>
<?php endif; ?>
<!-- Tickets List -->
<div class="card">
<div style="padding:12px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:14px;color:#374151;">التذاكر (<?= count($tickets) ?>)</h3>
</div>
<div class="table-responsive">
<table class="data-table">
<thead>
<tr>
<th>#</th>
<th>رقم التذكرة</th>
<th>اللاعب</th>
<th>النوع</th>
<th>الرسوم</th>
<th>الدفع</th>
<th>الحالة</th>
</tr>
</thead>
<tbody>
<?php if (empty($tickets)): ?>
<tr><td colspan="7" style="text-align:center;padding:30px;color:#6B7280;">لا توجد تذاكر</td></tr>
<?php else: ?>
<?php foreach ($tickets as $i => $t):
$typeLabels = ['member' => 'عضو', 'non_member' => 'غير عضو', 'guest' => 'ضيف'];
$payLabels = ['unpaid' => 'غير مدفوع', 'paid' => 'مدفوع', 'refunded' => 'مسترد'];
$payColors = ['unpaid' => '#D97706', 'paid' => '#059669', 'refunded' => '#6B7280'];
?>
<tr>
<td><?= $i + 1 ?></td>
<td><code style="font-size:11px;background:#F3F4F6;padding:2px 6px;border-radius:4px;"><?= e($t['ticket_number']) ?></code></td>
<td style="font-weight:600;"><?= e($t['sa_player_name'] ?? $t['player_name'] ?? '—') ?></td>
<td style="font-size:12px;"><?= $typeLabels[$t['player_type'] ?? ''] ?? $t['player_type'] ?></td>
<td style="font-weight:600;color:#0D7377;"><?= money((float) $t['fee_charged']) ?></td>
<td>
<?php $ps = $t['payment_status'] ?? 'unpaid'; ?>
<span style="padding:2px 8px;border-radius:8px;font-size:11px;font-weight:600;background:<?= $payColors[$ps] ?? '#6B7280' ?>15;color:<?= $payColors[$ps] ?? '#6B7280' ?>;">
<?= $payLabels[$ps] ?? $ps ?>
</span>
</td>
<td>
<?php if ($t['checked_in']): ?>
<span style="color:#059669;font-size:12px;font-weight:600;">✓ حاضر</span>
<?php else: ?>
<span style="color:#6B7280;font-size:12px;"></span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() { if (typeof lucide !== 'undefined') lucide.createIcons(); });
</script>
<?php $__template->endSection(); ?>
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?><?= e($game['name_ar']) ?><?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<a href="/sa/games" class="btn btn-outline"><i data-lucide="arrow-right" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> العودة</a>
<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<!-- Game Info -->
<div class="card" style="padding:20px;margin-bottom:20px;">
<div style="display:grid;grid-template-columns:repeat(4, 1fr);gap:15px;font-size:13px;">
<div>
<div style="color:#6B7280;font-size:12px;margin-bottom:3px;">النوع</div>
<div style="font-weight:600;">
<?php $typeLabels = ['free_play' => 'لعب حر', 'tournament' => 'بطولة', 'session' => 'جلسة']; ?>
<?= e($typeLabels[$game['game_type'] ?? ''] ?? $game['game_type']) ?>
</div>
</div>
<div>
<div style="color:#6B7280;font-size:12px;margin-bottom:3px;">المرفق</div>
<div style="font-weight:600;"><?= e($game['facility_name'] ?? '—') ?></div>
</div>
<div>
<div style="color:#6B7280;font-size:12px;margin-bottom:3px;">المدة</div>
<div style="font-weight:600;"><?= (int) $game['duration_minutes'] ?> دقيقة</div>
</div>
<div>
<div style="color:#6B7280;font-size:12px;margin-bottom:3px;">الحد الأقصى</div>
<div style="font-weight:600;"><?= (int) $game['max_players'] ?> لاعب</div>
</div>
<div>
<div style="color:#6B7280;font-size:12px;margin-bottom:3px;">رسوم العضو</div>
<div style="font-weight:700;color:#0D7377;"><?= money((float) $game['fee_member']) ?></div>
</div>
<div>
<div style="color:#6B7280;font-size:12px;margin-bottom:3px;">رسوم غير العضو</div>
<div style="font-weight:700;color:#0D7377;"><?= money((float) $game['fee_nonmember']) ?></div>
</div>
</div>
</div>
<!-- Create Session -->
<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:#374151;">إنشاء جلسة جديدة</h3>
</div>
<div style="padding:15px 20px;">
<form method="POST" action="/sa/games/<?= (int) $game['id'] ?>/sessions" style="display:flex;gap:12px;align-items:end;flex-wrap:wrap;">
<?= csrf_field() ?>
<div class="form-group" style="margin:0;">
<label class="form-label" style="font-size:12px;">التاريخ</label>
<input type="date" name="session_date" class="form-input" required value="<?= date('Y-m-d') ?>" style="font-size:13px;">
</div>
<div class="form-group" style="margin:0;">
<label class="form-label" style="font-size:12px;">من</label>
<input type="time" name="start_time" class="form-input" required style="font-size:13px;">
</div>
<div class="form-group" style="margin:0;">
<label class="form-label" style="font-size:12px;">إلى</label>
<input type="time" name="end_time" class="form-input" required style="font-size:13px;">
</div>
<div class="form-group" style="margin:0;">
<label class="form-label" style="font-size:12px;">ملاحظات</label>
<input type="text" name="notes" class="form-input" placeholder="اختياري..." style="font-size:13px;">
</div>
<button type="submit" class="btn btn-primary" style="font-size:13px;padding:8px 16px;">
<i data-lucide="plus" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i> إنشاء
</button>
</form>
</div>
</div>
<!-- Sessions List -->
<div class="card">
<div style="padding:12px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:14px;color:#374151;">الجلسات</h3>
</div>
<div class="table-responsive">
<table class="data-table">
<thead>
<tr>
<th>التاريخ</th>
<th>الوقت</th>
<th>اللاعبون</th>
<th>الحالة</th>
<th>إجراء</th>
</tr>
</thead>
<tbody>
<?php if (empty($sessions)): ?>
<tr><td colspan="5" style="text-align:center;padding:30px;color:#6B7280;">لا توجد جلسات</td></tr>
<?php else: ?>
<?php foreach ($sessions as $s):
$statusLabels = ['open' => 'متاحة', 'full' => 'ممتلئة', 'in_progress' => 'جارية', 'completed' => 'مكتملة', 'cancelled' => 'ملغية'];
$statusColors = ['open' => '#059669', 'full' => '#D97706', 'in_progress' => '#2563EB', 'completed' => '#6B7280', 'cancelled' => '#DC2626'];
$ss = $s['status'] ?? 'open';
?>
<tr>
<td style="font-weight:600;"><?= e($s['session_date']) ?></td>
<td style="direction:ltr;text-align:right;"><?= e(substr($s['start_time'], 0, 5)) ?> - <?= e(substr($s['end_time'], 0, 5)) ?></td>
<td><?= (int) $s['current_players'] ?> / <?= (int) $s['max_players'] ?></td>
<td>
<span style="padding:3px 8px;border-radius:8px;font-size:11px;font-weight:600;background:<?= $statusColors[$ss] ?? '#6B7280' ?>15;color:<?= $statusColors[$ss] ?? '#6B7280' ?>;">
<?= $statusLabels[$ss] ?? $ss ?>
</span>
</td>
<td>
<a href="/sa/games/<?= (int) $game['id'] ?>/sessions/<?= (int) $s['id'] ?>" class="btn btn-sm btn-outline" style="font-size:11px;padding:4px 10px;">
<i data-lucide="eye" style="width:12px;height:12px;vertical-align:middle;margin-left:3px;"></i> تفاصيل
</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() { if (typeof lucide !== 'undefined') lucide.createIcons(); });
</script>
<?php $__template->endSection(); ?>
...@@ -25,6 +25,7 @@ MenuRegistry::register('sports_activity', [ ...@@ -25,6 +25,7 @@ MenuRegistry::register('sports_activity', [
['label_ar' => 'المرافق', 'label_en' => 'Facilities', 'route' => '/sa/facilities', 'permission' => 'sa.facility.view', 'order' => 7], ['label_ar' => 'المرافق', 'label_en' => 'Facilities', 'route' => '/sa/facilities', 'permission' => 'sa.facility.view', 'order' => 7],
['label_ar' => 'المدربين', 'label_en' => 'Coaches', 'route' => '/sa/coaches', 'permission' => 'sa.coach.view', 'order' => 8], ['label_ar' => 'المدربين', 'label_en' => 'Coaches', 'route' => '/sa/coaches', 'permission' => 'sa.coach.view', 'order' => 8],
['label_ar' => 'اللاعبين', 'label_en' => 'Players', 'route' => '/sa/players', 'permission' => 'sa.player.view', 'order' => 9], ['label_ar' => 'اللاعبين', 'label_en' => 'Players', 'route' => '/sa/players', 'permission' => 'sa.player.view', 'order' => 9],
['label_ar' => 'تعيين اللاعبين', 'label_en' => 'Assignments', 'route' => '/sa/assignments', 'permission' => 'sa.player.assign', 'order' => 9.5],
['label_ar' => 'الكروت', 'label_en' => 'Cards', 'route' => '/sa/cards', 'permission' => 'sa.card.view', 'order' => 10], ['label_ar' => 'الكروت', 'label_en' => 'Cards', 'route' => '/sa/cards', 'permission' => 'sa.card.view', 'order' => 10],
['label_ar' => 'الأكاديميات', 'label_en' => 'Academies', 'route' => '/sa/academies', 'permission' => 'sa.academy.view', 'order' => 11], ['label_ar' => 'الأكاديميات', 'label_en' => 'Academies', 'route' => '/sa/academies', 'permission' => 'sa.academy.view', 'order' => 11],
['label_ar' => 'البرامج', 'label_en' => 'Programs', 'route' => '/sa/programs', 'permission' => 'sa.program.view', 'order' => 12], ['label_ar' => 'البرامج', 'label_en' => 'Programs', 'route' => '/sa/programs', 'permission' => 'sa.program.view', 'order' => 12],
...@@ -40,6 +41,7 @@ MenuRegistry::register('sports_activity', [ ...@@ -40,6 +41,7 @@ MenuRegistry::register('sports_activity', [
['label_ar' => 'اللوكرات', 'label_en' => 'Lockers', 'route' => '/sa/lockers', 'permission' => 'sa.locker.view', 'order' => 20], ['label_ar' => 'اللوكرات', 'label_en' => 'Lockers', 'route' => '/sa/lockers', 'permission' => 'sa.locker.view', 'order' => 20],
['label_ar' => 'إيجارات اللوكرات', 'label_en' => 'Locker Rentals', 'route' => '/sa/locker-rentals', 'permission' => 'sa.locker_rental.view','order' => 21], ['label_ar' => 'إيجارات اللوكرات', 'label_en' => 'Locker Rentals', 'route' => '/sa/locker-rentals', 'permission' => 'sa.locker_rental.view','order' => 21],
['label_ar' => 'تذاكر السباحة', 'label_en' => 'Pool Tickets', 'route' => '/sa/pool-tickets', 'permission' => 'sa.pool_ticket.view', 'order' => 22], ['label_ar' => 'تذاكر السباحة', 'label_en' => 'Pool Tickets', 'route' => '/sa/pool-tickets', 'permission' => 'sa.pool_ticket.view', 'order' => 22],
['label_ar' => 'الألعاب الترفيهية','label_en' => 'Recreational Games','route' => '/sa/games', 'permission' => 'sa.game.view', 'order' => 23],
['label_ar' => '── السباحة ──', 'label_en' => '── Swimming ──', 'route' => '#', 'permission' => 'sa.swimming.dashboard','order' => 30], ['label_ar' => '── السباحة ──', 'label_en' => '── Swimming ──', 'route' => '#', 'permission' => 'sa.swimming.dashboard','order' => 30],
['label_ar' => 'لوحة تحكم السباحة','label_en' => 'Swimming Dashboard','route' => '/sa/swimming', 'permission' => 'sa.swimming.dashboard','order' => 31], ['label_ar' => 'لوحة تحكم السباحة','label_en' => 'Swimming Dashboard','route' => '/sa/swimming', 'permission' => 'sa.swimming.dashboard','order' => 31],
['label_ar' => 'تسجيل لاعب سباحة', 'label_en' => 'Register Swimmer','route' => '/sa/swimming/register','permission' => 'sa.swimming.register','order' => 32], ['label_ar' => 'تسجيل لاعب سباحة', 'label_en' => 'Register Swimmer','route' => '/sa/swimming/register','permission' => 'sa.swimming.register','order' => 32],
...@@ -57,6 +59,7 @@ PermissionRegistry::register('sports_activity', [ ...@@ -57,6 +59,7 @@ PermissionRegistry::register('sports_activity', [
'sa.coach.manage' => ['ar' => 'إدارة المدربين', 'en' => 'Manage Coaches'], 'sa.coach.manage' => ['ar' => 'إدارة المدربين', 'en' => 'Manage Coaches'],
'sa.player.view' => ['ar' => 'عرض اللاعبين', 'en' => 'View Players'], 'sa.player.view' => ['ar' => 'عرض اللاعبين', 'en' => 'View Players'],
'sa.player.manage' => ['ar' => 'إدارة اللاعبين', 'en' => 'Manage Players'], 'sa.player.manage' => ['ar' => 'إدارة اللاعبين', 'en' => 'Manage Players'],
'sa.player.assign' => ['ar' => 'تعيين اللاعبين في المجموعات', 'en' => 'Assign Players to Groups'],
'sa.medical.approve' => ['ar' => '(ملغي) اعتماد الشهادات الطبية — استخدم medical.board.approve', 'en' => '(Deprecated) Use medical.board.approve'], 'sa.medical.approve' => ['ar' => '(ملغي) اعتماد الشهادات الطبية — استخدم medical.board.approve', 'en' => '(Deprecated) Use medical.board.approve'],
'sa.academy.view' => ['ar' => 'عرض الأكاديميات', 'en' => 'View Academies'], 'sa.academy.view' => ['ar' => 'عرض الأكاديميات', 'en' => 'View Academies'],
'sa.academy.manage' => ['ar' => 'إدارة الأكاديميات', 'en' => 'Manage Academies'], 'sa.academy.manage' => ['ar' => 'إدارة الأكاديميات', 'en' => 'Manage Academies'],
...@@ -86,6 +89,8 @@ PermissionRegistry::register('sports_activity', [ ...@@ -86,6 +89,8 @@ PermissionRegistry::register('sports_activity', [
'sa.waitlist.view' => ['ar' => 'عرض قائمة الانتظار', 'en' => 'View Waitlist'], 'sa.waitlist.view' => ['ar' => 'عرض قائمة الانتظار', 'en' => 'View Waitlist'],
'sa.waitlist.manage' => ['ar' => 'إدارة قائمة الانتظار', 'en' => 'Manage Waitlist'], 'sa.waitlist.manage' => ['ar' => 'إدارة قائمة الانتظار', 'en' => 'Manage Waitlist'],
'sa.pool-grid.manage' => ['ar' => 'إدارة شبكة حمام السباحة', 'en' => 'Manage Pool Grid'], 'sa.pool-grid.manage' => ['ar' => 'إدارة شبكة حمام السباحة', 'en' => 'Manage Pool Grid'],
'sa.game.view' => ['ar' => 'عرض الألعاب الترفيهية', 'en' => 'View Recreational Games'],
'sa.game.manage' => ['ar' => 'إدارة الألعاب الترفيهية', 'en' => 'Manage Recreational Games'],
'sa.locker.view' => ['ar' => 'عرض اللوكرات', 'en' => 'View Lockers'], 'sa.locker.view' => ['ar' => 'عرض اللوكرات', 'en' => 'View Lockers'],
'sa.locker.manage' => ['ar' => 'إدارة اللوكرات', 'en' => 'Manage Lockers'], 'sa.locker.manage' => ['ar' => 'إدارة اللوكرات', 'en' => 'Manage Lockers'],
'sa.locker_rental.view' => ['ar' => 'عرض إيجارات اللوكرات', 'en' => 'View Locker Rentals'], 'sa.locker_rental.view' => ['ar' => 'عرض إيجارات اللوكرات', 'en' => 'View Locker Rentals'],
......
<?php
declare(strict_types=1);
return [
'up' => "
CREATE TABLE `sa_games` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name_ar` VARCHAR(200) NOT NULL,
`name_en` VARCHAR(200) NULL,
`description` TEXT NULL,
`game_type` VARCHAR(30) NOT NULL DEFAULT 'free_play' COMMENT 'free_play, tournament, session',
`facility_id` BIGINT UNSIGNED NULL,
`max_players` INT UNSIGNED NOT NULL DEFAULT 20,
`duration_minutes` INT UNSIGNED NOT NULL DEFAULT 60,
`fee_member` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`fee_nonmember` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
`branch_id` BIGINT UNSIGNED NULL,
`created_by` BIGINT UNSIGNED NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_sa_games_active` (`is_active`),
INDEX `idx_sa_games_type` (`game_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `sa_game_sessions` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`game_id` BIGINT UNSIGNED NOT NULL,
`session_date` DATE NOT NULL,
`start_time` TIME NOT NULL,
`end_time` TIME NOT NULL,
`facility_unit_id` BIGINT UNSIGNED NULL,
`max_players` INT UNSIGNED NOT NULL DEFAULT 20,
`current_players` INT UNSIGNED NOT NULL DEFAULT 0,
`status` VARCHAR(20) NOT NULL DEFAULT 'open' COMMENT 'open, full, in_progress, completed, cancelled',
`notes` TEXT NULL,
`created_by` BIGINT UNSIGNED NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_sa_gs_game` (`game_id`),
INDEX `idx_sa_gs_date` (`session_date`),
INDEX `idx_sa_gs_status` (`status`),
CONSTRAINT `fk_sa_gs_game` FOREIGN KEY (`game_id`) REFERENCES `sa_games`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `sa_game_tickets` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`session_id` BIGINT UNSIGNED NOT NULL,
`ticket_number` VARCHAR(50) NOT NULL,
`player_id` BIGINT UNSIGNED NULL,
`player_name` VARCHAR(200) NULL COMMENT 'For walk-in guests',
`player_type` VARCHAR(20) NOT NULL DEFAULT 'guest' COMMENT 'member, non_member, guest',
`fee_charged` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`payment_status` VARCHAR(20) NOT NULL DEFAULT 'unpaid' COMMENT 'unpaid, paid, refunded',
`payment_id` BIGINT UNSIGNED NULL,
`checked_in` TINYINT(1) NOT NULL DEFAULT 0,
`checked_in_at` TIMESTAMP NULL,
`status` VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT 'active, cancelled, no_show',
`created_by` BIGINT UNSIGNED NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY `uq_sa_gt_number` (`ticket_number`),
INDEX `idx_sa_gt_session` (`session_id`),
INDEX `idx_sa_gt_player` (`player_id`),
INDEX `idx_sa_gt_status` (`status`),
CONSTRAINT `fk_sa_gt_session` FOREIGN KEY (`session_id`) REFERENCES `sa_game_sessions`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
",
'down' => "
DROP TABLE IF EXISTS `sa_game_tickets`;
DROP TABLE IF EXISTS `sa_game_sessions`;
DROP TABLE IF EXISTS `sa_games`;
",
];
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