return $this->belongsTo(Participant::class);
}
+ public function getHasFinishedAttribute() {
+ return $this->time > 0;
+ }
+
+ protected $appends = [
+ 'has_finished',
+ ];
+
protected $fillable = [
'round_id',
'time',
Icon.DISCORD = makePreset('DiscordIcon', ['fab', 'discord']);
Icon.EDIT = makePreset('EditIcon', 'edit');
+Icon.FINISHED = makePreset('FinishedIcon', 'square-check');
Icon.LOGOUT = makePreset('LogoutIcon', 'sign-out-alt');
+Icon.PENDING = makePreset('PendingIcon', 'clock');
Icon.PROTOCOL = makePreset('ProtocolIcon', 'file-alt');
export default Icon;
import React from 'react';
import { withTranslation } from 'react-i18next';
+import Icon from '../common/Icon';
import Box from '../users/Box';
import { formatTime } from '../../helpers/Result';
import { findResult } from '../../helpers/Participant';
-import i18n from '../../i18n';
+import { maySeeResults } from '../../helpers/permissions';
+import { withUser } from '../../helpers/UserContext';
const Item = ({
participant,
round,
+ tournament,
+ user,
}) => {
const result = findResult(participant, round);
return (
<div className="result">
<Box user={participant.user} />
- <div>
- {result ?
- <span>{i18n.t('results.time', { time: formatTime(result) })}</span>
- : null}
+ <div className="status">
+ <span className="time">
+ {result && maySeeResults(user, tournament, round) ?
+ formatTime(result)
+ : null}
+ </span>
+ {result && result.has_finished ?
+ <Icon.FINISHED size="lg" />
+ :
+ <Icon.PENDING size="lg" />
+ }
</div>
</div>
);
}),
};
-export default withTranslation()(Item);
+export default withTranslation()(withUser(Item));
+export const isComplete = (tournament, round) => {
+ if (!tournament || !tournament.participants) return false;
+ if (!round || !round.results) return false;
+ return tournament.participants.length === round.results.length &&
+ round.results.filter(r => !r.has_finished).length === 0;
+};
+
export const patchResult = (round, result) => {
if (!round) return round;
if (!round.results || !round.results.length) {
};
export default {
+ isComplete,
patchResult,
};
/// NOTE: These permissions are for UI cosmetics only!
/// They should be in sync with the backend Policies.
+import Round from './Round';
+
export const isAdmin = user => user && user.role === 'admin';
export const isSameUser = (user, subject) => user && subject && user.id === subject.id;
user && tournament && tournament.participants &&
tournament.participants.find(p => p.user && p.user.id == user.id);
+export const hasFinished = (user, round) =>
+ user && round && round.results &&
+ round.results.find(r => r.user_id == user.id && r.has_finished);
+
export const mayAddRounds = (user, tournament) =>
isAdmin(user) || isParticipant(user, tournament);
export const mayViewProtocol = user =>
isAdmin(user);
+export const maySeeResults = (user, tournament, round) =>
+ isAdmin(user) || hasFinished(user, round) || Round.isComplete(tournament, round);
// Custom
@import 'common';
@import 'participants';
+@import 'results';
@import 'rounds';
@import 'users';
--- /dev/null
+.results {
+ .result {
+ padding: 1ex;
+
+ .status {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 1ex;
+ padding: 0.5em;
+ border-radius: 1ex;
+ background: $dark;
+ color: $light;
+
+ .time {
+ min-width: 9ex;
+ height: 1.4em;
+ background: $secondary;
+ border-radius: 0.5ex;
+ padding: 0 0.5ex;
+ }
+ }
+ }
+}
border: thin solid $secondary;
border-radius: 1ex;
padding: 1ex;
+
+ .info {
+ padding-right: 1rem;
+ }
}
}