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'],
......
This diff is collapsed.
<?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