]> git.localhorst.tv Git - alttp.git/commitdiff
per result protocol
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Thu, 27 Nov 2025 11:27:15 +0000 (12:27 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Thu, 27 Nov 2025 11:27:15 +0000 (12:27 +0100)
app/Http/Controllers/ProtocolController.php
resources/js/components/groups/SwapButton.jsx
resources/js/components/protocol/ResultProtocol.jsx [new file with mode: 0644]
resources/js/components/results/Table.jsx
resources/js/components/results/TableRow.jsx
resources/js/i18n/de.js
resources/js/i18n/en.js
resources/sass/results.scss
routes/api.php

index c1077b01e0f715fd5ff1a3214526077a77cfea14..401e6232118a684458a515ba71be91001fa0e601 100644 (file)
@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
 
 use App\Models\Round;
 use App\Models\Tournament;
+use App\Models\User;
 
 class ProtocolController extends Controller
 {
@@ -19,6 +20,25 @@ class ProtocolController extends Controller
                return $protocol->values()->toJson();
        }
 
+       public function forRoundAndRunner(Tournament $tournament, Round $round, User $runner) {
+               $this->authorize('viewProtocol', $round->tournament);
+               $protocol = $round
+                       ->protocols()
+                       ->where(function ($query) use ($runner) {
+                               $query->where(function ($subquery) use ($runner) {
+                                       $subquery->where('user_id', '=', $runner->id);
+                                       $subquery->whereIn('type', ['result.comment', 'result.report', 'round.getseed']);
+                               });
+                               $query->orWhere('details->runner->id', '=', $runner->id);
+                               $query->orWhere('details->assignee->id', '=', $runner->id);
+                       })
+                       ->with('user')
+                       ->orderBy('created_at', 'desc')
+                       ->limit(150)
+                       ->get();
+               return $protocol->values()->toJson();
+       }
+
        public function forTournament(Tournament $tournament) {
                $this->authorize('viewProtocol', $tournament);
                $protocol = $tournament
index 0fbba01c312c61c7869e71d748fdb3c146ddb10c..35fcdf162995108b3bb4163555d51c615a6a2fae 100644 (file)
@@ -1,12 +1,15 @@
 import PropTypes from 'prop-types';
 import React from 'react';
 import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
 
 import Icon from '../common/Icon';
 
 const SwapButton = ({ onClick }) => {
-       return <Button onClick={onClick} variant="outline-secondary">
-               <Icon.SWAP />
+       const { t } = useTranslation();
+
+       return <Button onClick={onClick} title={t('groups.swapGroup')} variant="outline-secondary">
+               <Icon.SWAP title="" />
        </Button>;
 };
 
diff --git a/resources/js/components/protocol/ResultProtocol.jsx b/resources/js/components/protocol/ResultProtocol.jsx
new file mode 100644 (file)
index 0000000..6c545b3
--- /dev/null
@@ -0,0 +1,54 @@
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React, { useEffect, useState } from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Dialog from './Dialog';
+import Icon from '../common/Icon';
+
+const ResultProtocol = ({ roundId, tournamentId, userId }) => {
+       const [showDialog, setShowDialog] = useState(false);
+       const [protocol, setProtocol] = useState([]);
+
+       const { t } = useTranslation();
+
+       useEffect(() => {
+               if (!showDialog) return;
+               const ctrl = new AbortController();
+               axios
+                       .get(`/api/protocol/${tournamentId}/${roundId}/${userId}`, { signal: ctrl.signal })
+                       .then(response => {
+                               setProtocol(response.data);
+                       });
+               return () => {
+                       ctrl.abort();
+               };
+       }, [roundId, showDialog, tournamentId, userId]);
+
+       return (
+               <>
+                       <Button
+                               onClick={() => setShowDialog(true)}
+                               size="sm"
+                               title={t('button.protocol')}
+                               variant="outline-info"
+                       >
+                               <Icon.PROTOCOL title="" />
+                       </Button>
+                       <Dialog
+                               onHide={() => setShowDialog(false)}
+                               protocol={protocol}
+                               show={showDialog}
+                       />
+               </>
+       );
+};
+
+ResultProtocol.propTypes = {
+       roundId: PropTypes.number,
+       tournamentId: PropTypes.number,
+       userId: PropTypes.number,
+};
+
+export default ResultProtocol;
index 2a784f3475656658405f763d88e05175cfaa4f22..af3417cbc562d81e3eab80a782fc7b98f16ae704 100644 (file)
@@ -4,7 +4,8 @@ import { Table } from 'react-bootstrap';
 import { useTranslation } from 'react-i18next';
 
 import TableRow from './TableRow';
-import { maySeeResults } from '../../helpers/permissions';
+import Icon from '../common/Icon';
+import { maySeeResults, mayViewProtocol } from '../../helpers/permissions';
 import { compileResults } from '../../helpers/Round';
 import { useUser } from '../../hooks/user';
 
@@ -20,10 +21,17 @@ const ResultTable = ({ actions, round, tournament }) => {
                () => maySeeResults(user, tournament, round),
                [round, tournament, user],
        );
+       const mayProtocol = React.useMemo(
+               () => mayViewProtocol(user, tournament, round),
+               [round, tournament, user],
+       );
 
        return <Table className="result-table" striped hover>
                <thead>
                        <tr>
+                               {mayProtocol ?
+                                       <th className="result-protocol"><Icon.PROTOCOL /></th>
+                               : null}
                                <th className="result-runner">{t('results.runner')}</th>
                                {maySee ?
                                        <th className="result-placement">{t('results.placement')}</th>
@@ -39,6 +47,7 @@ const ResultTable = ({ actions, round, tournament }) => {
                                        key={result.id}
                                        round={round}
                                        showPlacement={maySee}
+                                       showProtocol={mayProtocol}
                                        tournament={tournament}
                                        user={result.user}
                                />
index c024bffe48845a93e618d1e1c505df205be46172..feefb548ec4bf025a38aca456dca20109cd32709 100644 (file)
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
 
 import Badge from './Badge';
 import VodLink from './VodLink';
+import ResultProtocol from '../protocol/ResultProtocol';
 import Box from '../users/Box';
 import { maySeeResult, mayVerifyResult } from '../../helpers/permissions';
 import { findResult } from '../../helpers/User';
@@ -13,6 +14,7 @@ const TableRow = ({
        actions,
        round,
        showPlacement = false,
+       showProtocol = false,
        tournament,
        user,
 }) => {
@@ -33,6 +35,17 @@ const TableRow = ({
        );
 
        return <tr>
+               {showProtocol ?
+                       <td className="result-protocol">
+                               {result ?
+                                       <ResultProtocol
+                                               roundId={result.round_id}
+                                               tournamentId={tournament.id}
+                                               userId={result.user_id}
+                                       />
+                               : null}
+                       </td>
+               : null}
                <td className="result-runner">
                        <Box user={user} />
                </td>
@@ -60,7 +73,9 @@ TableRow.propTypes = {
        round: PropTypes.shape({
        }),
        showPlacement: PropTypes.bool,
+       showProtocol: PropTypes.bool,
        tournament: PropTypes.shape({
+               id: PropTypes.number,
        }),
        user: PropTypes.shape({
        }),
index 8b3068a8b30112be6782740f54314f4408b00edc..b060a6a86d3ef78eabfdeb93e0c0a122e3a5f99c 100644 (file)
@@ -596,13 +596,13 @@ export default {
                                        verify: 'Ergebnis in Runde {{number}} von {{runner}} (<2>{{time}}</2>) verifiziert',
                                },
                                round: {
-                                       create: 'Runde #{{number}} hinzugefügt',
-                                       delete: 'Runde #{{number}} gelöscht',
-                                       edit: 'Runde #{{number}} bearbeitet',
-                                       getseed: 'Seed für Runde #{{number}} bezogen',
-                                       lock: 'Runde #{{number}} gesperrt',
-                                       seed: 'Seed für Runde #{{number}} eingetragen',
-                                       unlock: 'Runde #{{number}} entsperrt',
+                                       create: 'Runde {{number}} hinzugefügt',
+                                       delete: 'Runde {{number}} gelöscht',
+                                       edit: 'Runde {{number}} bearbeitet',
+                                       getseed: 'Seed für Runde {{number}} bezogen',
+                                       lock: 'Runde {{number}} gesperrt',
+                                       seed: 'Seed für Runde {{number}} eingetragen',
+                                       unlock: 'Runde {{number}} entsperrt',
                                },
                                tournament: {
                                        close: 'Anmeldung geschlossen',
index 8b978e61b5ec965c12c6ed51c8d049aee41423ae..2c2a3325d413a1c88f860c85c78ed40707fcd4d6 100644 (file)
@@ -596,13 +596,13 @@ export default {
                                        verify: 'Verified round {{number}} result of {{runner}} (<2>{{time}}</2>)',
                                },
                                round: {
-                                       create: 'Added round #{{number}}',
-                                       delete: 'Deleted round #{{number}}',
-                                       edit: 'Edited round #{{number}}',
-                                       getseed: 'Got seed for round #{{number}}',
-                                       lock: 'Round #{{number}} locked',
-                                       seed: 'Set seed for round #{{number}}',
-                                       unlock: 'Round #{{number}} unlocked',
+                                       create: 'Added round {{number}}',
+                                       delete: 'Deleted round {{number}}',
+                                       edit: 'Edited round {{number}}',
+                                       getseed: 'Got seed for round {{number}}',
+                                       lock: 'Round {{number}} locked',
+                                       seed: 'Set seed for round {{number}}',
+                                       unlock: 'Round {{number}} unlocked',
                                },
                                tournament: {
                                        close: 'Registration closed',
index e90bcca6903c7a127c450383062d3c0767ad8171..69a232ad3d5d1b5438a7c62c8f6476e0b82633ad 100644 (file)
 .result-table {
        width: 100%;
 
+       td {
+               vertical-align: middle;
+       }
+
+       .result-protocol {
+               text-align: center;
+               width: 5ex;
+       }
+
        .result-time,
        .result-vod {
                text-align: right;
index 88f19bffb11d22653234cf9a96dd59f653064173..5afc0efc98ffff629925f49a1b863e7a0a4ab262 100644 (file)
@@ -83,6 +83,7 @@ Route::get('pages/{type}/{name}', 'App\Http\Controllers\TechniqueController@byTy
 
 Route::get('protocol/{tournament}', 'App\Http\Controllers\ProtocolController@forTournament');
 Route::get('protocol/{tournament}/{round}', 'App\Http\Controllers\ProtocolController@forRound');
+Route::get('protocol/{tournament}/{round}/{runner}', 'App\Http\Controllers\ProtocolController@forRoundAndRunner');
 
 Route::post('results', 'App\Http\Controllers\ResultController@create');
 Route::post('results/{result}/modify', 'App\Http\Controllers\ResultController@modify');