diff --git a/webapp/src/Controller/Jury/BalloonController.php b/webapp/src/Controller/Jury/BalloonController.php index 9098772310..32aa5989a3 100644 --- a/webapp/src/Controller/Jury/BalloonController.php +++ b/webapp/src/Controller/Jury/BalloonController.php @@ -14,6 +14,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; @@ -58,6 +59,7 @@ public function indexAction(BalloonService $balloonService): Response } $balloons_table = $balloonService->collectBalloonTable($contest); + $teamSummary = $balloonService->collectTeamBalloonSummary($balloons_table); // Add CSS class and actions. foreach ($balloons_table as $element) { @@ -164,7 +166,8 @@ public function indexAction(BalloonService $balloonService): Response 'filteredCategories' => $filteredCategories, 'availableCategories' => $availableCategories, 'defaultCategories' => $defaultCategories, - 'balloons' => $balloons_table + 'balloons' => $balloons_table, + 'teamSummary' => $teamSummary, ]); } @@ -175,4 +178,13 @@ public function setDoneAction(int $balloonId, BalloonService $balloonService): R return $this->redirectToRoute("jury_balloons"); } + + #[Route(path: '/done', name: 'jury_balloons_setdone_multiple', methods: ['POST'])] + public function setMultipleDoneAction(Request $request, BalloonService $balloonService): RedirectResponse + { + $balloonIds = $request->request->all('balloonIds'); + $balloonService->setDone($balloonIds); + + return $this->redirectToRoute("jury_balloons"); + } } diff --git a/webapp/src/Service/BalloonService.php b/webapp/src/Service/BalloonService.php index 2b3dad6d13..bd8d458244 100644 --- a/webapp/src/Service/BalloonService.php +++ b/webapp/src/Service/BalloonService.php @@ -181,14 +181,64 @@ public function collectBalloonTable(Contest $contest, bool $todo = false): array return $balloons_table; } - public function setDone(int $balloonId): void + /** + * Collect a summary of balloons per team, sorted by location (or external ID if no location). + * + * @param array, + * done: bool}}> $balloons + * @return array}> + */ + public function collectTeamBalloonSummary(array $balloons): array + { + $teamSummary = []; + foreach ($balloons as $balloon) { + $data = $balloon['data']; + $teamId = $data['teamid']; + + if (!isset($teamSummary[$teamId])) { + $teamSummary[$teamId] = [ + 'team' => $data['team'], + 'affiliation' => $data['affiliation'], + 'affiliationid' => $data['affiliationid'], + 'location' => $data['location'], + 'category' => $data['category'], + 'categoryid' => $data['categoryid'], + 'total' => $data['total'], + ]; + } + } + + uasort($teamSummary, function ($a, $b) { + $aKey = $a['location'] ?? $a['team']->getExternalId(); + $bKey = $b['location'] ?? $b['team']->getExternalId(); + return strnatcasecmp((string)$aKey, (string)$bKey); + }); + + return $teamSummary; + } + + /** + * @param int|list $balloonId + */ + public function setDone(int|array $balloonId): void { $em = $this->em; - $balloon = $em->getRepository(Balloon::class)->find($balloonId); - if (!$balloon) { - throw new NotFoundHttpException('balloon not found'); + $balloons = $em->createQueryBuilder() + ->from(Balloon::class, 'b') + ->select('b') + ->andWhere('b.balloonid IN (:balloonIds)') + ->setParameter('balloonIds', $balloonId) + ->getQuery() + ->getResult(); + if (empty($balloons)) { + throw new NotFoundHttpException('balloon(s) not found'); + } + foreach ($balloons as $balloon) { + $balloon->setDone(true); } - $balloon->setDone(true); $em->flush(); } } diff --git a/webapp/templates/jury/balloons.html.twig b/webapp/templates/jury/balloons.html.twig index 38d87c88fe..32034fdd51 100644 --- a/webapp/templates/jury/balloons.html.twig +++ b/webapp/templates/jury/balloons.html.twig @@ -83,6 +83,16 @@ {% block extrafooter %} {% if current_contest is not null %}