Commit 3a5ae9a4 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix(org-leaderboards): rank from what the game writes

The pages read org_leaderboards and org_seasonal_rankings, which nothing in
the game writes (both empty), and "recalculate" counted match_players and
tournament_participants, which do not exist, so every board was empty.

The boards are now computed live for each org's active members from
player_game_ratings (games, wins, losses, draws, best streak, best rating per
game) and tournament_registrations (entered, won = final_standing 1): a
cross-org table ranked by wins, and a per-org board ranked by wins or, for
one game, by rating. There is no seasonal org ranking in the game, so that
page and the recalculate action are gone (the old URL redirects).
Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
parent 38add483
......@@ -382,7 +382,6 @@ return [
// Org Leaderboards
'org-leaderboards' => ['module' => 'org-leaderboards', 'action' => 'index'],
'org-leaderboards/seasonal' => ['module' => 'org-leaderboards', 'action' => 'seasonal'],
'org-leaderboards/recalculate/{orgId}' => ['module' => 'org-leaderboards', 'action' => 'recalculate'],
'org-leaderboards/{orgId}' => ['module' => 'org-leaderboards', 'action' => 'orgBoard'],
'api/org-leaderboards/{orgId}' => ['module' => 'org-leaderboards', 'action' => 'apiLeaderboard'],
......
<?php
/**
* Org leaderboards, computed live from what the game actually writes:
* player_game_ratings (rating, games, wins, losses, draws per game and mode)
* and tournament_registrations (tournaments entered, final_standing 1 = won),
* for each org's active members (org_members.status = 'active').
*
* This used to read org_leaderboards / org_seasonal_rankings, which nothing in
* the game writes (both empty), and its "recalculate" counted match_players
* and tournament_participants, tables that do not exist. Those are gone; there
* is no seasonal org ranking in the game, so that page is gone too.
*/
class OrgLeaderboardsController
{
private Database $db;
public const GAMES = ['all' => 'كل الألعاب', 'chess' => 'شطرنج', 'ludo' => 'لودو', 'domino' => 'دومينو', 'backgammon' => 'طاولة'];
public function __construct()
{
$this->db = Database::getInstance();
}
public function index(array $params, string $method): void
private static function game(): string
{
$queryParams = [
'select' => '*, el3ab_organizations(id,name,name_ar,logo_url)',
'order' => 'total_points.desc',
];
$total = $this->db->count('org_seasonal_rankings', []);
$pagination = Pagination::fromRequest($total);
$g = (string)($_GET['game'] ?? 'all');
return isset(self::GAMES[$g]) ? $g : 'all';
}
$queryParams['offset'] = $pagination->offset;
$queryParams['limit'] = $pagination->perPage;
/** Orgs ranked by their active members' wins (or, for one game, also by average rating). */
public function index(array $params, string $method): void
{
$game = self::game();
$q = ['select' => 'id,name,name_ar,logo_url,tag,member_count', 'is_active' => 'eq.true', 'order' => 'member_count.desc', 'limit' => 1000];
OrgScope::filter($q, ['id']);
$orgs = $this->db->select('el3ab_organizations', $q);
$members = $this->activeMembers(array_column($orgs, 'id'));
$stats = $this->playerStats(array_values(array_unique(array_merge(...array_values($members ?: [[]])))), $game);
$rows = [];
foreach ($orgs as $o) {
$r = ['org' => $o, 'members' => 0, 'players' => 0, 'games' => 0, 'wins' => 0, 'tournaments' => 0, 'tournaments_won' => 0, 'rating_sum' => 0, 'rated' => 0];
foreach ($members[$o['id']] ?? [] as $pid) {
$r['members']++;
$s = $stats[$pid] ?? null;
if (!$s) continue;
if ($s['games'] > 0) $r['players']++;
$r['games'] += $s['games'];
$r['wins'] += $s['wins'];
$r['tournaments'] += $s['tournaments'];
$r['tournaments_won'] += $s['tournaments_won'];
if ($s['rating'] !== null) { $r['rating_sum'] += $s['rating']; $r['rated']++; }
}
$r['avg_rating'] = $r['rated'] ? (int)round($r['rating_sum'] / $r['rated']) : null;
$rows[] = $r;
}
usort($rows, fn($a, $b) => [$b['wins'], $b['tournaments_won'], $b['games']] <=> [$a['wins'], $a['tournaments_won'], $a['games']]);
$rankings = $this->db->select('org_seasonal_rankings', $queryParams);
$pageTitle = 'متصدرو المنظمات';
View::render('org-leaderboards/index', compact('rows', 'game', 'pageTitle'));
}
$pageTitle = 'لوحة المتصدرين العامة';
/** One org's active members, ranked by rating for one game, or by wins across all games. */
public function orgBoard(array $params, string $method): void
{
$org = $this->org($params['orgId'] ?? '');
$game = self::game();
$leaderboard = $this->board($org['id'], $game);
$pageTitle = 'المتصدرين - ' . ((($org['name_ar'] ?? '') ?: $org['name']));
View::render('org-leaderboards/org-board', compact('org', 'leaderboard', 'game', 'pageTitle'));
}
View::render('org-leaderboards/index', compact('rankings', 'pagination', 'pageTitle'));
/** There is no seasonal org ranking in the game; the page that showed an empty table is gone. */
public function seasonal(array $params, string $method): void
{
header('Location: /org-leaderboards');
exit;
}
public function orgBoard(array $params, string $method): void
public function apiLeaderboard(array $params, string $method): void
{
$orgId = $params['orgId'];
$org = $this->db->selectOne('el3ab_organizations', ['id' => "eq.{$orgId}"]);
$org = $this->org($params['orgId'] ?? '');
header('Content-Type: application/json');
echo json_encode(array_map(fn($r) => [
'player_id' => $r['player_id'], 'username' => $r['player']['username'] ?? null, 'display_name' => $r['player']['display_name'] ?? null,
'role' => $r['role'], 'rating' => $r['rating'], 'games' => $r['games'], 'wins' => $r['wins'], 'losses' => $r['losses'], 'draws' => $r['draws'],
'tournaments' => $r['tournaments'], 'tournaments_won' => $r['tournaments_won'],
], array_slice($this->board($org['id'], self::game()), 0, 50)), JSON_UNESCAPED_UNICODE);
}
// ─── Internals ──────────────────────────────────────────────────────
private function org(string $id): array
{
$org = preg_match('/^[0-9a-f-]{36}$/i', $id) ? $this->db->selectOne('el3ab_organizations', ['id' => "eq.{$id}"]) : null;
if (!$org) {
http_response_code(404);
View::render('errors/404');
return;
exit;
}
$queryParams = [
'select' => '*, profiles(id,username,display_name,avatar_url)',
'org_id' => "eq.{$orgId}",
'order' => 'points.desc',
];
$total = $this->db->count('org_leaderboards', ['org_id' => "eq.{$orgId}"]);
$pagination = Pagination::fromRequest($total);
$queryParams['offset'] = $pagination->offset;
$queryParams['limit'] = $pagination->perPage;
$leaderboard = $this->db->select('org_leaderboards', $queryParams);
$pageTitle = 'المتصدرين - ' . ($org['name_ar'] ?? $org['name']);
View::render('org-leaderboards/org-board', compact('org', 'leaderboard', 'pagination', 'pageTitle'));
return $org;
}
public function seasonal(array $params, string $method): void
private function board(string $orgId, string $game): array
{
$season = $_GET['season'] ?? '';
$queryParams = [
'select' => '*, el3ab_organizations(id,name,name_ar,logo_url)',
'order' => 'total_points.desc',
];
if ($season) {
$queryParams['season'] = "eq.{$season}";
$rows = $this->db->select('org_members', ['select' => 'player_id,role,joined_at', 'org_id' => "eq.{$orgId}", 'status' => 'eq.active', 'limit' => 10000]);
$ids = array_column($rows, 'player_id');
$stats = $this->playerStats($ids, $game);
$profiles = [];
foreach (array_chunk($ids, 150) as $chunk) {
foreach ($this->db->select('profiles', ['select' => 'id,username,display_name,avatar_url', 'id' => 'in.(' . implode(',', $chunk) . ')']) as $p) $profiles[$p['id']] = $p;
}
$countParams = $queryParams;
unset($countParams['select'], $countParams['order']);
$total = $this->db->count('org_seasonal_rankings', $countParams);
$pagination = Pagination::fromRequest($total);
$queryParams['offset'] = $pagination->offset;
$queryParams['limit'] = $pagination->perPage;
$rankings = $this->db->select('org_seasonal_rankings', $queryParams);
$pageTitle = 'التصنيف الموسمي';
View::render('org-leaderboards/seasonal', compact('rankings', 'pagination', 'season', 'pageTitle'));
$empty = ['rating' => null, 'games' => 0, 'wins' => 0, 'losses' => 0, 'draws' => 0, 'best_streak' => 0, 'tournaments' => 0, 'tournaments_won' => 0];
$out = [];
foreach ($rows as $m) {
$out[] = ['player_id' => $m['player_id'], 'role' => $m['role'], 'player' => $profiles[$m['player_id']] ?? null] + ($stats[$m['player_id']] ?? $empty);
}
usort($out, $game === 'all'
? fn($a, $b) => [$b['wins'], $b['tournaments_won'], $b['games']] <=> [$a['wins'], $a['tournaments_won'], $a['games']]
: fn($a, $b) => [$b['rating'] ?? -1, $b['wins'], $b['games']] <=> [$a['rating'] ?? -1, $a['wins'], $a['games']]);
return $out;
}
public function recalculate(array $params, string $method): void
/** org id => [player ids] of active members. */
private function activeMembers(array $orgIds): array
{
Auth::requireCsrf();
$orgId = $params['orgId'];
$org = $this->db->selectOne('el3ab_organizations', ['id' => "eq.{$orgId}"]);
if (!$org) {
Response::error('المنظمة غير موجودة', '/org-leaderboards');
return;
}
// Get all members of this org
$members = $this->db->select('org_members', [
'select' => 'player_id',
'org_id' => "eq.{$orgId}",
]);
foreach ($members as $member) {
$playerId = $member['player_id'];
// Count matches participated
$matchCount = $this->db->count('match_players', [
'player_id' => "eq.{$playerId}",
]);
// Count tournament participations
$tournamentCount = $this->db->count('tournament_participants', [
'player_id' => "eq.{$playerId}",
]);
// Calculate points
$points = ($matchCount * 10) + ($tournamentCount * 50);
// Check if entry exists
$existing = $this->db->selectOne('org_leaderboards', [
'org_id' => "eq.{$orgId}",
'player_id' => "eq.{$playerId}",
]);
if ($existing) {
$this->db->update('org_leaderboards', [
'org_id' => "eq.{$orgId}",
'player_id' => "eq.{$playerId}",
], [
'points' => $points,
'matches_played' => $matchCount,
'tournaments_played' => $tournamentCount,
'updated_at' => date('c'),
]);
} else {
$this->db->insert('org_leaderboards', [
'org_id' => $orgId,
'player_id' => $playerId,
'points' => $points,
'matches_played' => $matchCount,
'tournaments_played' => $tournamentCount,
$out = [];
foreach (array_chunk($orgIds, 100) as $chunk) {
$offset = 0;
do {
$rows = $this->db->select('org_members', [
'select' => 'org_id,player_id', 'org_id' => 'in.(' . implode(',', $chunk) . ')', 'status' => 'eq.active',
'order' => 'id.asc', 'offset' => $offset, 'limit' => 1000,
]);
}
foreach ($rows as $r) $out[$r['org_id']][] = $r['player_id'];
$offset += 1000;
} while (count($rows) === 1000);
}
AuditLog::log('recalculate', 'org_leaderboard', $orgId, null, ['members_count' => count($members)]);
Response::success('تم إعادة حساب النقاط بنجاح', "/org-leaderboards/{$orgId}");
return $out;
}
public function apiLeaderboard(array $params, string $method): void
/**
* player id => totals. For one game, rating is the player's best rating
* across that game's modes; for all games it is null (ratings differ per game).
*/
private function playerStats(array $playerIds, string $game): array
{
$orgId = $params['orgId'];
$leaderboard = $this->db->select('org_leaderboards', [
'select' => '*, profiles(id,username,display_name,avatar_url)',
'org_id' => "eq.{$orgId}",
'order' => 'points.desc',
'limit' => 50,
]);
header('Content-Type: application/json');
echo json_encode($leaderboard);
$out = [];
$new = fn() => ['rating' => null, 'games' => 0, 'wins' => 0, 'losses' => 0, 'draws' => 0, 'best_streak' => 0, 'tournaments' => 0, 'tournaments_won' => 0];
foreach (array_chunk(array_values(array_unique(array_filter($playerIds))), 150) as $chunk) {
$in = 'in.(' . implode(',', $chunk) . ')';
$q = ['select' => 'player_id,game_key,rating,games_played,wins,losses,draws,best_win_streak', 'player_id' => $in, 'limit' => 5000];
if ($game !== 'all') $q['game_key'] = "eq.{$game}";
foreach ($this->db->select('player_game_ratings', $q) as $r) {
$s = $out[$r['player_id']] ?? $new();
$s['games'] += (int)$r['games_played'];
$s['wins'] += (int)$r['wins'];
$s['losses'] += (int)$r['losses'];
$s['draws'] += (int)$r['draws'];
$s['best_streak'] = max($s['best_streak'], (int)$r['best_win_streak']);
if ($game !== 'all' && $r['rating'] !== null && (int)$r['games_played'] > 0) $s['rating'] = max($s['rating'] ?? 0, (int)$r['rating']);
$out[$r['player_id']] = $s;
}
foreach ($this->db->select('tournament_registrations', ['select' => 'player_id,final_standing,withdrawn_at', 'player_id' => $in, 'is_bot' => 'is.false', 'limit' => 5000]) as $t) {
if (!empty($t['withdrawn_at'])) continue;
$s = $out[$t['player_id']] ?? $new();
$s['tournaments']++;
if ((int)($t['final_standing'] ?? 0) === 1) $s['tournaments_won']++;
$out[$t['player_id']] = $s;
}
}
return $out;
}
}
<?php
/** Expects: $rows (per org: org, members, players, games, wins, tournaments, tournaments_won, avg_rating), $game */
$medal = fn($rank) => $rank <= 3 ? '<span class="badge ' . ($rank === 1 ? 'badge-warning' : ($rank === 2 ? 'badge-default' : 'badge-info')) . '">#' . $rank . '</span>' : '<span class="text-muted">#' . $rank . '</span>';
?>
<div class="content-header">
<h1>لوحة المتصدرين العامة</h1>
<a href="/org-leaderboards/seasonal" class="btn btn-ghost">التصنيف الموسمي</a>
<h1>متصدرو المنظمات</h1>
</div>
<div class="filter-pills mb-4">
<?php foreach (OrgLeaderboardsController::GAMES as $k => $l): ?>
<a href="/org-leaderboards?game=<?= $k ?>" class="filter-pill <?= $game === $k ? 'active' : '' ?>"><?= $l ?></a>
<?php endforeach; ?>
</div>
<p class="text-sm text-muted mb-4">محسوبة الآن من نتائج اللعب الفعلية (تقييمات اللاعبين في كل لعبة وتسجيلات البطولات) لأعضاء كل منظمة النشطين. الترتيب بعدد الانتصارات ثم البطولات المحسومة.</p>
<div class="data-table-wrapper">
<?php if (empty($rankings)): ?>
<?php if (empty($rows)): ?>
<div class="empty-state">
<svg class="empty-state-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 21h8m-4-4v4m-4.5-8.5L12 8l4.5 4.5M3 3h18v4H3z"/></svg>
<h3 class="empty-state-title">لا توجد تصنيفات</h3>
<p class="empty-state-text">لم يتم تسجيل أي تصنيفات بعد</p>
<h3 class="empty-state-title">لا توجد منظمات نشطة</h3>
</div>
<?php else: ?>
<table class="data-table">
......@@ -16,57 +26,40 @@
<tr>
<th style="width: 60px;">المركز</th>
<th>المنظمة</th>
<th>النقاط</th>
<th>فوز مباريات</th>
<th>الأعضاء</th>
<th>لعبوا</th>
<th>المباريات</th>
<th>الانتصارات</th>
<th>البطولات</th>
<th>فوز بطولات</th>
<th>فوز تحديات</th>
<?php if ($game !== 'all'): ?><th>متوسط التقييم</th><?php endif; ?>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($rankings as $index => $ranking): ?>
<?php
$rank = $pagination->offset + $index + 1;
$org = $ranking['el3ab_organizations'] ?? null;
$orgName = $org['name_ar'] ?? $org['name'] ?? '-';
?>
<?php foreach ($rows as $i => $r): $org = $r['org']; ?>
<tr>
<td>
<?php if ($rank <= 3): ?>
<span class="badge <?= $rank === 1 ? 'badge-warning' : ($rank === 2 ? 'badge-default' : 'badge-info') ?>">#<?= $rank ?></span>
<?php else: ?>
<span class="text-muted">#<?= $rank ?></span>
<?php endif; ?>
</td>
<td><?= $medal($i + 1) ?></td>
<td>
<div class="flex items-center gap-2">
<?php if (!empty($org['logo_url'])): ?>
<img src="<?= View::e($org['logo_url']) ?>" alt="" style="width:24px;height:24px;border-radius:4px;">
<?php endif; ?>
<span><?= View::e($orgName) ?></span>
<span><?= View::e(($org['name_ar'] ?? '') ?: $org['name']) ?></span>
<?php if (!empty($org['tag'])): ?><span class="text-xs text-muted" dir="ltr"><?= View::e($org['tag']) ?></span><?php endif; ?>
</div>
</td>
<td><strong class="tabular-nums"><?= number_format($ranking['total_points'] ?? 0) ?></strong></td>
<td class="tabular-nums"><?= number_format($ranking['matches_won'] ?? 0) ?></td>
<td class="tabular-nums"><?= number_format($ranking['tournaments_won'] ?? 0) ?></td>
<td class="tabular-nums"><?= number_format($ranking['challenges_won'] ?? 0) ?></td>
<td>
<?php if ($org): ?>
<a href="/org-leaderboards/<?= $org['id'] ?>" class="btn btn-sm btn-ghost">التفاصيل</a>
<?php endif; ?>
</td>
<td class="tabular-nums"><?= number_format($r['members']) ?></td>
<td class="tabular-nums"><?= number_format($r['players']) ?></td>
<td class="tabular-nums"><?= number_format($r['games']) ?></td>
<td><strong class="tabular-nums"><?= number_format($r['wins']) ?></strong></td>
<td class="tabular-nums"><?= number_format($r['tournaments']) ?></td>
<td class="tabular-nums"><?= number_format($r['tournaments_won']) ?></td>
<?php if ($game !== 'all'): ?><td class="tabular-nums"><?= $r['avg_rating'] === null ? '-' : number_format($r['avg_rating']) ?></td><?php endif; ?>
<td><a href="/org-leaderboards/<?= $org['id'] ?>?game=<?= $game ?>" class="btn btn-sm btn-ghost">التفاصيل</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="table-footer">
<span><?= $pagination->rangeText() ?></span>
<div class="pagination">
<?php foreach ($pagination->pages() as $p): ?>
<a href="?page=<?= $p ?>" class="pagination-btn <?= $p === $pagination->page ? 'active' : '' ?>"><?= $p ?></a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<?php
/** Expects: $org, $leaderboard (per active member), $game */
$roles = ['owner' => 'مالك', 'admin' => 'مدير', 'arbiter' => 'حكم', 'moderator' => 'مشرف'];
?>
<div class="content-header">
<div class="flex items-center gap-4">
<a href="/organizations/<?= $org['id'] ?>" class="btn btn-icon btn-ghost">
<a href="/organizations/<?= $org['id'] ?>" class="btn btn-icon btn-ghost" aria-label="رجوع">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
</a>
<h1>المتصدرين - <?= View::e($org['name_ar'] ?? $org['name']) ?></h1>
<h1>المتصدرين - <?= View::e(($org['name_ar'] ?? '') ?: $org['name']) ?></h1>
</div>
<form method="POST" action="/org-leaderboards/recalculate/<?= $org['id'] ?>" style="margin:0;">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<button type="submit" class="btn btn-primary" onclick="return confirm('هل تريد إعادة حساب النقاط؟')">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
إعادة الحساب
</button>
</form>
</div>
<!-- Filters -->
<div class="card mb-5">
<form method="GET" action="/org-leaderboards/<?= $org['id'] ?>" class="flex gap-4 items-end flex-wrap">
<div class="form-group" style="margin-bottom:0;">
<label class="form-label">الموسم</label>
<input type="text" name="season" class="form-input" value="<?= View::e($_GET['season'] ?? '') ?>" placeholder="e.g. 2024-Q1" dir="ltr">
</div>
<button type="submit" class="btn btn-primary">تصفية</button>
</form>
<div class="filter-pills mb-4">
<?php foreach (OrgLeaderboardsController::GAMES as $k => $l): ?>
<a href="/org-leaderboards/<?= $org['id'] ?>?game=<?= $k ?>" class="filter-pill <?= $game === $k ? 'active' : '' ?>"><?= $l ?></a>
<?php endforeach; ?>
</div>
<!-- Leaderboard Table -->
<p class="text-sm text-muted mb-4"><?= $game === 'all'
? 'الأعضاء النشطون مرتبون بالانتصارات في كل الألعاب، من نتائج اللعب الفعلية.'
: 'الأعضاء النشطون مرتبون بأعلى تقييم لهم في ' . View::e(OrgLeaderboardsController::GAMES[$game]) . '، من نتائج اللعب الفعلية.' ?></p>
<div class="data-table-wrapper">
<?php if (empty($leaderboard)): ?>
<div class="empty-state">
<svg class="empty-state-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 15l-2 5l9-11h-5l2-5l-9 11h5z"/></svg>
<h3 class="empty-state-title">لا توجد بيانات</h3>
<p class="empty-state-text">لم يتم تسجيل أي نقاط بعد. استخدم زر "إعادة الحساب" لتحديث البيانات.</p>
<h3 class="empty-state-title">لا يوجد أعضاء نشطون</h3>
</div>
<?php else: ?>
<table class="data-table">
......@@ -39,25 +33,21 @@
<tr>
<th style="width: 60px;">المركز</th>
<th>اللاعب</th>
<th>النقاط</th>
<?php if ($game !== 'all'): ?><th>التقييم</th><?php endif; ?>
<th>المباريات</th>
<th>الفوز</th>
<th>فوز</th>
<th>خسارة</th>
<th>تعادل</th>
<th>أفضل سلسلة</th>
<th>البطولات</th>
<th>فوز بطولات</th>
<th>سلسلة حالية</th>
<th>أفضل سلسلة</th>
</tr>
</thead>
<tbody>
<?php foreach ($leaderboard as $index => $entry): ?>
<?php
$rank = $pagination->offset + $index + 1;
$player = $entry['profiles'] ?? null;
$playerName = $player['display_name'] ?? $player['username'] ?? substr($entry['player_id'], 0, 8) . '...';
?>
<?php foreach ($leaderboard as $i => $e): $rank = $i + 1; $p = $e['player']; ?>
<tr>
<td>
<?php if ($rank <= 3): ?>
<?php if ($rank <= 3 && $e['games'] > 0): ?>
<span class="badge <?= $rank === 1 ? 'badge-warning' : ($rank === 2 ? 'badge-default' : 'badge-info') ?>">#<?= $rank ?></span>
<?php else: ?>
<span class="text-muted">#<?= $rank ?></span>
......@@ -65,31 +55,24 @@
</td>
<td>
<div class="flex items-center gap-2">
<?php if (!empty($player['avatar_url'])): ?>
<img src="<?= View::e($player['avatar_url']) ?>" alt="" style="width:24px;height:24px;border-radius:50%;">
<?php if (!empty($p['avatar_url'])): ?>
<img src="<?= View::e($p['avatar_url']) ?>" alt="" style="width:24px;height:24px;border-radius:50%;">
<?php endif; ?>
<span><?= View::e($playerName) ?></span>
<span><?= View::e($p ? ($p['display_name'] ?: $p['username']) : substr($e['player_id'], 0, 8)) ?></span>
<?php if (($e['role'] ?? 'member') !== 'member'): ?><span class="text-xs text-muted"><?= View::e($roles[$e['role']] ?? $e['role']) ?></span><?php endif; ?>
</div>
</td>
<td><strong class="tabular-nums"><?= number_format($entry['points'] ?? 0) ?></strong></td>
<td class="tabular-nums"><?= number_format($entry['matches_played'] ?? 0) ?></td>
<td class="tabular-nums"><?= number_format($entry['matches_won'] ?? 0) ?></td>
<td class="tabular-nums"><?= number_format($entry['tournaments_played'] ?? 0) ?></td>
<td class="tabular-nums"><?= number_format($entry['tournaments_won'] ?? 0) ?></td>
<td class="tabular-nums"><?= number_format($entry['streak_current'] ?? 0) ?></td>
<td class="tabular-nums"><?= number_format($entry['streak_best'] ?? 0) ?></td>
<?php if ($game !== 'all'): ?><td><strong class="tabular-nums"><?= $e['rating'] === null ? '-' : number_format($e['rating']) ?></strong></td><?php endif; ?>
<td class="tabular-nums"><?= number_format($e['games']) ?></td>
<td class="tabular-nums"><?php if ($game === 'all'): ?><strong><?= number_format($e['wins']) ?></strong><?php else: ?><?= number_format($e['wins']) ?><?php endif; ?></td>
<td class="tabular-nums"><?= number_format($e['losses']) ?></td>
<td class="tabular-nums"><?= number_format($e['draws']) ?></td>
<td class="tabular-nums"><?= number_format($e['best_streak']) ?></td>
<td class="tabular-nums"><?= number_format($e['tournaments']) ?></td>
<td class="tabular-nums"><?= number_format($e['tournaments_won']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="table-footer">
<span><?= $pagination->rangeText() ?></span>
<div class="pagination">
<?php foreach ($pagination->pages() as $p): ?>
<a href="?page=<?= $p ?>&season=<?= urlencode($_GET['season'] ?? '') ?>" class="pagination-btn <?= $p === $pagination->page ? 'active' : '' ?>"><?= $p ?></a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
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