]> git.localhorst.tv Git - alttp.git/commitdiff
grouped rounds tournament type
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 19 Nov 2025 13:23:28 +0000 (14:23 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 19 Nov 2025 13:23:28 +0000 (14:23 +0100)
12 files changed:
app/Http/Controllers/RoundController.php
app/Http/Controllers/TournamentController.php
app/Models/Protocol.php
app/Models/Round.php
app/Models/Tournament.php
database/migrations/2025_11_19_083248_tournament_group_size.php [new file with mode: 0644]
database/migrations/2025_11_19_085228_round_group.php [new file with mode: 0644]
resources/js/components/rounds/DeleteDialog.jsx
resources/js/components/rounds/Item.jsx
resources/js/helpers/Round.js
resources/js/i18n/de.js
resources/js/i18n/en.js

index 06cd7bdb25b6d489c919848cbf860885231a187d..589ab8d93e0d6e032673bdeada67710ca815f45b 100644 (file)
@@ -22,23 +22,29 @@ class RoundController extends Controller
                $this->authorize('addRound', $tournament);
 
                $tournament->loadMax('rounds', 'number');
+               $number = intval($tournament->rounds_max_number) + 1;
+
+               $rounds = [];
+
+               for ($i = 0; $i < $tournament->group_size; ++$i) {
+                       $group = static::$GROUP_NAMES[$i];
+                       $round = Round::create([
+                               'game' => $tournament->game,
+                               'group' => $group,
+                               'number' => $number,
+                               'no_record' => $tournament->no_record,
+                               'tournament_id' => $tournament->id,
+                       ]);
+                       Protocol::roundAdded(
+                               $tournament,
+                               $round,
+                               $request->user(),
+                       );
+                       RoundAdded::dispatch($round);
+                       $rounds[] = $round;
+               }
 
-               $round = Round::create([
-                       'game' => $tournament->game,
-                       'number' => intval($tournament->rounds_max_number) + 1,
-                       'no_record' => $tournament->no_record,
-                       'tournament_id' => $validatedData['tournament_id'],
-               ]);
-
-               Protocol::roundAdded(
-                       $tournament,
-                       $round,
-                       $request->user(),
-               );
-
-               RoundAdded::dispatch($round);
-
-               return $round->toJson();
+               return $rounds;
        }
 
        public function delete(Request $request, Round $round) {
@@ -166,4 +172,8 @@ class RoundController extends Controller
                return $round->toJson();
        }
 
+       private static $GROUP_NAMES = [
+               'A', 'B', 'C', 'D', 'E',
+       ];
+
 }
index 58b6222f3dca6aaee2af44cab54d3ec2dcdf7c67..8e3df16d1915f42fee237109d2c1243e7a1b9b61 100644 (file)
@@ -34,7 +34,10 @@ class TournamentController extends Controller
                        'participants',
                        'participants.user',
                ]);
