Implement page for browsing leaderboards
This commit is contained in:
parent
9fc7564f28
commit
f9d60a7d89
|
@ -9,6 +9,7 @@ final class AppRoutes
|
|||
{
|
||||
public const HOME = 'app_home';
|
||||
public const FAVICON_ICO = 'app_favicon_ico';
|
||||
public const LEADERBOARDS = 'app_leaderboards';
|
||||
public const LOGIN = 'app_login';
|
||||
public const LOGOUT = 'app_logout';
|
||||
public const SUBMIT_JSON = 'app_submit_json';
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Contract\Config\AppRoutes;
|
||||
use App\Service\Repository\Nexus\GamePeriodRepository;
|
||||
use App\Service\Repository\Nexus\LeaderboardRepository;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Twig\Environment;
|
||||
|
||||
use function array_key_exists;
|
||||
use function intval;
|
||||
use function sprintf;
|
||||
|
||||
final class LeaderboardController
|
||||
{
|
||||
public function __construct(
|
||||
private Environment $twigEnvironment,
|
||||
private GamePeriodRepository $gamePeriodRepository,
|
||||
private LeaderboardRepository $leaderboardRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route(path: '/leaderboards', name: AppRoutes::LEADERBOARDS, methods: [Request::METHOD_GET])]
|
||||
public function index(
|
||||
Request $request
|
||||
): Response {
|
||||
$gamePeriods = $this->gamePeriodRepository->findAll();
|
||||
|
||||
$defaultGamePeriodId = null;
|
||||
$optionsGamePeriods = [];
|
||||
foreach ($gamePeriods as $gamePeriod) {
|
||||
$id = $gamePeriod->getId();
|
||||
$optionsGamePeriods[$id] = $gamePeriod;
|
||||
if ($gamePeriod->isCurrent()) {
|
||||
$defaultGamePeriodId = $id;
|
||||
}
|
||||
}
|
||||
|
||||
$selectedGamePeriodStr = $request->get(key: 'gamePeriod');
|
||||
if (null !== $selectedGamePeriodStr) {
|
||||
$selectedGamePeriodId = intval(value: $selectedGamePeriodStr);
|
||||
if (false === array_key_exists(key: $selectedGamePeriodId, array: $optionsGamePeriods)) {
|
||||
throw new NotFoundHttpException(
|
||||
message: sprintf('Invalid ID of game period: %d', $selectedGamePeriodId)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$selectedGamePeriodId = $defaultGamePeriodId;
|
||||
}
|
||||
|
||||
$selectedGamePeriod = $optionsGamePeriods[$selectedGamePeriodId];
|
||||
$leaderboards = $this->leaderboardRepository->findByGamePeriod(gamePeriod: $selectedGamePeriod);
|
||||
|
||||
$context = [
|
||||
'optionsGamePeriods' => $optionsGamePeriods,
|
||||
'selectedGamePeriodId' => $selectedGamePeriodId,
|
||||
'leaderboards' => $leaderboards,
|
||||
];
|
||||
$responseBody = $this->twigEnvironment->render(name: 'leaderboards/index.html.twig', context: $context);
|
||||
|
||||
return new Response(content: $responseBody);
|
||||
}
|
||||
|
||||
}
|
|
@ -21,6 +21,11 @@ final class MainMenuService
|
|||
'url' => $this->urlGenerator->generate(name: AppRoutes::HOME),
|
||||
'external' => false,
|
||||
],
|
||||
[
|
||||
'name' => 'Leaderboards',
|
||||
'url' => $this->urlGenerator->generate(name: AppRoutes::LEADERBOARDS),
|
||||
'external' => false,
|
||||
],
|
||||
[
|
||||
'name' => 'Submit data',
|
||||
'url' => $this->urlGenerator->generate(name: AppRoutes::SUBMIT_JSON),
|
||||
|
|
|
@ -26,4 +26,18 @@ final class GamePeriodRepository
|
|||
|
||||
return $query->getOneOrNullResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return GamePeriod[]
|
||||
*/
|
||||
public function findAll(): array
|
||||
{
|
||||
$queryBuilder = $this->entityManager->createQueryBuilder()
|
||||
->select(select: 'gp')
|
||||
->from(from: GamePeriod::class, alias: 'gp')
|
||||
->orderBy(sort: 'gp.id', order: 'ASC');
|
||||
$query = $queryBuilder->getQuery();
|
||||
|
||||
return $query->getResult();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Repository\Nexus;
|
||||
|
||||
use App\Doctrine\Entity\Nexus\GamePeriod;
|
||||
use App\Doctrine\Entity\Nexus\Leaderboard;
|
||||
use App\Doctrine\Entity\Nexus\LeaderboardCategory;
|
||||
use Doctrine\ORM\AbstractQuery;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Query\Expr\Join;
|
||||
|
||||
final class LeaderboardRepository
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function findByGamePeriod(GamePeriod $gamePeriod): array
|
||||
{
|
||||
$queryBuilder = $this->entityManager->createQueryBuilder()
|
||||
->select(select: 'l')
|
||||
->from(from: Leaderboard::class, alias: 'l')
|
||||
->innerJoin(
|
||||
join: LeaderboardCategory::class,
|
||||
alias: 'cat',
|
||||
conditionType: Join::WITH,
|
||||
condition: 'l.category = cat',
|
||||
)
|
||||
->where(predicates: 'l.gamePeriod = :gamePeriod')
|
||||
->orderBy(sort: 'cat.name', order: 'ASC')
|
||||
->addOrderBy(sort: 'cat.type', order: 'ASC')
|
||||
->setParameter(key: 'gamePeriod', value: $gamePeriod);
|
||||
|
||||
$query = $queryBuilder->getQuery();
|
||||
|
||||
return $query->getResult(hydrationMode: AbstractQuery::HYDRATE_OBJECT);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Leaderboards - Nexus Archive{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Leaderboards</h1>
|
||||
|
||||
<section>
|
||||
<form action="" method="get">
|
||||
<label>
|
||||
Game period:
|
||||
<select name="gamePeriod">
|
||||
{% for id, item in optionsGamePeriods %}
|
||||
<option value="{{ id }}"
|
||||
{% if id == selectedGamePeriodId %}selected{% endif %}>{{ item.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<button type="submit">Switch</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="leaderboard-grid">
|
||||
{% for leaderboard in leaderboards %}
|
||||
<article>
|
||||
<table>
|
||||
<caption>{{ leaderboard.category.name }} ({{ leaderboard.category.type|capitalize }})</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Position</th>
|
||||
<th>Character</th>
|
||||
<th>{{ leaderboard.category.scoreLabel }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entry in leaderboard.entries %}
|
||||
<tr>
|
||||
<td>{{ entry.position }}</td>
|
||||
<td>{{ entry.characterName }}</td>
|
||||
<td>{{ entry.score }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
{% else %}
|
||||
<article class="error">No leaderboards found for selected period.</article>
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endblock %}
|
Reference in New Issue