From: Daniel Karbach Date: Wed, 7 May 2025 15:41:07 +0000 (+0200) Subject: add per-round protocol dialog X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=c445d96181ae4ad63121f4f5b2ede8de39c6f69e;p=alttp.git add per-round protocol dialog --- diff --git a/app/Http/Controllers/ProtocolController.php b/app/Http/Controllers/ProtocolController.php index 6b5e48c..c1077b0 100644 --- a/app/Http/Controllers/ProtocolController.php +++ b/app/Http/Controllers/ProtocolController.php @@ -2,12 +2,23 @@ namespace App\Http\Controllers; +use App\Models\Round; use App\Models\Tournament; -use Illuminate\Http\Request; class ProtocolController extends Controller { + public function forRound(Tournament $tournament, Round $round) { + $this->authorize('viewProtocol', $round->tournament); + $protocol = $round + ->protocols() + ->with('user') + ->orderBy('created_at', 'desc') + ->limit(150) + ->get(); + return $protocol->values()->toJson(); + } + public function forTournament(Tournament $tournament) { $this->authorize('viewProtocol', $tournament); $protocol = $tournament diff --git a/app/Models/Round.php b/app/Models/Round.php index e5ce703..66cc271 100644 --- a/app/Models/Round.php +++ b/app/Models/Round.php @@ -9,6 +9,10 @@ class Round extends Model { use HasFactory; + public function protocols() { + return $this->tournament->protocols()->where('details->round->id', '=', $this->id); + } + public function isComplete() { if (count($this->tournament->participants) == 0) return false; diff --git a/resources/js/components/protocol/Dialog.js b/resources/js/components/protocol/Dialog.js index 1b66c6c..dcf064b 100644 --- a/resources/js/components/protocol/Dialog.js +++ b/resources/js/components/protocol/Dialog.js @@ -1,53 +1,39 @@ import PropTypes from 'prop-types'; import React from 'react'; import { Alert, Button, Modal } from 'react-bootstrap'; -import { withTranslation } from 'react-i18next'; +import { useTranslation } from 'react-i18next'; import List from './List'; -import i18n from '../../i18n'; -class Dialog extends React.Component { - - componentDidMount() { - this.timer = setInterval(() => { - this.forceUpdate(); - }, 30000); - } - - componentWillUnmount() { - clearInterval(this.timer); - } - - render() { - const { - onHide, - protocol, - show, - } = this.props; - return - - - {i18n.t('protocol.heading')} - - - {protocol && protocol.length ? - - : - - - {i18n.t('protocol.empty')} - - - } - - - - ; - } - -} +const Dialog = ({ + onHide = null, + protocol = null, + show = false, +}) => { + const { t } = useTranslation(); + + return + + + {t('protocol.heading')} + + + {protocol && protocol.length ? + + : + + + {t('protocol.empty')} + + + } + + + + ; +}; Dialog.propTypes = { onHide: PropTypes.func, @@ -57,10 +43,4 @@ Dialog.propTypes = { show: PropTypes.bool, }; -Dialog.defaultProps = { - onHide: null, - protocol: null, - show: false, -}; - -export default withTranslation()(Dialog); +export default Dialog; diff --git a/resources/js/components/protocol/Item.js b/resources/js/components/protocol/Item.js index 7c3da40..aebd211 100644 --- a/resources/js/components/protocol/Item.js +++ b/resources/js/components/protocol/Item.js @@ -2,13 +2,12 @@ import moment from 'moment'; import PropTypes from 'prop-types'; import React from 'react'; import { ListGroup } from 'react-bootstrap'; -import { Trans, withTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import Icon from '../common/Icon'; import Spoiler from '../common/Spoiler'; import { formatTime } from '../../helpers/Result'; import { getUserName } from '../../helpers/User'; -import i18n from '../../i18n'; const getEntryDate = entry => { const dateStr = moment(entry.created_at).fromNow(); @@ -39,12 +38,12 @@ const getEntryResultTime = entry => { return formatTime(result); }; -const getEntryDescription = entry => { +const getEntryDescription = (entry, t) => { switch (entry.type) { case 'application.accepted': case 'application.received': case 'application.rejected': - return i18n.t( + return t( `protocol.description.${entry.type}`, { ...entry, @@ -72,7 +71,7 @@ const getEntryDescription = entry => { case 'round.lock': case 'round.seed': case 'round.unlock': - return i18n.t( + return t( `protocol.description.${entry.type}`, { ...entry, @@ -85,12 +84,12 @@ const getEntryDescription = entry => { case 'tournament.open': case 'tournament.settings': case 'tournament.unlock': - return i18n.t( + return t( `protocol.description.${entry.type}`, entry, ); default: - return i18n.t('protocol.description.unknown', entry); + return t('protocol.description.unknown', entry); } }; @@ -115,23 +114,40 @@ const getEntryIcon = entry => { } }; -const Item = ({ entry = {} }) => - +const Item = ({ entry = {} }) => { + const { t } = useTranslation(); + + const icon = React.useMemo(() => getEntryIcon(entry), [entry]); + const description = React.useMemo(() => getEntryDescription(entry, t), [entry, t]); + const [date, setDate] = React.useState(getEntryDate(entry)); + + React.useEffect(() => { + setDate(getEntryDate(entry)); + const timer = setInterval(() => { + setDate(getEntryDate(entry)); + }, 30_000); + return () => { + clearInterval(timer); + }; + }, [entry]); + + return
- {getEntryIcon(entry)} + {icon}
- {getEntryDescription(entry)} + {description}
- {getEntryDate(entry)} + {date}
; +}; Item.propTypes = { entry: PropTypes.shape({ @@ -139,4 +155,4 @@ Item.propTypes = { }), }; -export default withTranslation()(Item); +export default Item; diff --git a/resources/js/components/protocol/Protocol.js b/resources/js/components/protocol/Protocol.js index a2bb930..4b832c7 100644 --- a/resources/js/components/protocol/Protocol.js +++ b/resources/js/components/protocol/Protocol.js @@ -2,16 +2,17 @@ import axios from 'axios'; import PropTypes from 'prop-types'; import React, { useEffect, useState } from 'react'; import { Button } from 'react-bootstrap'; -import { withTranslation } from 'react-i18next'; +import { useTranslation } from 'react-i18next'; import Dialog from './Dialog'; import Icon from '../common/Icon'; -import i18n from '../../i18n'; const Protocol = ({ id }) => { const [showDialog, setShowDialog] = useState(false); const [protocol, setProtocol] = useState([]); + const { t } = useTranslation(); + useEffect(() => { const ctrl = new AbortController(); axios @@ -40,7 +41,7 @@ const Protocol = ({ id }) => { <> + setShowDialog(false)} + protocol={protocol} + show={showDialog} + /> + + ); +}; + +RoundProtocol.propTypes = { + roundId: PropTypes.number, + tournamentId: PropTypes.number, +}; + +export default RoundProtocol; diff --git a/resources/js/components/rounds/Item.js b/resources/js/components/rounds/Item.js index 0ef77c5..160fbbf 100644 --- a/resources/js/components/rounds/Item.js +++ b/resources/js/components/rounds/Item.js @@ -7,9 +7,15 @@ import LockButton from './LockButton'; import SeedButton from './SeedButton'; import SeedCode from './SeedCode'; import SeedRolledBy from './SeedRolledBy'; +import RoundProtocol from '../protocol/RoundProtocol'; import List from '../results/List'; import ReportButton from '../results/ReportButton'; -import { mayEditRound, mayReportResult, isRunner } from '../../helpers/permissions'; +import { + mayEditRound, + mayReportResult, + mayViewProtocol, + isRunner, +} from '../../helpers/permissions'; import { isComplete } from '../../helpers/Round'; import { hasFinishedRound } from '../../helpers/User'; import { useUser } from '../../hooks/user'; @@ -41,7 +47,7 @@ const Item = ({ const { t } = useTranslation(); const { user } = useUser(); -return
  • + return
  • {round.title ?

    {round.title}

    : null} @@ -82,6 +88,9 @@ return
  • {mayEditRound(user, tournament, round) ? : null} + {mayViewProtocol(user, tournament, round) ? + + : null} @@ -94,6 +103,7 @@ Item.propTypes = { code: PropTypes.arrayOf(PropTypes.string), created_at: PropTypes.string, game: PropTypes.string, + id: PropTypes.number, locked: PropTypes.bool, number: PropTypes.number, results: PropTypes.arrayOf(PropTypes.shape({ @@ -104,6 +114,7 @@ Item.propTypes = { tournament: PropTypes.shape({ participants: PropTypes.arrayOf(PropTypes.shape({ })), + id: PropTypes.number, show_numbers: PropTypes.bool, type: PropTypes.string, }), diff --git a/routes/api.php b/routes/api.php index 2818414..bc4b1fb 100644 --- a/routes/api.php +++ b/routes/api.php @@ -68,6 +68,7 @@ Route::get('pages/{type}', 'App\Http\Controllers\TechniqueController@byType'); Route::get('pages/{type}/{name}', 'App\Http\Controllers\TechniqueController@byTypeAndName'); Route::get('protocol/{tournament}', 'App\Http\Controllers\ProtocolController@forTournament'); +Route::get('protocol/{tournament}/{round}', 'App\Http\Controllers\ProtocolController@forRound'); Route::post('results', 'App\Http\Controllers\ResultController@create');