-               $rounds = $tournament->rounds()->with(['results', 'results.user'])->limit(25)->get();
+               $rounds = $tournament->rounds()
+                       ->with(['results', 'results.user'])
+                       ->limit($tournament->ceilRoundLimit(25))
+                       ->get();
                foreach ($rounds as $round) {
                        if (!Gate::allows('seeResults', $round)) {
                                $round->hideResults($request->user());
@@ -91,7 +94,7 @@ class TournamentController extends Controller
                $rounds = $tournament->rounds()
                        ->where('number', '<', $validatedData['last_known'])
                        ->with(['results', 'results.user'])
-                       ->limit(25)->get();
+                       ->limit($tournament->ceilRoundLimit(25))->get();
                foreach ($rounds as $round) {
                        if (!Gate::allows('seeResults', $round)) {
                                $round->hideResults($request->user());
index 9c902ce6c44ffef6204eab7d298c0abe99fe6fb7..73dca93d7a4f1444a997b5f90a0b3ff79837023e 100644 (file)
@@ -274,6 +274,7 @@ class Protocol extends Model
        protected static function roundMemo(Round $round) {
                return [
                        'id' => $round->id,
+                       'group' => $round->group,
                        'locked' => $round->locked,
                        'no_record' => $round->no_record,
                        'number' => $round->number,
index c9435c285867e20e0974fbcc81bb6023806bca95..480832209850be0c9450568ca7c70e7a4ea9d439 100644 (file)
@@ -130,6 +130,7 @@ class Round extends Model
 
        protected $fillable = [
                'game',
+               'group',
                'number',
                'no_record',
                'tournament_id',
index 8080d2ac0a4b97be259dcec29282199d2117125e..c2697a05e84ef1f4972b56d76a98b58c8d17e81d 100644 (file)
@@ -87,7 +87,7 @@ class Tournament extends Model {
        }
 
        public function rounds() {
-               return $this->hasMany(Round::class)->orderBy('number', 'DESC');
+               return $this->hasMany(Round::class)->orderBy('number', 'DESC')->orderBy('group', 'ASC');
        }
 
        public function getTranslatedTitle(): string {
@@ -104,6 +104,13 @@ class Tournament extends Model {
                return '';
        }
 
+       public function ceilRoundLimit(int $limit): int {
+               if ($this->group_size > 1 && ($limit % $this->group_size)) {
+                       return $limit + $this->group_size - ($limit % $this->group_size);
+               }
+               return $limit;
+       }
+
 
        protected $casts = [
                'accept_applications' => 'boolean',
diff --git a/database/migrations/2025_11_19_083248_tournament_group_size.php b/database/migrations/2025_11_19_083248_tournament_group_size.php
new file mode 100644 (file)
index 0000000..8c794ea
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration {
+
+       /**
+        * Run the migrations.
+        */
+       public function up(): void
+       {
+               Schema::table('tournaments', function (Blueprint $table) {
+                       $table->unsignedInteger('group_size')->default(1);
+               });
+       }
+
+       /**
+        * Reverse the migrations.
+        */
+       public function down(): void
+       {
+               Schema::table('tournaments', function (Blueprint $table) {
+                       $table->dropColumn('group_size');
+               });
+       }
+
+};
diff --git a/database/migrations/2025_11_19_085228_round_group.php b/database/migrations/2025_11_19_085228_round_group.php
new file mode 100644 (file)
index 0000000..86d7379
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration {
+
+       /**
+        * Run the migrations.
+        */
+       public function up(): void
+       {
+               Schema::table('rounds', function (Blueprint $table) {
+                       $table->string('group')->default('A');
+               });
+       }
+
+       /**
+        * Reverse the migrations.
+        */
+       public function down(): void
+       {
+               Schema::table('rounds', function (Blueprint $table) {
+                       $table->dropColumn('group');
+               });
+       }
+
+};
index 188f08263f8d0da565b8bf32a1967d7828dcee16..376dd1f4f07f6d8313ae40e3de73359918b2c839 100644 (file)
@@ -5,10 +5,13 @@ import { Alert, Button, Modal } from 'react-bootstrap';
 import { useTranslation } from 'react-i18next';
 import toastr from 'toastr';
 
-const EditDialog = ({
+import { formatNumber } from '../../helpers/Round';
+
+const DeleteDialog = ({
        onHide,
        round,
        show,
+       tournament,
 }) => {
        const { t } = useTranslation();
 
@@ -32,6 +35,7 @@ const EditDialog = ({
                                {t('rounds.deleteConfirmMessage', {
                                        ...round,
                                        date: new Date(round.created_at),
+                                       number: formatNumber(tournament, round),
                                })}
                        </Alert>
                </Modal.Body>
@@ -46,7 +50,7 @@ const EditDialog = ({
        </Modal>;
 };
 
-EditDialog.propTypes = {
+DeleteDialog.propTypes = {
        onHide: PropTypes.func,
        round: PropTypes.shape({
                created_at: PropTypes.string,
@@ -57,4 +61,4 @@ EditDialog.propTypes = {
        }),
 };
 
-export default EditDialog;
+export default DeleteDialog;
index 9cd095fc15244ba3b7b260a6527d4457a9da76ca..39f45862b255dd410d24a9dba7a1b0691ab2e92c 100644 (file)
@@ -18,7 +18,7 @@ import {
        mayViewProtocol,
        isRunner,
 } from '../../helpers/permissions';
-import { isComplete } from '../../helpers/Round';
+import { formatNumber, isComplete } from '../../helpers/Round';
 import { hasFinishedRound } from '../../helpers/User';
 import { useUser } from '../../hooks/user';
 
@@ -56,7 +56,7 @@ const Item = ({
                <div className="d-flex">
                        <div className="info">
                                <p className="date">
-                                       {tournament.show_numbers && round.number ? `#${round.number} ` : ''}
+                                       {formatNumber(tournament, round)}
                                        {t('rounds.date', { date: new Date(round.created_at) })}
                                </p>
                                <p className="seed">
index 68579b84815cd15fad98c2c9463af3a8c8553346..d1f5ef335785c14d6b417e601de6fdaff6941f7e 100644 (file)
@@ -1,6 +1,11 @@
 import Participant from './Participant';
 import Tournament from './Tournament';
 
+export const formatNumber = (tournament, round) => {
+       const group = (tournament?.group_size > 1 && round?.group) || '';
+       return tournament.show_numbers && round?.number ? `#${round.number}${group} ` : '';
+};
+
 export const hasResults = (round) => {
        return round && round.results && round.results.length > 0;
 };
index ef57f67e2f1a177683e77c0229b8915c44955a33..738e0a34f1ed5a5f28fdf98712fcf398053a662c 100644 (file)
@@ -608,7 +608,7 @@ export default {
                        code: 'Code',
                        date: '{{ date, L }}',
                        delete: 'Runde löschen',
-                       deleteConfirmMessage: 'Runde #{{ number }} vom {{ date, L }} löschen?',
+                       deleteConfirmMessage: 'Runde {{ number }} vom {{ date, L }} löschen?',
                        deleteError: 'Fehler beim Löschen',
                        deleteSuccess: 'Runde gelöscht',
                        edit: 'Runde bearbeiten',
index 028e563a3f1db0cdfe05f5ee852e11e0e18a6e15..13c41c78d80ea517f49bf37059ac42a8281737b3 100644 (file)
@@ -608,7 +608,7 @@ export default {
                        code: 'Code',
                        date: '{{ date, L }}',
                        delete: 'Delete round',
-                       deleteConfirmMessage: 'Remove round #{{ number }} from {{ date, L }}?',
+                       deleteConfirmMessage: 'Remove round {{ number }} from {{ date, L }}?',
                        deleteError: 'Error deleting round',
                        deleteSuccess: 'Round deleted',
                        edit: 'Edit round',