From: Daniel Karbach Date: Wed, 7 May 2025 18:40:41 +0000 (+0200) Subject: option to delete rounds as long as they have no results yes X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=6f4e4011864f6a261def5114f990916ed6c60047;p=alttp.git option to delete rounds as long as they have no results yes --- diff --git a/app/Http/Controllers/RoundController.php b/app/Http/Controllers/RoundController.php index 8ec2b84..050a1cf 100644 --- a/app/Http/Controllers/RoundController.php +++ b/app/Http/Controllers/RoundController.php @@ -39,6 +39,21 @@ class RoundController extends Controller return $round->toJson(); } + public function delete(Request $request, Round $round) { + $this->authorize('delete', $round); + if (count($round->results) > 0) { + return response('Forbidden', 403); + } + $round->load('tournament'); + $round->delete(); + Protocol::roundDeleted( + $round->tournament, + $round, + $request->user(), + ); + return $round->toJson(); + } + public function update(Request $request, Round $round) { $this->authorize('update', $round); diff --git a/app/Models/Protocol.php b/app/Models/Protocol.php index 60b25f2..9c902ce 100644 --- a/app/Models/Protocol.php +++ b/app/Models/Protocol.php @@ -93,6 +93,19 @@ class Protocol extends Model ProtocolAdded::dispatch($protocol); } + public static function roundDeleted(Tournament $tournament, Round $round, User $user) { + $protocol = static::create([ + 'tournament_id' => $tournament->id, + 'user_id' => $user->id, + 'type' => 'round.delete', + 'details' => [ + 'tournament' => static::tournamentMemo($tournament), + 'round' => static::roundMemo($round), + ], + ]); + ProtocolAdded::dispatch($protocol); + } + public static function roundEdited(Tournament $tournament, Round $round, User $user) { $protocol = static::create([ 'tournament_id' => $tournament->id, diff --git a/app/Models/Round.php b/app/Models/Round.php index 66cc271..fb78af1 100644 --- a/app/Models/Round.php +++ b/app/Models/Round.php @@ -2,17 +2,26 @@ namespace App\Models; +use Illuminate\Broadcasting\Channel; +use Illuminate\Database\Eloquent\BroadcastsEvents; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Round extends Model { + use BroadcastsEvents; use HasFactory; public function protocols() { return $this->tournament->protocols()->where('details->round->id', '=', $this->id); } + public function broadcastOn($event) { + return [ + new Channel('Tournament.'.$this->tournament_id), + ]; + } + public function isComplete() { if (count($this->tournament->participants) == 0) return false; diff --git a/app/Policies/RoundPolicy.php b/app/Policies/RoundPolicy.php index 645a03f..7f53e0f 100644 --- a/app/Policies/RoundPolicy.php +++ b/app/Policies/RoundPolicy.php @@ -65,7 +65,7 @@ class RoundPolicy */ public function delete(User $user, Round $round) { - return false; + return !$round->tournament->locked && $user->isTournamentAdmin($round->tournament) && count($round->results) == 0; } /** diff --git a/resources/js/components/protocol/Item.js b/resources/js/components/protocol/Item.js index aebd211..17dd744 100644 --- a/resources/js/components/protocol/Item.js +++ b/resources/js/components/protocol/Item.js @@ -67,6 +67,7 @@ const getEntryDescription = (entry, t) => { ; } case 'round.create': + case 'round.delete': case 'round.edit': case 'round.lock': case 'round.seed': @@ -99,6 +100,8 @@ const getEntryIcon = entry => { return ; case 'round.create': return ; + case 'round.delete': + return ; case 'round.lock': case 'tournament.close': case 'tournament.lock': diff --git a/resources/js/components/rounds/DeleteButton.js b/resources/js/components/rounds/DeleteButton.js new file mode 100644 index 0000000..578380d --- /dev/null +++ b/resources/js/components/rounds/DeleteButton.js @@ -0,0 +1,43 @@ +import PropTypes from 'prop-types'; +import React, { useState } from 'react'; +import { Button } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import DeleteDialog from './DeleteDialog'; +import Icon from '../common/Icon'; + +const DeleteButton = ({ + round, + tournament, +}) => { + const [showDialog, setShowDialog] = useState(false); + + const { t } = useTranslation(); + + return <> + setShowDialog(false)} + round={round} + show={showDialog} + tournament={tournament} + /> + + ; +}; + +DeleteButton.propTypes = { + round: PropTypes.shape({ + locked: PropTypes.bool, + }), + tournament: PropTypes.shape({ + }), +}; + +export default DeleteButton; diff --git a/resources/js/components/rounds/DeleteDialog.js b/resources/js/components/rounds/DeleteDialog.js new file mode 100644 index 0000000..188f082 --- /dev/null +++ b/resources/js/components/rounds/DeleteDialog.js @@ -0,0 +1,60 @@ +import axios from 'axios'; +import PropTypes from 'prop-types'; +import React from 'react'; +import { Alert, Button, Modal } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; +import toastr from 'toastr'; + +const EditDialog = ({ + onHide, + round, + show, +}) => { + const { t } = useTranslation(); + + const handleDelete = React.useCallback(async() => { + try { + await axios.delete(`/api/rounds/${round.id}`); + onHide(); + } catch (e) { + toastr.error(t('rounds.deleteError')); + } + }, [onHide, round, t]); + + return + + + {t('rounds.delete')} + + + + + {t('rounds.deleteConfirmMessage', { + ...round, + date: new Date(round.created_at), + })} + + + + + + + ; +}; + +EditDialog.propTypes = { + onHide: PropTypes.func, + round: PropTypes.shape({ + created_at: PropTypes.string, + id: PropTypes.number, + }), + show: PropTypes.bool, + tournament: PropTypes.shape({ + }), +}; + +export default EditDialog; diff --git a/resources/js/components/rounds/EditButton.js b/resources/js/components/rounds/EditButton.js index edc6fbf..d32b843 100644 --- a/resources/js/components/rounds/EditButton.js +++ b/resources/js/components/rounds/EditButton.js @@ -1,11 +1,10 @@ import PropTypes from 'prop-types'; import React, { useState } from 'react'; import { Button } from 'react-bootstrap'; -import { withTranslation } from 'react-i18next'; +import { useTranslation } from 'react-i18next'; import EditDialog from './EditDialog'; import Icon from '../common/Icon'; -import i18n from '../../i18n'; const EditButton = ({ round, @@ -13,6 +12,8 @@ const EditButton = ({ }) => { const [showDialog, setShowDialog] = useState(false); + const { t } = useTranslation(); + return <> setShowDialog(false)} @@ -23,7 +24,7 @@ const EditButton = ({