]> git.localhorst.tv Git - alttp.git/commitdiff
open tournament type
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 19 Oct 2022 13:39:54 +0000 (15:39 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 19 Oct 2022 13:39:54 +0000 (15:39 +0200)
20 files changed:
app/Events/ResultChanged.php
app/Http/Controllers/ResultController.php
app/Http/Controllers/RoundController.php
app/Http/Controllers/TournamentController.php
app/Models/Result.php
app/Models/Round.php
app/Models/Tournament.php
database/migrations/2022_10_19_072222_tournament_type.php [new file with mode: 0644]
resources/js/components/results/DetailDialog.js
resources/js/components/results/Item.js
resources/js/components/results/List.js
resources/js/components/results/ReportButton.js
resources/js/components/results/ReportDialog.js
resources/js/components/results/ReportForm.js
resources/js/components/rounds/Item.js
resources/js/components/tournament/Detail.js
resources/js/components/tournament/SettingsDialog.js
resources/js/helpers/Round.js
resources/js/helpers/Tournament.js
resources/js/helpers/permissions.js

index 4a62d9d5c707f4e0cd857a7043ebc1c6105fec4f..da93768655a1f85845284f935869648d5f36929a 100644 (file)
@@ -23,6 +23,7 @@ class ResultChanged implements ShouldBroadcast
        public function __construct(Result $result)
        {
                $this->result = $result;
+               $result->load('user');
        }
 
        /**
index 8698e19838aef615ee45b3ec5f4579ba58bb88d1..57f78a157f2d1537cebc3bee96626cd629271a12 100644 (file)
@@ -4,10 +4,10 @@ namespace App\Http\Controllers;
 
 use App\Events\ResultChanged;
 use App\Models\DiscordBotCommand;
-use App\Models\Participant;
 use App\Models\Protocol;
 use App\Models\Result;
 use App\Models\Round;
+use App\Models\User;
 use Illuminate\Http\Request;
 
 class ResultController extends Controller
@@ -17,22 +17,20 @@ class ResultController extends Controller
                $validatedData = $request->validate([
                        'comment' => 'string',
                        'forfeit' => 'boolean',
-                       'participant_id' => 'required|exists:App\\Models\\Participant,id',
                        'round_id' => 'required|exists:App\\Models\\Round,id',
                        'time' => 'numeric',
+                       'user_id' => 'required|exists:App\\Models\\User,id',
                ]);
 
-               $participant = Participant::findOrFail($validatedData['participant_id']);
                $round = Round::findOrFail($validatedData['round_id']);
 
-               $user = $request->user();
-               if ($user->id != $participant->user->id) {
+               if ($validatedData['user_id'] != $request->user()->id) {
                        $this->authorize('create', Result::class);
                }
 
                $result = Result::firstOrCreate([
                        'round_id' => $validatedData['round_id'],
-                       'user_id' => $participant->user_id,
+                       'user_id' => $validatedData['user_id'],
                ]);
                if (!$round->locked) {
                        if (isset($validatedData['forfeit'])) $result->forfeit = $validatedData['forfeit'];
@@ -62,7 +60,11 @@ class ResultController extends Controller
 
                $round->load('results');
                $round->updatePlacement();
-               $round->tournament->updatePlacement();
+               if ($round->tournament->hasScoreboard()) {
+                       $round->tournament->updatePlacement();
+               }
+
+               $result->load('user');
 
                return $result->toJson();
        }
index 596628d072da19ceae60e2c5190d8c2e6c23404d..04e1b865ca0fb3560be7ac9231e5f998b3974897 100644 (file)
@@ -57,7 +57,7 @@ class RoundController extends Controller
 
                RoundChanged::dispatch($round);
 
-               $round->load('results');
+               $round->load(['results', 'results.user']);
 
                return $round->toJson();
        }
@@ -76,7 +76,7 @@ class RoundController extends Controller
 
                RoundChanged::dispatch($round);
 
-               $round->load('results');
+               $round->load(['results', 'results.user']);
 
                return $round->toJson();
        }
@@ -95,7 +95,7 @@ class RoundController extends Controller
 
                RoundChanged::dispatch($round);
 
-               $round->load('results');
+               $round->load(['results', 'results.user']);
 
                return $round->toJson();
        }
index 2bb258efb0be4de7cba36804cd8021b4e4bd8027..9b21736d8ea4a111eece00198e6cd858ca856773 100644 (file)
@@ -30,6 +30,7 @@ class TournamentController extends Controller
                        'applications.user',
                        'rounds',
                        'rounds.results',
+                       'rounds.results.user',
                        'participants',
                        'participants.user',
                )->findOrFail($id);
index 99f3791eacfc951acabb8d69d22053f592adbc80..c672a6a780856b1222b5e4e5ac6f7cb7ef93aa6b 100644 (file)
@@ -11,6 +11,37 @@ class Result extends Model
        use HasFactory;
 
 
+       public static function compareResult(Result $a, Result $b) {
+               $a_time = !$a->forfeit ? $a->time : 0;
+               $b_time = !$b->forfeit ? $b->time : 0;
+               if ($a_time) {
+                       if ($b_time) {
+                               if ($a_time < $b_time) return -1;
+                               if ($b_time < $a_time) return 1;
+                               return static::compareUsername($a, $b);
+                       }
+                       return -1;
+               }
+               if ($b_time) {
+                       return 1;
+               }
+               if ($a->forfeit) {
+                       if ($b->forfeit) {
+                               return static::compareUsername($a, $b);
+                       }
+                       return -1;
+               }
+               if ($b->forfeit) {
+                       return 1;
+               }
+               return static::compareUsername($a, $b);
+       }
+
+       public static function compareUsername(Participant $a, Participant $b) {
+               return strcasecmp($a->user->username, $b->user->username);
+       }
+
+
        public function formatTime() {
                $hours = floor($this->time / 60 / 60);
                $minutes = floor(($this->time / 60) % 60);
@@ -45,6 +76,10 @@ class Result extends Model
                return $this->belongsTo(Participant::class);
        }
 
+       public function user() {
+               return $this->belongsTo(User::class);
+       }
+
        public function getHasFinishedAttribute() {
                return $this->time > 0 || $this->forfeit;
        }
index b6c9d023917f4076775dd3f5d8f55321bb57c745..0f4847a7edb3bc38a7abb0adf977fd05a1881f8d 100644 (file)
@@ -21,43 +21,67 @@ class Round extends Model
        }
 
        public function updatePlacement() {
-               $runners = [];
-               foreach ($this->tournament->participants as $p) {
-                       if ($p->isRunner()) {
-                               $runners[] = $p;
-                       } else {
-                               $result = $p->findResult($this);
-                               if ($result) {
-                                       $result->updatePlacement(null, null);
+               if ($this->tournament->type == 'open-async') {
+                       $results = $this->results->sort([Result::class, 'compareResult']);
+                       $reversed = $results->reverse();
+
+                       $running = 0;
+                       $bonus = 1;
+                       $lastResult = null;
+                       foreach ($reversed as $result) {
+                               $betterThanLast = is_null($lastResult) || $result->time < $lastResult;
+                               if (!$result->forfeit && $betterThanLast) {
+                                       $running += $bonus;
+                                       $lastResult = $result->time;
+                                       $bonus = 1;
+                               } else {
+                                       ++$bonus;
+                               }
+                               if (!$result->forfeit) {
+                                       $result->updatePlacement($running, count($results) - $running + 1);
+                               } else {
+                                       $result->updatePlacement(0, count($results));
                                }
                        }
-               }
-
-               usort($runners, Participant::compareResult($this));
-               $mapped = array_map(function ($p) {
-                       return ['participant' => $p, 'result' => $p->findResult($this)];
-               }, $runners);
-               $filtered = array_filter($mapped, function($r) {
-                       return $r['result'] && ($r['result']->time || $r['result']->forfeit);
-               });
-               $reversed = array_reverse($filtered);
-
-               $running = 0;
-               $bonus = 1;
-               $lastResult = null;
-               foreach ($reversed as $r) {
-                       $betterThanLast = is_null($lastResult) || $r['result']->time < $lastResult;
-                       if (!$r['result']->forfeit && $betterThanLast) {
-                               $running += $bonus;
-                               $lastResult = $r['result']->time;
-                               $bonus = 1;
-                       } else {
-                               ++$bonus;
+               } else {
+                       $runners = [];
+                       foreach ($this->tournament->participants as $p) {
+                               if ($p->isRunner()) {
+                                       $runners[] = $p;
+                               } else {
+                                       $result = $p->findResult($this);
+                                       if ($result) {
+                                               $result->updatePlacement(null, null);
+                                       }
+                               }
                        }
-                       if (!$r['result']->forfeit) {
-                               $r['result']->updatePlacement($running, count($filtered) - $running + 1);
-                       } else {
-                               $r['result']->updatePlacement(0, count($filtered));
+
+                       usort($runners, Participant::compareResult($this));
+                       $mapped = array_map(function ($p) {
+                               return ['participant' => $p, 'result' => $p->findResult($this)];
+                       }, $runners);
+                       $filtered = array_filter($mapped, function($r) {
+                               return $r['result'] && ($r['result']->time || $r['result']->forfeit);
+                       });
+                       $reversed = array_reverse($filtered);
+
+                       $running = 0;
+                       $bonus = 1;
+                       $lastResult = null;
+                       foreach ($reversed as $r) {
+                               $betterThanLast = is_null($lastResult) || $r['result']->time < $lastResult;
+                               if (!$r['result']->forfeit && $betterThanLast) {
+                                       $running += $bonus;
+                                       $lastResult = $r['result']->time;
+                                       $bonus = 1;
+                               } else {
+                                       ++$bonus;
+                               }
+                               if (!$r['result']->forfeit) {
+                                       $r['result']->updatePlacement($running, count($filtered) - $running + 1);
+                               } else {
+                                       $r['result']->updatePlacement(0, count($filtered));
+                               }
                        }
                }
        }
index eea88096bdfc71da9a39115c2ed997a98a0afdb2..6f0feb1237db7a8693c20bf7e73e7c44f62e3fa7 100644 (file)
@@ -21,6 +21,10 @@ class Tournament extends Model
                return $runners;
        }
 
+       public function hasScoreboard() {
+               return $this->type == 'signup-async';
+       }
+
        public function updatePlacement() {
                $runners = [];
                foreach ($this->participants as $p) {
diff --git a/database/migrations/2022_10_19_072222_tournament_type.php b/database/migrations/2022_10_19_072222_tournament_type.php
new file mode 100644 (file)
index 0000000..8f05554
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+       /**
+        * Run the migrations.
+        *
+        * @return void
+        */
+       public function up()
+       {
+               Schema::table('tournaments', function(Blueprint $table) {
+                       $table->string('type')->default('signup-async');
+               });
+       }
+
+       /**
+        * Reverse the migrations.
+        *
+        * @return void
+        */
+       public function down()
+       {
+               Schema::table('tournaments', function(Blueprint $table) {
+                       $table->dropColumn('type');
+               });
+       }
+};
index 368575bd359013d739e87b9c4ca593e5deff562e..a914f5d0e134926dd5fd47f654795cc45fe6209c 100644 (file)
@@ -5,8 +5,8 @@ import { withTranslation } from 'react-i18next';
 
 import Box from '../users/Box';
 import { getTime } from '../../helpers/Result';
