$this->authorize('view', $user);
$user->load('participation');
$user->load('participation.tournament');
+ $user->loadCount('round_first');
+ $user->loadCount('round_second');
+ $user->loadCount('round_third');
+ $user->loadCount('tournament_first');
+ $user->loadCount('tournament_second');
+ $user->loadCount('tournament_third');
return $user->toJson();
}
return false;
}
+
public function participation() {
return $this->hasMany(Participant::class);
}
+ public function results() {
+ return $this->hasMany(Result::class);
+ }
+
+ public function rounds() {
+ return $this->belongsToMany(Round::class, 'results');
+ }
+
+ public function tournaments() {
+ return $this->belongsToMany(Tournament::class, 'participants');
+ }
+
+
+ public function round_first() {
+ return $this->rounds()
+ ->whereNot('locked')
+ ->wherePivot('placement', 1);
+ }
+
+ public function round_second() {
+ return $this->rounds()
+ ->whereNot('locked')
+ ->wherePivot('placement', 2);
+ }
+
+ public function round_third() {
+ return $this->rounds()
+ ->whereNot('locked')
+ ->wherePivot('placement', 3);
+ }
+
+ public function tournament_first() {
+ return $this->tournaments()
+ ->whereNot('locked')
+ ->wherePivot('placement', 1);
+ }
+
+ public function tournament_second() {
+ return $this->tournaments()
+ ->whereNot('locked')
+ ->wherePivot('placement', 2);
+ }
+
+ public function tournament_third() {
+ return $this->tournaments()
+ ->whereNot('locked')
+ ->wherePivot('placement', 3);
+ }
+
+
/**
* The attributes that are mass assignable.
*
import { withTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
+import Icon from '../common/Icon';
+import { isRunner } from '../../helpers/Participant';
import i18n from '../../i18n';
+const getIcon = participant => {
+ if (!isRunner(participant)) {
+ return '—';
+ }
+ if (participant.placement === 1) {
+ return <Icon.FIRST_PLACE className="text-gold" size="lg" />;
+ }
+ if (participant.placement === 2) {
+ return <Icon.SECOND_PLACE className="text-silver" size="lg" />;
+ }
+ if (participant.placement === 3) {
+ return <Icon.THIRD_PLACE className="text-bronze" size="lg" />;
+ }
+ return participant.placement;
+};
+
const Participation = ({ user }) => {
const navigate = useNavigate();
{i18n.t('users.participationEmpty')}
</Alert>;
}
- return <Table className="participation">
+ return <Table className="participation align-middle">
<thead>
<tr>
<th>{i18n.t('participants.tournament')}</th>
+ <th>{i18n.t('participants.placement')}</th>
<th>{i18n.t('participants.roles')}</th>
</tr>
</thead>
{p.tournament.title}
</Button>
</td>
+ <td>
+ {getIcon(p)}
+ {!p.tournament.locked && isRunner(p) ?
+ <span title={i18n.t('participants.placementSubjectToChange')}> *</span>
+ : null}
+ </td>
<td>
{p.roles ? p.roles.map((role, index) =>
<span key={role}>
import { withTranslation } from 'react-i18next';
import Box from './Box';
+import Records from './Records';
import EditStreamLinkButton from './EditStreamLinkButton';
import Participation from './Participation';
import Icon from '../common/Icon';
const Profile = ({ user }) => <Container>
<h1>{user.username}</h1>
<Row>
- <Col md={6}>
+ <Col md={6} className="mb-5">
<h2>{i18n.t('users.discordTag')}</h2>
<Box discriminator user={user} />
</Col>
- <Col md={6}>
+ <Col md={6} className="mb-5">
<h2>{i18n.t('users.streamLink')}</h2>
<p>
{user.stream_link ?
<EditStreamLinkButton user={user} />
</p>
</Col>
- <Col md={12}>
+ <Col md={6} className="mb-5">
+ <h2>{i18n.t('users.tournamentRecords')}</h2>
+ <Records
+ first={user.tournament_first_count}
+ second={user.tournament_second_count}
+ third={user.tournament_third_count}
+ />
+ </Col>
+ <Col md={6} className="mb-5">
+ <h2>{i18n.t('users.roundRecords')}</h2>
+ <Records
+ first={user.round_first_count}
+ second={user.round_second_count}
+ third={user.round_third_count}
+ />
+ </Col>
+ <Col md={12} className="mb-5">
<h2>{i18n.t('users.tournaments')}</h2>
<Participation user={user} />
</Col>
user: PropTypes.shape({
participation: PropTypes.arrayOf(PropTypes.shape({
})),
+ round_first_count: PropTypes.number,
+ round_second_count: PropTypes.number,
+ round_third_count: PropTypes.number,
stream_link: PropTypes.string,
+ tournament_first_count: PropTypes.number,
+ tournament_second_count: PropTypes.number,
+ tournament_third_count: PropTypes.number,
username: PropTypes.string,
}),
};
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Col, Row } from 'react-bootstrap';
+
+import Icon from '../common/Icon';
+
+const Records = ({
+ first,
+ second,
+ third,
+}) => <Row>
+ <Col>
+ <div className="record-box">
+ <span className="icon">
+ <Icon.FIRST_PLACE className="text-gold" />
+ </span>
+ <span className="count">
+ {first}
+ </span>
+ </div>
+ </Col>
+ <Col>
+ <div className="record-box">
+ <span className="icon">
+ <Icon.SECOND_PLACE className="text-silver" />
+ </span>
+ <span className="count">
+ {second}
+ </span>
+ </div>
+ </Col>
+ <Col>
+ <div className="record-box">
+ <span className="icon">
+ <Icon.THIRD_PLACE className="text-bronze" />
+ </span>
+ <span className="count">
+ {third}
+ </span>
+ </div>
+ </Col>
+</Row>;
+
+Records.propTypes = {
+ first: PropTypes.number,
+ second: PropTypes.number,
+ third: PropTypes.number,
+};
+
+export default Records;
participant: 'Teilnehmer',
placement: 'Platzierung',
placementShort: '#',
+ placementSubjectToChange: 'Das Turnier wurde noch nicht abgeschlossen, die Platzierung kann sich noch ändern.',
roleNames: {
admin: 'Administrator',
runner: 'Runner',
editStreamLink: 'Stream Link bearbeiten',
noStream: 'Kein Stream gesetzt',
participationEmpty: 'Hat noch an keinen Turnieren teilgenommen.',
+ roundRecords: 'Renn-Platzierungen',
setStreamLinkError: 'Konnte Stream Link nicht speichern',
setStreamLinkSuccess: 'Stream Link gespeichert',
stream: 'Stream',
streamLink: 'Stream Link',
tournaments: 'Turniere',
+ tournamentRecords: 'Turnier-Platzierungen',
},
validation: {
error: {
participant: 'Participant',
placement: 'Placement',
placementShort: '#',
+ placementSubjectToChange: 'Tournament incomplete, placement subject to change.',
roleNames: {
admin: 'Administrator',
runner: 'Runner',
editStreamLink: 'Edit stream link',
noStream: 'No stream set',
participationEmpty: 'Has not participated in any tourneys yet.',
+ roundRecords: 'Race records',
setStreamLinkError: 'Could not save stream link',
setStreamLinkSuccess: 'Stream link saved',
stream: 'Stream',
streamLink: 'Stream link',
tournaments: 'Tournaments',
+ tournamentRecords: 'Tournament records',
},
validation: {
error: {
+.record-box {
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+ border: thin solid $secondary;
+ border-radius: 0.5rem;
+ padding: 1rem;
+ font-size: 1.5rem;
+
+ .count {
+ margin-left: 0.5rem;
+ }
+}
+
.user-box {
padding: 0;
color: inherit;