]> git.localhorst.tv Git - alttp.git/commitdiff
result comments
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 25 Mar 2022 14:06:25 +0000 (15:06 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 25 Mar 2022 14:06:25 +0000 (15:06 +0100)
15 files changed:
app/Http/Controllers/ResultController.php
app/Models/Protocol.php
app/Models/Result.php
app/Models/Round.php
app/Models/Tournament.php
database/migrations/2022_03_25_123528_result_comment.php [new file with mode: 0644]
resources/js/components/protocol/Item.js
resources/js/components/results/Item.js
resources/js/components/results/ReportButton.js
resources/js/components/results/ReportForm.js
resources/js/components/rounds/Item.js
resources/js/i18n/de.js
resources/js/i18n/en.js
resources/sass/form.scss
resources/sass/results.scss

index 5102c8aa01ebec790a2aa4deea0aacc90d32b574..ab1759d6faaf1bf97fb9c0d5d2c51014a8935d3a 100644 (file)
@@ -14,44 +14,54 @@ class ResultController extends Controller
 
        public function create(Request $request) {
                $validatedData = $request->validate([
 
        public function create(Request $request) {
                $validatedData = $request->validate([
+                       'comment' => 'string',
                        'forfeit' => 'boolean',
                        'participant_id' => 'required|exists:App\\Models\\Participant,id',
                        'round_id' => 'required|exists:App\\Models\\Round,id',
                        'forfeit' => 'boolean',
                        'participant_id' => 'required|exists:App\\Models\\Participant,id',
                        'round_id' => 'required|exists:App\\Models\\Round,id',
-                       'time' => 'required_if:forfeit,false|numeric',
+                       'time' => 'numeric',
                ]);
                ]);
-               error_log(var_export($validatedData, true));
 
                $participant = Participant::findOrFail($validatedData['participant_id']);
                $round = Round::findOrFail($validatedData['round_id']);
 
                $participant = Participant::findOrFail($validatedData['participant_id']);
                $round = Round::findOrFail($validatedData['round_id']);
-               if (!$round || $round->locked) {
-                       abort(403);
-               }
 
                $user = $request->user();
                if ($user->id != $participant->user->id) {
                        $this->authorize('create', Result::class);
                }
 
 
                $user = $request->user();
                if ($user->id != $participant->user->id) {
                        $this->authorize('create', Result::class);
                }
 
-               $result = Result::updateOrCreate([
+               $result = Result::firstOrCreate([
                        'round_id' => $validatedData['round_id'],
                        'user_id' => $participant->user_id,
                        'round_id' => $validatedData['round_id'],
                        'user_id' => $participant->user_id,
-               ], [
-                       'forfeit' => $validatedData['forfeit'],
-                       'time' => isset($validatedData['time']) ? $validatedData['time'] : 0,
                ]);
                ]);
+               if (!$round->locked) {
+                       if (isset($validatedData['forfeit'])) $result->forfeit = $validatedData['forfeit'];
+                       if (isset($validatedData['time'])) $result->time = $validatedData['time'];
+               }
+               $result->comment = $validatedData['comment'] ? $validatedData['comment'] : null;
+               $result->save();
+
                if ($result->wasChanged()) {
                        ResultChanged::dispatch($result);
                }
                if ($result->wasChanged()) {
                        ResultChanged::dispatch($result);
                }
+
+               if ($result->wasChanged(['forfeit', 'time'])) {
+                       Protocol::resultReported(
+                               $round->tournament,
+                               $result,
+                               $request->user(),
+                       );
+               } else if ($result->wasChanged('comment')) {
+                       Protocol::resultCommented(
+                               $round->tournament,
+                               $result,
+                               $request->user(),
+                       );
+               }
+
                $round->load('results');
                $round->updatePlacement();
                $round->tournament->updatePlacement();
 
                $round->load('results');
                $round->updatePlacement();
                $round->tournament->updatePlacement();
 
-               Protocol::resultReported(
-                       $round->tournament,
-                       $result,
-                       $request->user(),
-               );
-
                return $result->toJson();
        }
 
                return $result->toJson();
        }
 
index 4659665e87ffddeed7adcae097afcd0df81f3cd2..995f9c02ab8707f33825d88147ba0191efa54cad 100644 (file)
@@ -10,6 +10,20 @@ class Protocol extends Model
 {
        use HasFactory;
 
 {
        use HasFactory;
 
+       public static function resultCommented(Tournament $tournament, Result $result, User $user) {
+               $protocol = static::create([
+                       'tournament_id' => $tournament->id,
+                       'user_id' => $user->id,
+                       'type' => 'result.comment',
+                       'details' => [
+                               'tournament' => static::tournamentMemo($tournament),
+                               'result' => static::resultMemo($result),
+                               'round' => static::roundMemo($result->round),
+                       ],
+               ]);
+               ProtocolAdded::dispatch($protocol);
+       }
+
        public static function resultReported(Tournament $tournament, Result $result, User $user) {
                $protocol = static::create([
                        'tournament_id' => $tournament->id,
        public static function resultReported(Tournament $tournament, Result $result, User $user) {
                $protocol = static::create([
                        'tournament_id' => $tournament->id,
@@ -18,6 +32,7 @@ class Protocol extends Model
                        'details' => [
                                'tournament' => static::tournamentMemo($tournament),
                                'result' => static::resultMemo($result),
                        'details' => [
                                'tournament' => static::tournamentMemo($tournament),
                                'result' => static::resultMemo($result),
+                               'round' => static::roundMemo($result->round),
                        ],
                ]);
                ProtocolAdded::dispatch($protocol);
                        ],
                ]);
                ProtocolAdded::dispatch($protocol);
@@ -103,6 +118,7 @@ class Protocol extends Model
        protected static function resultMemo(Result $result) {
                return [
                        'id' => $result->id,
        protected static function resultMemo(Result $result) {
                return [
                        'id' => $result->id,
+                       'comment' => $result->comment,
                        'forfeit' => $result->forfeit,
                        'time' => $result->time,
                ];
                        'forfeit' => $result->forfeit,
                        'time' => $result->time,
                ];
@@ -111,6 +127,8 @@ class Protocol extends Model
        protected static function roundMemo(Round $round) {
                return [
                        'id' => $round->id,
        protected static function roundMemo(Round $round) {
                return [
                        'id' => $round->id,
+                       'locked' => $round->locked,
+                       'no_record' => $round->no_record,
                        'number' => $round->number,
                        'seed' => $round->seed,
                ];
                        'number' => $round->number,
                        'seed' => $round->seed,
                ];
@@ -119,6 +137,8 @@ class Protocol extends Model
        protected static function tournamentMemo(Tournament $tournament) {
                return [
                        'id' => $tournament->id,
        protected static function tournamentMemo(Tournament $tournament) {
                return [
                        'id' => $tournament->id,
+                       'locked' => $tournament->locked,
+                       'no_record' => $tournament->no_record,
                        'title' => $tournament->title,
                ];
        }
                        'title' => $tournament->title,
                ];
        }
index cdb86c77798fb2bccb204c740ee0ced94473bf71..308b9e9fcfd2e8935b25a46df87a39e4d991f8b5 100644 (file)
@@ -43,6 +43,11 @@ class Result extends Model
        }
 
 
        }
 
 
+       protected $casts = [
+               'forfeit' => 'boolean',
+               'time' => 'double',
+       ];
+
        protected $appends = [
                'has_finished',
        ];
        protected $appends = [
                'has_finished',
        ];
index 2232a0e2c95d675411de55c045e7f677217394d0..2c1a43963897563cfe2686245418dc5f9c149e38 100644 (file)
@@ -65,6 +65,7 @@ class Round extends Model
        protected $casts = [
                'code' => 'array',
                'locked' => 'boolean',
        protected $casts = [
                'code' => 'array',
                'locked' => 'boolean',
+               'no_record' => 'boolean',
        ];
 
        protected $fillable = [
        ];
 
        protected $fillable = [
index a38e9bde591a546975a402e361c4f420fa63be17..56ffe78f79c5dc1dc9b488f962b6c01f69bc6434 100644 (file)
@@ -73,4 +73,10 @@ class Tournament extends Model
                return $this->hasMany(Round::class)->orderBy('number', 'DESC');
        }
 
                return $this->hasMany(Round::class)->orderBy('number', 'DESC');
        }
 
+
+       protected $casts = [
+               'locked' => 'boolean',
+               'no_record' => 'boolean',
+       ];
+
 }
 }
diff --git a/database/migrations/2022_03_25_123528_result_comment.php b/database/migrations/2022_03_25_123528_result_comment.php
new file mode 100644 (file)
index 0000000..b5d5b05
--- /dev/null
@@ -0,0 +1,33 @@
+<?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('results', function(Blueprint $table) {
+                       $table->decimal('time', 6, 2)->unsigned()->default(0)->change();
+                       $table->text('comment')->nullable()->default(null);
+               });
+       }
+
+       /**
+        * Reverse the migrations.
+        *
+        * @return void
+        */
+       public function down()
+       {
+               Schema::table('results', function(Blueprint $table) {
+                       $table->dropColumn('comment');
+               });
+       }
+};
index 2eaae5b6c3cecf7700f533d79155de1974e04903..74bfa50039cf82ab62c3395d2e2683d2ec1072df 100644 (file)
@@ -44,6 +44,7 @@ const getEntryDescription = entry => {
                                        number: getEntryRoundNumber(entry),
                                },
                        );
                                        number: getEntryRoundNumber(entry),
                                },
                        );