-import { findResult } from '../../helpers/Participant';
 import { maySeeResults } from '../../helpers/permissions';
+import { findResult } from '../../helpers/User';
 import { withUser } from '../../helpers/UserContext';
 import i18n from '../../i18n';
 
@@ -14,15 +14,15 @@ const getPlacement = result =>
        `${result.placement}. (${i18n.t('results.points', { count: result.score })})`;
 
 const DetailDialog = ({
+       authUser,
        onHide,
-       participant,
        round,
        show,
        tournament,
        user,
 }) => {
-       const result = findResult(participant, round);
-       const maySee = maySeeResults(user, tournament, round);
+       const result = findResult(user, round);
+       const maySee = maySeeResults(authUser, tournament, round);
        return <Modal className="result-dialog" onHide={onHide} show={show}>
                <Modal.Header closeButton>
                        <Modal.Title>
@@ -41,7 +41,7 @@ const DetailDialog = ({
                                </Form.Group>
                                <Form.Group as={Col} sm={6}>
                                        <Form.Label>{i18n.t('results.runner')}</Form.Label>
-                                       <div><Box user={participant.user} /></div>
+                                       <div><Box user={user} /></div>
                                </Form.Group>
                                <Form.Group as={Col} sm={6}>
                                        <Form.Label>{i18n.t('results.result')}</Form.Label>
@@ -76,11 +76,9 @@ const DetailDialog = ({
 };
 
 DetailDialog.propTypes = {
-       onHide: PropTypes.func,
-       participant: PropTypes.shape({
-               user: PropTypes.shape({
-               }),
+       authUser: PropTypes.shape({
        }),
+       onHide: PropTypes.func,
        round: PropTypes.shape({
                created_at: PropTypes.string,
                number: PropTypes.number,
@@ -92,4 +90,4 @@ DetailDialog.propTypes = {
        }),
 };
 
-export default withTranslation()(withUser(DetailDialog));
+export default withTranslation()(withUser(DetailDialog, 'authUser'));
index a2ee8e392b82a392b76236b8bab92e4e7806d6f7..4b243b34bc4a9a673a55c233d4c4c62481745851 100644 (file)
@@ -5,8 +5,8 @@ import { Button } from 'react-bootstrap';
 import DetailDialog from './DetailDialog';
 import Box from '../users/Box';
 import { getIcon, getTime } from '../../helpers/Result';
-import { findResult } from '../../helpers/Participant';
 import { maySeeResults } from '../../helpers/permissions';
+import { findResult } from '../../helpers/User';
 import { withUser } from '../../helpers/UserContext';
 
 const getClassName = result => {
@@ -23,16 +23,16 @@ const getClassName = result => {
 };
 
 const Item = ({
-       participant,
+       authUser,
        round,
        tournament,
        user,
 }) => {
        const [showDialog, setShowDialog] = useState(false);
-       const result = findResult(participant, round);
-       const maySee = maySeeResults(user, tournament, round);
+       const result = findResult(user, round);
+       const maySee = maySeeResults(authUser, tournament, round);
        return <div className="result">
-               <Box user={participant.user} />
+               <Box user={user} />
                <Button
                        className={getClassName(result)}
                        onClick={() => setShowDialog(true)}
@@ -45,18 +45,16 @@ const Item = ({
                </Button>
                <DetailDialog
                        onHide={() => setShowDialog(false)}
-                       participant={participant}
                        round={round}
                        show={showDialog}
                        tournament={tournament}
+                       user={user}
                />
        </div>;
 };
 
 Item.propTypes = {
-       participant: PropTypes.shape({
-               user: PropTypes.shape({
-               }),
+       authUser: PropTypes.shape({
        }),
        round: PropTypes.shape({
        }),
@@ -66,4 +64,4 @@ Item.propTypes = {
        }),
 };
 
-export default withUser(Item);
+export default withUser(Item, 'authUser');
index 14c70968cd5324823c558732df049e3a2fd715ea..3d1bdfa8f27c9ff0147832934ce81975a8bee2c0 100644 (file)
@@ -8,6 +8,19 @@ import { getRunners } from '../../helpers/Tournament';
 import { withUser } from '../../helpers/UserContext';
 
 const List = ({ round, tournament, user }) => {
+       if (tournament.type === 'open-async') {
+               const results = round.results || [];
+               return <div className="results d-flex flex-wrap">
+                       {results.map(result =>
+                               <Item
+                                       key={result.id}
+                                       round={round}
+                                       tournament={tournament}
+                                       user={result.user}
+                               />
+                       )}
+               </div>;
+       }
        const runners = maySeeResults(user, tournament, round)
                ? sortByResult(getRunners(tournament), round)
                : sortByFinished(getRunners(tournament), round);
@@ -15,9 +28,9 @@ const List = ({ round, tournament, user }) => {
                {runners.map(participant =>
                        <Item
                                key={participant.id}
-                               participant={participant}
                                round={round}
                                tournament={tournament}
+                               user={participant.user}
                        />
                )}
        </div>;
@@ -25,10 +38,15 @@ const List = ({ round, tournament, user }) => {
 
 List.propTypes = {
        round: PropTypes.shape({
+               results: PropTypes.arrayOf(PropTypes.shape({
+               })),
        }),
        tournament: PropTypes.shape({
                participants: PropTypes.arrayOf(PropTypes.shape({
                })),
+               type: PropTypes.string,
+               users: PropTypes.arrayOf(PropTypes.shape({
+               })),
        }),
        user: PropTypes.shape({
        }),
index 0a8c139469db478b09fde94cdd342249dba56d46..ec8f8fd17156e920f7acb74bc14171e48f83a1d1 100644 (file)
@@ -5,11 +5,11 @@ import { withTranslation } from 'react-i18next';
 
 import ReportDialog from './ReportDialog';
 import Icon from '../common/Icon';
-import { findResult } from '../../helpers/Participant';
+import { findResult } from '../../helpers/User';
 import i18n from '../../i18n';
 
-const getButtonLabel = (participant, round) => {
-       const result = findResult(participant, round);
+const getButtonLabel = (user, round) => {
+       const result = findResult(user, round);
        if (round.locked) {
                if (result && result.comment) {
                        return i18n.t('results.editComment');
@@ -25,7 +25,7 @@ const getButtonLabel = (participant, round) => {
        }
 };
 
-const ReportButton = ({ participant, round }) => {
+const ReportButton = ({ round, user }) => {
        const [showDialog, setShowDialog] = useState(false);
 
        return <>
@@ -33,26 +33,26 @@ const ReportButton = ({ participant, round }) => {
                        onClick={() => setShowDialog(true)}
                        variant="secondary"
                >
-                       {getButtonLabel(participant, round)}
+                       {getButtonLabel(user, round)}
                        {' '}
                        <Icon.EDIT title="" />
                </Button>
                <ReportDialog
                        onHide={() => setShowDialog(false)}
-                       participant={participant}
                        round={round}
                        show={showDialog}
+                       user={user}
                />
        </>;
 };
 
 ReportButton.propTypes = {
-       participant: PropTypes.shape({
-       }),
        round: PropTypes.shape({
        }),
        tournament: PropTypes.shape({
        }),
+       user: PropTypes.shape({
+       }),
 };
 
 export default withTranslation()(ReportButton);
index 62adf8491c137ee7b8462254a609c62e59c7db72..b3e528220f8121aa853e8ac684fc78b2595af798 100644 (file)
@@ -8,9 +8,9 @@ import i18n from '../../i18n';
 
 const ReportDialog = ({
        onHide,
-       participant,
        round,
        show,
+       user,
 }) =>
 <Modal className="report-dialog" onHide={onHide} show={show}>
        <Modal.Header closeButton>
@@ -20,20 +20,20 @@ const ReportDialog = ({
        </Modal.Header>
        <ReportForm
                onCancel={onHide}
-               participant={participant}
                round={round}
+               user={user}
        />
 </Modal>;
 
 ReportDialog.propTypes = {
        onHide: PropTypes.func,
-       participant: PropTypes.shape({
-       }),
        round: PropTypes.shape({
        }),
        show: PropTypes.bool,
        tournament: PropTypes.shape({
        }),
+       user: PropTypes.shape({
+       }),
 };
 
 export default withTranslation()(ReportDialog);
index 942869da7123c36b160ef76368c3877f2032bbee..9940171f344f676edf3ce1d89388e4f789be5be7 100644 (file)
@@ -8,7 +8,7 @@ import toastr from 'toastr';
 
 import LargeCheck from '../common/LargeCheck';
 import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
-import { findResult } from '../../helpers/Participant';
+import { findResult } from '../../helpers/User';
 import { formatTime, parseTime } from '../../helpers/Result';
 import i18n from '../../i18n';
 import yup from '../../schema/yup';
@@ -121,16 +121,16 @@ export default withFormik({
        displayName: 'ReportForm',
        enableReinitialize: true,
        handleSubmit: async (values, actions) => {
-               const { comment, forfeit, participant_id, round_id, time } = values;
+               const { comment, forfeit, round_id, time, user_id } = values;
                const { setErrors } = actions;
                const { onCancel } = actions.props;
                try {
                        await axios.post('/api/results', {
                                comment,
                                forfeit,
-                               participant_id,
                                round_id,
                                time: parseTime(time) || 0,
+                               user_id,
                        });
                        toastr.success(i18n.t('results.reportSuccess'));
                        if (onCancel) {
@@ -143,14 +143,14 @@ export default withFormik({
                        }
                }
        },
-       mapPropsToValues: ({ participant, round }) => {
-               const result = findResult(participant, round);
+       mapPropsToValues: ({ round, user }) => {
+               const result = findResult(user, round);
                return {
                        comment: result && result.comment ? result.comment : '',
                        forfeit: result ? !!result.forfeit : false,
-                       participant_id: participant.id,
                        round_id: round.id,
                        time: result && result.time ? formatTime(result) : '',
+                       user_id: user.id,
                };
        },
        validationSchema: yup.object().shape({
index 6eb3d9446809a34d8eac6711f65f5031bc709720..1b1edb22111233a63d13023a26d94523850cafff 100644 (file)
@@ -8,9 +8,8 @@ import SeedCode from './SeedCode';
 import SeedRolledBy from './SeedRolledBy';
 import List from '../results/List';
 import ReportButton from '../results/ReportButton';
-import { isRunner } from '../../helpers/permissions';
+import { mayReportResult, isRunner } from '../../helpers/permissions';
 import { isComplete } from '../../helpers/Round';
-import { findParticipant } from '../../helpers/Tournament';
 import { hasFinishedRound } from '../../helpers/User';
 import { withUser } from '../../helpers/UserContext';
 import i18n from '../../i18n';
@@ -60,12 +59,12 @@ const Item = ({
                        {' '}
                        <SeedRolledBy round={round} />
                </p>
-               {isRunner(user, tournament) ?
+               {mayReportResult(user, tournament) ?
                        <p className="report">
                                <ReportButton
-                                       participant={findParticipant(tournament, user)}
                                        round={round}
                                        tournament={tournament}
+                                       user={user}
                                />
                        </p>
                : null}
index 66b425e83740e4c0b81cdf10efa6cc2368016c04..bb9b9284461ebe85ea7c4a818cb417c8c7df43f6 100644 (file)
@@ -21,6 +21,7 @@ import {
        getTournamentAdmins,
        getTournamentMonitors,
        hasRunners,
+       hasScoreboard,
        hasTournamentAdmins,
        hasTournamentMonitors,
 } from '../../helpers/Tournament';
@@ -65,15 +66,17 @@ const Detail = ({
        <Row>
                <Col lg={{ order: 2, span: 4 }} xl={{ order: 2, span: 3 }}>
                        <div className="tournament-sidebar">
-                               <div className="d-flex align-items-center justify-content-between">
-                                       <h2>{i18n.t('tournaments.scoreboard')}</h2>
-                                       {hasRunners(tournament) && tournament.rounds.length > 2 ?
-                                               <ScoreChartButton tournament={tournament} />
+                               {hasScoreboard(tournament) ? <>
+                                       <div className="d-flex align-items-center justify-content-between">
+                                               <h2>{i18n.t('tournaments.scoreboard')}</h2>
+                                               {hasRunners(tournament) && tournament.rounds.length > 2 ?
+                                                       <ScoreChartButton tournament={tournament} />
+                                               : null}
+                                       </div>
+                                       {hasRunners(tournament) ?
+                                               <Scoreboard tournament={tournament} />
                                        : null}
-                               </div>
-                               {hasRunners(tournament) ?
-                                       <Scoreboard tournament={tournament} />
-                               : null}
+                               </> : null}
                                {hasTournamentAdmins(tournament) ?
                                        <>
                                                <div className="d-flex align-items-center justify-content-between">
index af4d664efe0d2f60c435e169c9a000a395b39bd6..362cd6fbab36595da9754e1784eb4325911984d9 100644 (file)
@@ -9,6 +9,7 @@ import DiscordForm from './DiscordForm';
 import DiscordSelect from '../common/DiscordSelect';
 import Icon from '../common/Icon';
 import ToggleSwitch from '../common/ToggleSwitch';
+import Tournament from '../../helpers/Tournament';
 import i18n from '../../i18n';
 
 const open = async tournament => {
@@ -77,14 +78,16 @@ const SettingsDialog = ({
        <Modal.Body>
                <Row>
                        <Col sm={tournament.discord ? 6 : 12}>
-                               <div className="d-flex align-items-center justify-content-between mb-3">
-                                       <span>{i18n.t('tournaments.open')}</span>
-                                       <ToggleSwitch
-                                               onChange={({ target: { value } }) => value
-                                                       ? open(tournament) : close(tournament)}
-                                               value={tournament.accept_applications}
-                                       />
-                               </div>
+                               {Tournament.hasSignup(tournament) ?
+                                       <div className="d-flex align-items-center justify-content-between mb-3">
+                                               <span>{i18n.t('tournaments.open')}</span>
+                                               <ToggleSwitch
+                                                       onChange={({ target: { value } }) => value
+                                                               ? open(tournament) : close(tournament)}
+                                                       value={tournament.accept_applications}
+                                               />
+                                       </div>
+                               : null}
                                <div className="d-flex align-items-center justify-content-between mb-3">
                                        <span>{i18n.t('tournaments.locked')}</span>
                                        <ToggleSwitch
index f662defaa4d5f23bc476127b55a458ab6fc4a04e..c7bb811a9c581f80cea1224db8d7d1c35e8d87d8 100644 (file)
@@ -5,6 +5,7 @@ export const isComplete = (tournament, round) => {
        if (!tournament || !tournament.participants) return false;
        if (!round || !round.results) return false;
        const runners = Tournament.getRunners(tournament);
+       if (!runners.length) return false;
        for (let i = 0; i < runners.length; ++i) {
                const result = Participant.findResult(runners[i], round);
                if (!result || !result.has_finished) return false;
index 8f2239f1695c2a87515d5cf876784b1ef4b769f7..9d2962d27c5e3f4a63ee2a6cc58f526b068bff22 100644 (file)
@@ -30,6 +30,10 @@ export const getRunners = tournament => {
                .sort(Participant.compareUsername);
 };
 
+export const hasScoreboard = tournament => !!(tournament && tournament.type === 'signup-async');
+
+export const hasSignup = tournament => !!(tournament && tournament.type === 'signup-async');
+
 export const getScoreTable = tournament => {
        if (!tournament || !tournament.rounds || !tournament.rounds.length) return [];
        const runners = getRunners(tournament);
@@ -190,6 +194,8 @@ export default {
        getTournamentCrew,
        getTournamentMonitors,
        hasRunners,
+       hasScoreboard,
+       hasSignup,
        hasTournamentAdmins,
        hasTournamentCrew,
        hasTournamentMonitors,
index 4ce6935d83054ebb8bcb4e127166c53a0f181698..d7613256d8c2f8e2a569bd2f75514799a818cfb8 100644 (file)
@@ -61,6 +61,12 @@ export const mayApply = (user, tournament) =>
 export const mayHandleApplications = (user, tournament) =>
        tournament && tournament.accept_applications && isTournamentAdmin(user, tournament);
 
+export const mayReportResult = (user, tournament) => {
+       if (!user || !tournament) return false;
+       if (tournament.type === 'open-async') return true;
+       return isRunner(user, tournament);
+};
+
 export const mayLockRound = (user, tournament) =>
        !tournament.locked && isTournamentAdmin(user, tournament);
 
@@ -78,7 +84,6 @@ export const maySeeResults = (user, tournament, round) =>
        round.locked ||
        hasFinished(user, round) ||
        isTournamentMonitor(user, tournament) ||
-       (isTournamentAdmin(user, tournament) && !isRunner(user, tournament)) ||
        Round.isComplete(tournament, round);
 
 // Users