]> git.localhorst.tv Git - alttp.git/commitdiff
show placements on user profile
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 25 Mar 2022 10:28:56 +0000 (11:28 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 25 Mar 2022 10:29:18 +0000 (11:29 +0100)
app/Http/Controllers/UserController.php
app/Models/User.php
resources/js/components/users/Participation.js
resources/js/components/users/Profile.js
resources/js/components/users/Records.js [new file with mode: 0644]
resources/js/i18n/de.js
resources/js/i18n/en.js
resources/sass/users.scss

index 8c451fc1328c3260ff6a9ed0c984b353536d917a..692c45862feb0e21186862476cf9ff5a76e31bc8 100644 (file)
@@ -43,6 +43,12 @@ class UserController extends Controller
                $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();
        }
 
index 4f56ac17f93b8af4ef2aff0c8478ee913ee19fd1..b4950c306c458fc5973f2a899f7021f93603f53d 100644 (file)
@@ -39,10 +39,61 @@ class User extends Authenticatable
                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.
         *
index d0d5d689658dc9f69608d1cf7ab41cae79d1f94a..b12adff897fdd964b6fd4af36bd487709031845e 100644 (file)
@@ -4,8 +4,26 @@ import { Alert, Button, Table } from 'react-bootstrap';
 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();
 
@@ -14,10 +32,11 @@ const Participation = ({ user }) => {
                        {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>
@@ -31,6 +50,12 @@ const Participation = ({ user }) => {
                                        {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}>
index 10c70dbf92bb4037a8ae2dbc96ab2fae22829c6d..e2fe2e790be5e3739a52da8e266d0212b8937ac8 100644 (file)
@@ -4,6 +4,7 @@ import { Button, Col, Container, Row } from 'react-bootstrap';
 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';
@@ -12,11 +13,11 @@ import i18n from '../../i18n';
 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 ?
@@ -36,7 +37,23 @@ const Profile = ({ user }) => <Container>
                                <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>
@@ -47,7 +64,13 @@ Profile.propTypes = {
        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,
        }),
 };
diff --git a/resources/js/components/users/Records.js b/resources/js/components/users/Records.js
new file mode 100644 (file)
index 0000000..5b110fe
--- /dev/null
@@ -0,0 +1,50 @@
+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;
index 9a7bb04c2a13d70e1545c65f432cd16545e5fbd0..7f2c713adec21faf7d69bf14c06582258e203227 100644 (file)
@@ -111,6 +111,7 @@ export default {
                        participant: 'Teilnehmer',
                        placement: 'Platzierung',
                        placementShort: '#',
+                       placementSubjectToChange: 'Das Turnier wurde noch nicht abgeschlossen, die Platzierung kann sich noch ändern.',
                        roleNames: {
                                admin: 'Administrator',
                                runner: 'Runner',
@@ -179,11 +180,13 @@ export default {
                        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: {
index 9f64901a257694f0a2352c3bc4aebf333bbda05e..e7907437eabc78742bf096ad16af5a7db3a687c0 100644 (file)
@@ -111,6 +111,7 @@ export default {
                        participant: 'Participant',
                        placement: 'Placement',
                        placementShort: '#',
+                       placementSubjectToChange: 'Tournament incomplete, placement subject to change.',
                        roleNames: {
                                admin: 'Administrator',
                                runner: 'Runner',
@@ -179,11 +180,13 @@ export default {
                        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: {
index c6f7509e47cb9ea63b29dfff8006a8a518960222..5847ae7aa4bb3401e5c8c2037b0e9fd17bcd11c8 100644 (file)
@@ -1,3 +1,17 @@
+.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;