+               case 'result.comment':
                case 'tournament.lock':
                        return i18n.t(
                                `protocol.description.${entry.type}`,
                case 'tournament.lock':
                        return i18n.t(
                                `protocol.description.${entry.type}`,
index 84395222407e1d50854e283a282a31002b73a328..0781ccaaa5b062b1f3993993349041d80d91ef9b 100644 (file)
@@ -41,6 +41,19 @@ const getTime = (result, maySee) => {
        return '?';
 };
 
        return '?';
 };
 
+const getClassName = result => {
+       const classNames = ['status'];
+       if (result && result.has_finished) {
+               classNames.push('finished');
+               if (result.comment) {
+                       classNames.push('has-comment');
+               }
+       } else {
+               classNames.push('pending');
+       }
+       return classNames.join(' ');
+};
+
 const Item = ({
        participant,
        round,
 const Item = ({
        participant,
        round,
@@ -51,7 +64,10 @@ const Item = ({
        const maySee = maySeeResults(user, tournament, round);
        return <div className="result">
                <Box user={participant.user} />
        const maySee = maySeeResults(user, tournament, round);
        return <div className="result">
                <Box user={participant.user} />
-               <div className={`status ${result && result.has_finished ? 'finished' : 'pending'}`}>
+               <div
+                       className={getClassName(result)}
+                       title={maySee && result && result.comment ? result.comment : null}
+               >
                        <span className="time">
                                {getTime(result, maySee)}
                        </span>
                        <span className="time">
                                {getTime(result, maySee)}
                        </span>
index d88c1a11285ee5ed72543f70f65421b5a6100fc2..0a8c139469db478b09fde94cdd342249dba56d46 100644 (file)
@@ -8,6 +8,23 @@ import Icon from '../common/Icon';
 import { findResult } from '../../helpers/Participant';
 import i18n from '../../i18n';
 
 import { findResult } from '../../helpers/Participant';
 import i18n from '../../i18n';
 
+const getButtonLabel = (participant, round) => {
+       const result = findResult(participant, round);
+       if (round.locked) {
+               if (result && result.comment) {
+                       return i18n.t('results.editComment');
+               } else {
+                       return i18n.t('results.addComment');
+               }
+       } else {
+               if (result && (result.time || result.forfeit)) {
+                       return i18n.t('results.edit');
+               } else {
+                       return i18n.t('results.report');
+               }
+       }
+};
+
 const ReportButton = ({ participant, round }) => {
        const [showDialog, setShowDialog] = useState(false);
 
 const ReportButton = ({ participant, round }) => {
        const [showDialog, setShowDialog] = useState(false);
 
@@ -16,7 +33,7 @@ const ReportButton = ({ participant, round }) => {
                        onClick={() => setShowDialog(true)}
                        variant="secondary"
                >
                        onClick={() => setShowDialog(true)}
                        variant="secondary"
                >
-                       {i18n.t(findResult(participant, round) ? 'results.edit' : 'results.report')}
+                       {getButtonLabel(participant, round)}
                        {' '}
                        <Icon.EDIT title="" />
                </Button>
                        {' '}
                        <Icon.EDIT title="" />
                </Button>
index 28a757a78ed9326a916aa6f3f24a18b3f7fedda6..942869da7123c36b160ef76368c3877f2032bbee 100644 (file)
@@ -19,47 +19,63 @@ const ReportForm = ({
        handleChange,
        handleSubmit,
        onCancel,
        handleChange,
        handleSubmit,
        onCancel,
+       round,
        touched,
        values,
 }) =>
 <Form noValidate onSubmit={handleSubmit}>
        <Modal.Body>
        touched,
        values,
 }) =>
 <Form noValidate onSubmit={handleSubmit}>
        <Modal.Body>
+               {!round.locked ?
+                       <Row>
+                               <Form.Group as={Col} sm={9} controlId="report.time">
+                                       <Form.Label>{i18n.t('results.reportTime')}</Form.Label>
+                                       <Form.Control
+                                               isInvalid={!!(touched.time && errors.time)}
+                                               name="time"
+                                               onBlur={handleBlur}
+                                               onChange={handleChange}
+                                               placeholder={values.forfeit ? 'DNF' : '1:22:59'}
+                                               type="text"
+                                               value={values.time || ''}
+                                       />
+                                       {touched.time && errors.time ?
+                                               <Form.Control.Feedback type="invalid">
+                                                       {i18n.t(errors.time)}
+                                               </Form.Control.Feedback>
+                                       :
+                                               <Form.Text muted>
+                                                       {parseTime(values.time) ?
+                                                               i18n.t(
+                                                                       'results.reportPreview',
+                                                                       { time: formatTime({ time: parseTime(values.time) })},
+                                                               )
+                                                       : null}
+                                               </Form.Text>
+                                       }
+                               </Form.Group>
+                               <Form.Group as={Col} sm={3} controlId="report.forfeit">
+                                       <Form.Label>{i18n.t('results.forfeit')}</Form.Label>
+                                       <Form.Control
+                                               as={LargeCheck}
+                                               isInvalid={!!(touched.forfeit && errors.forfeit)}
+                                               name="forfeit"
+                                               onBlur={handleBlur}
+                                               onChange={handleChange}
+                                               value={!!values.forfeit}
+                                       />
+                               </Form.Group>
+                       </Row>
+               : null}
                <Row>
                <Row>
-                       <Form.Group as={Col} sm={9} controlId="report.time">
-                               <Form.Label>{i18n.t('results.reportTime')}</Form.Label>
+                       <Form.Group as={Col} sm={12} controlId="report.comment">
+                               <Form.Label>{i18n.t('results.comment')}</Form.Label>
                                <Form.Control
                                <Form.Control
-                                       isInvalid={!!(touched.time && errors.time)}
-                                       name="time"
+                                       as="textarea"
+                                       isInvalid={!!(touched.comment && errors.comment)}
+                                       name="comment"
                                        onBlur={handleBlur}
                                        onChange={handleChange}
                                        onBlur={handleBlur}
                                        onChange={handleChange}
-                                       placeholder={values.forfeit ? 'DNF' : '1:22:59'}
-                                       type="text"
-                                       value={values.time || ''}
-                               />
-                               {touched.time && errors.time ?
-                                       <Form.Control.Feedback type="invalid">
-                                               {i18n.t(errors.time)}
-                                       </Form.Control.Feedback>
-                               :
-                                       <Form.Text muted>
-                                               {parseTime(values.time) ?
-                                                       i18n.t(
-                                                               'results.reportPreview',
-                                                               { time: formatTime({ time: parseTime(values.time) })},
-                                                       )
-                                               : null}
-                                       </Form.Text>
-                               }
-                       </Form.Group>
-                       <Form.Group as={Col} sm={3} controlId="report.forfeit">
-                               <Form.Label>{i18n.t('results.forfeit')}</Form.Label>
-                               <Form.Control
-                                       as={LargeCheck}
-                                       isInvalid={!!(touched.forfeit && errors.forfeit)}
-                                       name="forfeit"
-                                       onBlur={handleBlur}
-                                       onChange={handleChange}
-                                       value={!!values.forfeit}
+                                       value={values.comment || ''}
                                />
                        </Form.Group>
                </Row>
                                />
                        </Form.Group>
                </Row>
@@ -78,6 +94,7 @@ const ReportForm = ({
 
 ReportForm.propTypes = {
        errors: PropTypes.shape({
 
 ReportForm.propTypes = {
        errors: PropTypes.shape({
+               comment: PropTypes.string,
                forfeit: PropTypes.string,
                time: PropTypes.string,
        }),
                forfeit: PropTypes.string,
                time: PropTypes.string,
        }),
@@ -85,11 +102,16 @@ ReportForm.propTypes = {
        handleChange: PropTypes.func,
        handleSubmit: PropTypes.func,
        onCancel: PropTypes.func,
        handleChange: PropTypes.func,
        handleSubmit: PropTypes.func,
        onCancel: PropTypes.func,
+       round: PropTypes.shape({
+               locked: PropTypes.bool,
+       }),
        touched: PropTypes.shape({
        touched: PropTypes.shape({
+               comment: PropTypes.bool,
                forfeit: PropTypes.bool,
                time: PropTypes.bool,
        }),
        values: PropTypes.shape({
                forfeit: PropTypes.bool,
                time: PropTypes.bool,
        }),
        values: PropTypes.shape({
+               comment: PropTypes.string,
                forfeit: PropTypes.bool,
                time: PropTypes.string,
        }),
                forfeit: PropTypes.bool,
                time: PropTypes.string,
        }),
@@ -99,11 +121,12 @@ export default withFormik({
        displayName: 'ReportForm',
        enableReinitialize: true,
        handleSubmit: async (values, actions) => {
        displayName: 'ReportForm',
        enableReinitialize: true,
        handleSubmit: async (values, actions) => {
-               const { forfeit, participant_id, round_id, time } = values;
+               const { comment, forfeit, participant_id, round_id, time } = values;
                const { setErrors } = actions;
                const { onCancel } = actions.props;
                try {
                        await axios.post('/api/results', {
                const { setErrors } = actions;
                const { onCancel } = actions.props;
                try {
                        await axios.post('/api/results', {
+                               comment,
                                forfeit,
                                participant_id,
                                round_id,
                                forfeit,
                                participant_id,
                                round_id,
@@ -123,6 +146,7 @@ export default withFormik({
        mapPropsToValues: ({ participant, round }) => {
                const result = findResult(participant, round);
                return {
        mapPropsToValues: ({ participant, round }) => {
                const result = findResult(participant, round);
                return {
+                       comment: result && result.comment ? result.comment : '',
                        forfeit: result ? !!result.forfeit : false,
                        participant_id: participant.id,
                        round_id: round.id,
                        forfeit: result ? !!result.forfeit : false,
                        participant_id: participant.id,
                        round_id: round.id,
@@ -130,6 +154,7 @@ export default withFormik({
                };
        },
        validationSchema: yup.object().shape({
                };
        },
        validationSchema: yup.object().shape({
+               comment: yup.string(),
                forfeit: yup.boolean().required(),
                time: yup.string().time().when('forfeit', {
                        is: false,
                forfeit: yup.boolean().required(),
                time: yup.string().time().when('forfeit', {
                        is: false,
index f85492de8b1e6c9de0c838335e8a49bb9db22dc9..52b989d7a4aac04e484022103b6b6c86139570b4 100644 (file)
@@ -57,7 +57,7 @@ const Item = ({
                                tournament={tournament}
                        />
                </p>
                                tournament={tournament}
                        />
                </p>
-               {!round.locked && isParticipant(user, tournament) ?
+               {isParticipant(user, tournament) ?
                        <p className="report">
                                <ReportButton
                                        participant={findParticipant(tournament, user)}
                        <p className="report">
                                <ReportButton
                                        participant={findParticipant(tournament, user)}
index 40af4165f47fe60f9cf813954066522582c9d65e..9416ead6722c7f21809642c4ec0231fec1089b49 100644 (file)
@@ -124,6 +124,7 @@ export default {
                protocol: {
                        description: {
                                result: {
                protocol: {
                        description: {
                                result: {
+                                       comment: 'Ergebnis kommentiert: "{{details.result.comment}}"',
                                        report: 'Ergebnis von <0>{{time}}</0> eingetragen',
                                },
                                round: {
                                        report: 'Ergebnis von <0>{{time}}</0> eingetragen',
                                },
                                round: {
@@ -140,12 +141,15 @@ export default {
                        heading: 'Protokoll',
                },
                results: {
                        heading: 'Protokoll',
                },
                results: {
+                       addComment: 'Kommentieren',
+                       comment: 'Kommentar',
                        edit: 'Ergebnis ändern',
                        edit: 'Ergebnis ändern',
+                       editComment: 'Kommentar ändern',
                        forfeit: 'Aufgegeben',
                        report: 'Ergebnis eintragen',
                        reportError: 'Fehler beim Eintragen :(',
                        reportPreview: 'Wird als {{ time }} festgehalten',
                        forfeit: 'Aufgegeben',
                        report: 'Ergebnis eintragen',
                        reportError: 'Fehler beim Eintragen :(',
                        reportPreview: 'Wird als {{ time }} festgehalten',
-                       reportSuccess: 'Ergebnis festgehalten',
+                       reportSuccess: 'Festgehalten',
                        reportTime: 'Zeit',
                        time: 'Zeit: {{ time }}',
                },
                        reportTime: 'Zeit',
                        time: 'Zeit: {{ time }}',
                },
index 856aa56089c12af36a789e90b8e2c960746f48db..6aa5cc8802d4b132e0c6e8f3990e0546a90546c1 100644 (file)
@@ -124,6 +124,7 @@ export default {
                protocol: {
                        description: {
                                result: {
                protocol: {
                        description: {
                                result: {
+                                       comment: 'Result commented: "{{details.result.comment}}"',
                                        report: 'Result of {{time}} reported',
                                },
                                round: {
                                        report: 'Result of {{time}} reported',
                                },
                                round: {
@@ -140,12 +141,15 @@ export default {
                        heading: 'Protocol',
                },
                results: {
                        heading: 'Protocol',
                },
                results: {
+                       addComment: 'Comment',
+                       comment: 'Comment',
                        edit: 'Change result',
                        edit: 'Change result',
+                       editComment: 'Edit comment',
                        forfeit: 'Forfeit',
                        report: 'Report result',
                        forfeit: 'Forfeit',
                        report: 'Report result',
-                       reportError: 'Error saving result :(',
+                       reportError: 'Error saving :(',
                        reportPreview: 'Will be recorded as {{ time }}',
                        reportPreview: 'Will be recorded as {{ time }}',
-                       reportSuccess: 'Result stored, thanks :)',
+                       reportSuccess: 'Stored, thanks :)',
                        reportTime: 'Time',
                        time: 'Time: {{ time }}',
                },
                        reportTime: 'Time',
                        time: 'Time: {{ time }}',
                },
index b44ae8112dd0bc5a776405bb727d1ec2bd10b2ce..ee2ee5fd567b03e2b3b7dbd8632916cdf23f8088 100644 (file)
@@ -1,3 +1,7 @@
+label {
+       margin-top: 1ex;
+}
+
 .custom-check {
        display: table;
        width: auto;
 .custom-check {
        display: table;
        width: auto;
index b07fdeb505bc1e931db5b74bac566909de6f9f8a..d78ebc53b47a8560a32d978509dc9a0c2a1f4661 100644 (file)
                        background: $dark;
                        color: $light;
 
                        background: $dark;
                        color: $light;
 
+                       &.has-comment {
+                               box-shadow: 0 0.5ex 0 $info;
+                       }
+
                        .time {
                                min-width: 9ex;
                                height: 1.4em;
                        .time {
                                min-width: 9ex;
                                height: 1.4em;