From 812a270c7a410461e931394496512d36b34ef7b5 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Tue, 15 Mar 2022 17:07:35 +0100 Subject: [PATCH] add forfeit result --- app/Http/Controllers/ResultController.php | 7 +- app/Models/Protocol.php | 1 + app/Models/Result.php | 3 +- .../2022_03_15_081701_add_forfeit_result.php | 32 +++++++++ package.json | 4 +- resources/js/bootstrap.js | 4 +- resources/js/components/common/Icon.js | 3 +- resources/js/components/common/LargeCheck.js | 54 +++++++++++++++ resources/js/components/results/Item.js | 3 + resources/js/components/results/ReportForm.js | 69 ++++++++++++++----- resources/js/helpers/Participant.js | 15 +++- resources/js/helpers/Result.js | 2 +- resources/js/helpers/laravelErrorsToFormik.js | 7 ++ resources/js/i18n/de.js | 5 +- resources/js/schema/yup.js | 1 + resources/sass/app.scss | 1 + resources/sass/form.scss | 10 +++ 17 files changed, 194 insertions(+), 27 deletions(-) create mode 100644 database/migrations/2022_03_15_081701_add_forfeit_result.php create mode 100644 resources/js/components/common/LargeCheck.js create mode 100644 resources/js/helpers/laravelErrorsToFormik.js create mode 100644 resources/sass/form.scss diff --git a/app/Http/Controllers/ResultController.php b/app/Http/Controllers/ResultController.php index 37ebf3b..b23c907 100644 --- a/app/Http/Controllers/ResultController.php +++ b/app/Http/Controllers/ResultController.php @@ -14,10 +14,12 @@ class ResultController extends Controller public function create(Request $request) { $validatedData = $request->validate([ + 'forfeit' => 'boolean', 'participant_id' => 'required|exists:App\\Models\\Participant,id', 'round_id' => 'required|exists:App\\Models\\Round,id', - 'time' => 'required|numeric', + 'time' => 'required_if:forfeit,false|numeric', ]); + error_log(var_export($validatedData, true)); $participant = Participant::findOrFail($validatedData['participant_id']); $round = Round::findOrFail($validatedData['round_id']); @@ -31,7 +33,8 @@ class ResultController extends Controller 'round_id' => $validatedData['round_id'], 'user_id' => $participant->user_id, ], [ - 'time' => $validatedData['time'], + 'forfeit' => $validatedData['forfeit'], + 'time' => isset($validatedData['time']) ? $validatedData['time'] : 0, ]); Protocol::resultReported( diff --git a/app/Models/Protocol.php b/app/Models/Protocol.php index 8adc545..ddbc441 100644 --- a/app/Models/Protocol.php +++ b/app/Models/Protocol.php @@ -65,6 +65,7 @@ class Protocol extends Model protected static function resultMemo(Result $result) { return [ 'id' => $result->id, + 'forfeit' => $result->forfeit, 'time' => $result->time, ]; } diff --git a/app/Models/Result.php b/app/Models/Result.php index e495419..3ced393 100644 --- a/app/Models/Result.php +++ b/app/Models/Result.php @@ -18,7 +18,7 @@ class Result extends Model } public function getHasFinishedAttribute() { - return $this->time > 0; + return $this->time > 0 || $this->forfeit; } protected $appends = [ @@ -26,6 +26,7 @@ class Result extends Model ]; protected $fillable = [ + 'forfeit', 'round_id', 'time', 'user_id', diff --git a/database/migrations/2022_03_15_081701_add_forfeit_result.php b/database/migrations/2022_03_15_081701_add_forfeit_result.php new file mode 100644 index 0000000..82a8166 --- /dev/null +++ b/database/migrations/2022_03_15_081701_add_forfeit_result.php @@ -0,0 +1,32 @@ +boolean('forfeit')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('results', function(Blueprint $table) { + $table->dropColumn('forfeit'); + }); + } +}; diff --git a/package.json b/package.json index dc091e1..abdb0d5 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ }, "eslintConfig": { "env": { - "browser": true + "browser": true, + "node": true }, "extends": [ "eslint:recommended", @@ -23,6 +24,7 @@ "sourceType": "module" }, "rules": { + "import/no-named-as-default-member": 0, "max-len": [ "warn", { diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js index 453ad72..cffa4cb 100644 --- a/resources/js/bootstrap.js +++ b/resources/js/bootstrap.js @@ -2,7 +2,9 @@ window._ = require('lodash'); try { require('bootstrap'); -} catch (e) {} +} catch (e) { + // well... +} /** * We'll load the axios HTTP library which allows us to easily issue requests diff --git a/resources/js/components/common/Icon.js b/resources/js/components/common/Icon.js index 003adff..2ad4e51 100644 --- a/resources/js/components/common/Icon.js +++ b/resources/js/components/common/Icon.js @@ -1,6 +1,5 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faJsSquare } from '@fortawesome/free-brands-svg-icons'; import { fab } from '@fortawesome/free-brands-svg-icons'; import { fas } from '@fortawesome/free-solid-svg-icons'; import React from 'react'; @@ -9,7 +8,6 @@ import { withTranslation } from 'react-i18next'; import i18n from '../../i18n'; -library.add(faJsSquare); library.add(fab); library.add(fas); @@ -63,6 +61,7 @@ Icon.DISCORD = makePreset('DiscordIcon', ['fab', 'discord']); Icon.EDIT = makePreset('EditIcon', 'edit'); Icon.FINISHED = makePreset('FinishedIcon', 'square-check'); Icon.FIRST_PLACE = makePreset('FirstPlaceIcon', 'trophy'); +Icon.FORFEIT = makePreset('ForfeitIcon', 'square-xmark'); Icon.LOGOUT = makePreset('LogoutIcon', 'sign-out-alt'); Icon.PENDING = makePreset('PendingIcon', 'clock'); Icon.PROTOCOL = makePreset('ProtocolIcon', 'file-alt'); diff --git a/resources/js/components/common/LargeCheck.js b/resources/js/components/common/LargeCheck.js new file mode 100644 index 0000000..e159b45 --- /dev/null +++ b/resources/js/components/common/LargeCheck.js @@ -0,0 +1,54 @@ +import PropTypes from 'prop-types'; +import React from 'react'; + +import Icon from './Icon'; + +const LargeCheck = ({ + className, + id, + name, + onBlur, + onChange, + value, +}) => { + let clsn = className ? `${className} custom-check` : 'custom-check'; + if (value) { + clsn += ' checked'; + } + return onBlur({ target: { name, value } }) : null} + onClick={onChange ? () => onChange({ target: { name, value: !value } }) : null} + onKeyPress={onChange ? e => { + if (e.key == 'Enter' || e.key == ' ') { + e.preventDefault(); + e.stopPropagation(); + onChange({ target: { name, value: !value } }); + } + } : null} + tabIndex="0" + > + + ; +}; + +LargeCheck.propTypes = { + className: PropTypes.string, + id: PropTypes.string, + name: PropTypes.string, + onBlur: PropTypes.func, + onChange: PropTypes.func, + value: PropTypes.bool, +}; + +LargeCheck.defaultProps = { + className: '', + id: '', + name: '', + onBlur: null, + onChange: null, + value: false, +}; + +export default LargeCheck; diff --git a/resources/js/components/results/Item.js b/resources/js/components/results/Item.js index 2c1b656..43ea91d 100644 --- a/resources/js/components/results/Item.js +++ b/resources/js/components/results/Item.js @@ -13,6 +13,9 @@ const getIcon = (result, index) => { if (!result || !result.has_finished) { return ; } + if (result.forfeit) { + return ; + } if (index === 0) { return ; } diff --git a/resources/js/components/results/ReportForm.js b/resources/js/components/results/ReportForm.js index fc98c7e..e7d1d9d 100644 --- a/resources/js/components/results/ReportForm.js +++ b/resources/js/components/results/ReportForm.js @@ -4,8 +4,12 @@ import PropTypes from 'prop-types'; import React from 'react'; import { Button, Col, Form, Modal, Row } from 'react-bootstrap'; import { withTranslation } from 'react-i18next'; +import toastr from 'toastr'; +import LargeCheck from '../common/LargeCheck'; import i18n from '../../i18n'; +import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik'; +import { findResult } from '../../helpers/Participant'; import { formatTime, parseTime } from '../../helpers/Result'; import yup from '../../schema/yup'; @@ -21,14 +25,14 @@ const ReportForm = ({
- + {i18n.t('results.reportTime')} @@ -47,6 +51,17 @@ const ReportForm = ({ } + + {i18n.t('results.forfeit')} + + @@ -63,6 +78,7 @@ const ReportForm = ({ ReportForm.propTypes = { errors: PropTypes.shape({ + forfeit: PropTypes.string, time: PropTypes.string, }), handleBlur: PropTypes.func, @@ -70,9 +86,11 @@ ReportForm.propTypes = { handleSubmit: PropTypes.func, onCancel: PropTypes.func, touched: PropTypes.shape({ + forfeit: PropTypes.bool, time: PropTypes.bool, }), values: PropTypes.shape({ + forfeit: PropTypes.bool, time: PropTypes.string, }), }; @@ -81,23 +99,42 @@ export default withFormik({ displayName: 'ReportForm', enableReinitialize: true, handleSubmit: async (values, actions) => { - const { participant_id, round_id, time } = values; + const { forfeit, participant_id, round_id, time } = values; + const { setErrors } = actions; const { onCancel } = actions.props; - await axios.post('/api/results', { - participant_id, - round_id, - time: parseTime(time), - }); - if (onCancel) { - onCancel(); + try { + await axios.post('/api/results', { + forfeit, + participant_id, + round_id, + time: parseTime(time) || 0, + }); + toastr.success(i18n.t('results.reportSuccess')); + if (onCancel) { + onCancel(); + } + } catch (e) { + toastr.success(i18n.t('results.reportError')); + if (e.response && e.response.data && e.response.data.errors) { + setErrors(laravelErrorsToFormik(e.response.data.errors)); + } } }, - mapPropsToValues: ({ participant, round }) => ({ - participant_id: participant.id, - round_id: round.id, - time: '', - }), + mapPropsToValues: ({ participant, round }) => { + const result = findResult(participant, round); + console.log(result); + return { + forfeit: result ? !!result.forfeit : false, + participant_id: participant.id, + round_id: round.id, + time: result && result.time ? formatTime(result) : '', + }; + }, validationSchema: yup.object().shape({ - time: yup.string().required().time(), + forfeit: yup.boolean().required(), + time: yup.string().time().when('forfeit', { + is: false, + then: yup.string().required().time(), + }), }), })(withTranslation()(ReportForm)); diff --git a/resources/js/helpers/Participant.js b/resources/js/helpers/Participant.js index e5a135f..3db4121 100644 --- a/resources/js/helpers/Participant.js +++ b/resources/js/helpers/Participant.js @@ -1,8 +1,8 @@ export const compareResult = round => (a, b) => { const a_result = findResult(a, round); const b_result = findResult(b, round); - const a_time = a_result ? a_result.time : 0; - const b_time = b_result ? b_result.time : 0; + const a_time = a_result && !a_result.forfeit ? a_result.time : 0; + const b_time = b_result && !b_result.forfeit ? b_result.time : 0; if (a_time) { if (b_time) { if (a_time < b_time) return -1; @@ -14,6 +14,17 @@ export const compareResult = round => (a, b) => { if (b_time) { return 1; } + const a_forfeit = a_result && a_result.forfeit; + const b_forfeit = b_result && b_result.forfeit; + if (a_forfeit) { + if (b_forfeit) { + return 0; + } + return -1; + } + if (b_forfeit) { + return 1; + } return 0; }; diff --git a/resources/js/helpers/Result.js b/resources/js/helpers/Result.js index 4de0aae..06ade09 100644 --- a/resources/js/helpers/Result.js +++ b/resources/js/helpers/Result.js @@ -13,7 +13,7 @@ export const formatTime = result => { export const parseTime = str => { if (!str) return null; - return `${str}`.split(/[-\.: ]+/).reduce((acc,time) => (60 * acc) + +time, 0); + return `${str}`.split(/[-.: ]+/).reduce((acc,time) => (60 * acc) + +time, 0); }; export default { diff --git a/resources/js/helpers/laravelErrorsToFormik.js b/resources/js/helpers/laravelErrorsToFormik.js new file mode 100644 index 0000000..477627d --- /dev/null +++ b/resources/js/helpers/laravelErrorsToFormik.js @@ -0,0 +1,7 @@ +const laravelErrorsToFormik = errors => + Object.keys(errors || {}).reduce((result, key) => ({ + ...result, + [key]: errors[key].join(', '), + }), {}); + +export default laravelErrorsToFormik; diff --git a/resources/js/i18n/de.js b/resources/js/i18n/de.js index c525000..7fbcf66 100644 --- a/resources/js/i18n/de.js +++ b/resources/js/i18n/de.js @@ -47,9 +47,12 @@ export default { }, results: { edit: 'Ergebnis ändern', + forfeit: 'Aufgegeben', report: 'Ergebnis eintragen', - reportTime: 'Zeit', + reportError: 'Fehler beim Eintragen :(', reportPreview: 'Wird als {{ time }} festgehalten', + reportSuccess: 'Ergebnis festgehalten', + reportTime: 'Zeit', time: 'Zeit: {{ time }}', }, rounds: { diff --git a/resources/js/schema/yup.js b/resources/js/schema/yup.js index 19f8b58..bc506bc 100644 --- a/resources/js/schema/yup.js +++ b/resources/js/schema/yup.js @@ -6,6 +6,7 @@ yup.addMethod(yup.string, 'time', function (errorMessage) { return this.test('test-time-format', errorMessage, function (value) { const { path, createError } = this; return ( + !value || parseTime(value) || createError({ path, message: errorMessage || 'validation.error.time' }) ); diff --git a/resources/sass/app.scss b/resources/sass/app.scss index cbb7c67..b19f13e 100644 --- a/resources/sass/app.scss +++ b/resources/sass/app.scss @@ -12,6 +12,7 @@ // Custom @import 'common'; +@import 'form'; @import 'participants'; @import 'results'; @import 'rounds'; diff --git a/resources/sass/form.scss b/resources/sass/form.scss new file mode 100644 index 0000000..b44ae81 --- /dev/null +++ b/resources/sass/form.scss @@ -0,0 +1,10 @@ +.custom-check { + display: table; + width: auto; + > * { + visibility: hidden; + } + &.checked > * { + visibility: visible; + } +} -- 2.39.2