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',
- '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']);
- if (!$round || $round->locked) {
- abort(403);
- }
$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,
- ], [
- '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(['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();
- Protocol::resultReported(
- $round->tournament,
- $result,
- $request->user(),
- );
-
return $result->toJson();
}
{
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,
'details' => [
'tournament' => static::tournamentMemo($tournament),
'result' => static::resultMemo($result),
+ 'round' => static::roundMemo($result->round),
],
]);
ProtocolAdded::dispatch($protocol);
protected static function resultMemo(Result $result) {
return [
'id' => $result->id,
+ 'comment' => $result->comment,
'forfeit' => $result->forfeit,
'time' => $result->time,
];
protected static function roundMemo(Round $round) {
return [
'id' => $round->id,
+ 'locked' => $round->locked,
+ 'no_record' => $round->no_record,
'number' => $round->number,
'seed' => $round->seed,
];
protected static function tournamentMemo(Tournament $tournament) {
return [
'id' => $tournament->id,
+ 'locked' => $tournament->locked,
+ 'no_record' => $tournament->no_record,
'title' => $tournament->title,
];
}
}
+ protected $casts = [
+ 'forfeit' => 'boolean',
+ 'time' => 'double',
+ ];
+
protected $appends = [
'has_finished',
];
protected $casts = [
'code' => 'array',
'locked' => 'boolean',
+ 'no_record' => 'boolean',
];
protected $fillable = [
return $this->hasMany(Round::class)->orderBy('number', 'DESC');
}
+
+ protected $casts = [
+ 'locked' => 'boolean',
+ 'no_record' => 'boolean',
+ ];
+
}
--- /dev/null
+<?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');
+ });
+ }
+};
number: getEntryRoundNumber(entry),
},
);
+ case 'result.comment':
case 'tournament.lock':
return i18n.t(
`protocol.description.${entry.type}`,
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 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>
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);
onClick={() => setShowDialog(true)}
variant="secondary"
>
- {i18n.t(findResult(participant, round) ? 'results.edit' : 'results.report')}
+ {getButtonLabel(participant, round)}
{' '}
<Icon.EDIT title="" />
</Button>
handleChange,
handleSubmit,
onCancel,
+ round,
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>
- <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
- isInvalid={!!(touched.time && errors.time)}
- name="time"
+ as="textarea"
+ isInvalid={!!(touched.comment && errors.comment)}
+ name="comment"
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>
ReportForm.propTypes = {
errors: PropTypes.shape({
+ comment: PropTypes.string,
forfeit: PropTypes.string,
time: PropTypes.string,
}),
handleChange: PropTypes.func,
handleSubmit: PropTypes.func,
onCancel: PropTypes.func,
+ round: PropTypes.shape({
+ locked: PropTypes.bool,
+ }),
touched: PropTypes.shape({
+ comment: PropTypes.bool,
forfeit: PropTypes.bool,
time: PropTypes.bool,
}),
values: PropTypes.shape({
+ comment: PropTypes.string,
forfeit: PropTypes.bool,
time: PropTypes.string,
}),
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', {
+ comment,
forfeit,
participant_id,
round_id,
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,
};
},
validationSchema: yup.object().shape({
+ comment: yup.string(),
forfeit: yup.boolean().required(),
time: yup.string().time().when('forfeit', {
is: false,
tournament={tournament}
/>
</p>
- {!round.locked && isParticipant(user, tournament) ?
+ {isParticipant(user, tournament) ?
<p className="report">
<ReportButton
participant={findParticipant(tournament, user)}
protocol: {
description: {
result: {
+ comment: 'Ergebnis kommentiert: "{{details.result.comment}}"',
report: 'Ergebnis von <0>{{time}}</0> eingetragen',
},
round: {
heading: 'Protokoll',
},
results: {
+ addComment: 'Kommentieren',
+ comment: 'Kommentar',
edit: 'Ergebnis ändern',
+ editComment: 'Kommentar ändern',
forfeit: 'Aufgegeben',
report: 'Ergebnis eintragen',
reportError: 'Fehler beim Eintragen :(',
reportPreview: 'Wird als {{ time }} festgehalten',
- reportSuccess: 'Ergebnis festgehalten',
+ reportSuccess: 'Festgehalten',
reportTime: 'Zeit',
time: 'Zeit: {{ time }}',
},
protocol: {
description: {
result: {
+ comment: 'Result commented: "{{details.result.comment}}"',
report: 'Result of {{time}} reported',
},
round: {
heading: 'Protocol',
},
results: {
+ addComment: 'Comment',
+ comment: 'Comment',
edit: 'Change result',
+ editComment: 'Edit comment',
forfeit: 'Forfeit',
report: 'Report result',
- reportError: 'Error saving result :(',
+ reportError: 'Error saving :(',
reportPreview: 'Will be recorded as {{ time }}',
- reportSuccess: 'Result stored, thanks :)',
+ reportSuccess: 'Stored, thanks :)',
reportTime: 'Time',
time: 'Time: {{ time }}',
},
+label {
+ margin-top: 1ex;
+}
+
.custom-check {
display: table;
width: auto;
background: $dark;
color: $light;
+ &.has-comment {
+ box-shadow: 0 0.5ex 0 $info;
+ }
+
.time {
min-width: 9ex;
height: 1.4em;