Merge pull request #39 from turniere/ticket/TURNIERE-119
Create group view (Ticket/turniere 119)
This commit is contained in:
commit
40ccc87d9d
|
|
@ -0,0 +1,100 @@
|
||||||
|
import {Button, Card, CardBody, Col, Collapse, Row, Table} from 'reactstrap';
|
||||||
|
import {Match} from './Match';
|
||||||
|
import React, {Component} from 'react';
|
||||||
|
import {getGroup} from '../redux/tournamentApi';
|
||||||
|
import {notify} from 'react-notify-toast';
|
||||||
|
|
||||||
|
export default class GroupStage extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {showMatches: this.props.showMatches};
|
||||||
|
this.toggleShowMatches = this.toggleShowMatches.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleShowMatches() {
|
||||||
|
this.setState({showMatches: !this.state.showMatches});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (<div className='py-5 px-5'>
|
||||||
|
<h1 className='custom-font'>
|
||||||
|
Gruppenphase
|
||||||
|
<ShowMatchesToggleButton show={this.state.showMatches} toggle={this.toggleShowMatches}/>
|
||||||
|
</h1>
|
||||||
|
<Row className='mt-3'>
|
||||||
|
{this.props.groups.map(group => <Group group={group} key={group.id} isSignedIn={this.props.isSignedIn}
|
||||||
|
isOwner={this.props.isOwner} showMatches={this.state.showMatches}/>)}
|
||||||
|
</Row>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ShowMatchesToggleButton(props) {
|
||||||
|
return (<Button onClick={props.toggle} className='float-right default-font-family'>
|
||||||
|
{props.show ? 'Spiele ausblenden' : 'Spiele anzeigen'}
|
||||||
|
</Button>);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Group extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = props.group;
|
||||||
|
this.reload = this.reload.bind(this);
|
||||||
|
this.onReloadSuccess = this.onReloadSuccess.bind(this);
|
||||||
|
this.onReloadError = this.onReloadError.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
reload() {
|
||||||
|
getGroup(this.state.id, this.onReloadSuccess, this.onReloadError);
|
||||||
|
}
|
||||||
|
|
||||||
|
onReloadSuccess(status, updatedGroup) {
|
||||||
|
this.setState(updatedGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
onReloadError() {
|
||||||
|
notify.show('Die Gruppe konnte nicht aktualisiert werden.', 'warning', 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (<Col className='minw-25'>
|
||||||
|
<Card>
|
||||||
|
<CardBody>
|
||||||
|
<h3 className='custom-font'>Gruppe {this.state.number}</h3>
|
||||||
|
<Collapse isOpen={this.props.showMatches}>
|
||||||
|
{this.state.matches.map((match => (
|
||||||
|
<Match match={match} isSignedIn={this.props.isSignedIn} isOwner={this.props.isOwner}
|
||||||
|
onChange={this.reload} key={match.id}/>)))}
|
||||||
|
</Collapse>
|
||||||
|
<GroupScoresTable scores={this.state.scores}/>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</Col>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function GroupScoresTable(props) {
|
||||||
|
return (<Table className='mt-4' striped size='sm' responsive>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Team</th>
|
||||||
|
<th>Punkte</th>
|
||||||
|
<th>erzielt</th>
|
||||||
|
<th>kassiert</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{props.scores.map(groupScore => <GroupScoresTableRow score={groupScore} key={groupScore.id}/>)}
|
||||||
|
</tbody>
|
||||||
|
</Table>);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GroupScoresTableRow(props) {
|
||||||
|
return (<tr>
|
||||||
|
<td>{props.score.team.name}</td>
|
||||||
|
<td>{props.score.group_points}</td>
|
||||||
|
<td>{props.score.received_points}</td>
|
||||||
|
<td>{props.score.scored_points}</td>
|
||||||
|
</tr>);
|
||||||
|
}
|
||||||
|
|
@ -70,6 +70,7 @@ export class Match extends React.Component {
|
||||||
updatedMatch.team1.score = scoreTeam1;
|
updatedMatch.team1.score = scoreTeam1;
|
||||||
updatedMatch.team2.score = scoreTeam2;
|
updatedMatch.team2.score = scoreTeam2;
|
||||||
this.setState({match: updatedMatch});
|
this.setState({match: updatedMatch});
|
||||||
|
this.props.onChange !== undefined && this.props.onChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
getMatchFinishedMessage() {
|
getMatchFinishedMessage() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import {Stage} from './Stage';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export function PlayoffStages(props) {
|
||||||
|
return (<div>
|
||||||
|
{props.playoffStages.map(stage => <Stage isSignedIn={props.isSignedIn} isOwner={props.isOwner}
|
||||||
|
level={getLevelName(stage.level)} matches={stage.matches} key={stage.level}/>)}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLevelName(levelNumber) {
|
||||||
|
const names = ['Finale', 'Halbfinale', 'Viertelfinale', 'Achtelfinale'];
|
||||||
|
if (levelNumber < names.length) {
|
||||||
|
return names[levelNumber];
|
||||||
|
} else {
|
||||||
|
return Math.pow(2, levelNumber) + 'tel-Finale';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import {Col, Container, Row} from 'reactstrap';
|
||||||
|
import {Match} from './Match';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export function Stage(props) {
|
||||||
|
const {isSignedIn, isOwner} = props;
|
||||||
|
|
||||||
|
return (<div>
|
||||||
|
<Container className='py-5'>
|
||||||
|
<h1 className='custom-font'>{props.level}</h1>
|
||||||
|
<Row>
|
||||||
|
{props.matches.map((match => (
|
||||||
|
<Col className='minw-25' key={match.id}><Match match={match} isSignedIn={isSignedIn}
|
||||||
|
isOwner={isOwner}/></Col>)))}
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
import {getRequest} from './backendApi';
|
||||||
|
import {getState} from '../api';
|
||||||
|
|
||||||
|
export function getTournament(code, successCallback, errorCallback) {
|
||||||
|
getRequest(getState(), '/tournaments/' + code)
|
||||||
|
.then(response => {
|
||||||
|
successCallback(response.status, convertTournament(response.data));
|
||||||
|
})
|
||||||
|
.catch(errorCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGroup(groupId, successCallback, errorCallback) {
|
||||||
|
getRequest(getState(), '/groups/' + groupId)
|
||||||
|
.then(response => {
|
||||||
|
successCallback(response.status, convertGroup(response.data));
|
||||||
|
})
|
||||||
|
.catch(errorCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertTournament(apiTournament) {
|
||||||
|
let groupStage = null;
|
||||||
|
const playoffStages = [];
|
||||||
|
for (const stage of apiTournament.stages) {
|
||||||
|
if (stage.groups.length > 0) {
|
||||||
|
// group stage
|
||||||
|
groupStage = {groups: stage.groups.map(group => convertGroup(group))};
|
||||||
|
} else {
|
||||||
|
// playoff stage
|
||||||
|
playoffStages.push({
|
||||||
|
id: stage.id, level: stage.level, matches: stage.matches.map(match => convertMatch(match, false))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: apiTournament.id,
|
||||||
|
code: apiTournament.code,
|
||||||
|
description: apiTournament.description,
|
||||||
|
name: apiTournament.name,
|
||||||
|
isPublic: apiTournament.public,
|
||||||
|
ownerUsername: apiTournament.owner_username,
|
||||||
|
groupStage: groupStage,
|
||||||
|
playoffStages: playoffStages
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertGroup(apiGroup) {
|
||||||
|
return {
|
||||||
|
id: apiGroup.id,
|
||||||
|
number: apiGroup.number,
|
||||||
|
scores: apiGroup.group_scores,
|
||||||
|
matches: apiGroup.matches.map(match => convertMatch(match, true))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertMatch(apiMatch, allowUndecided) {
|
||||||
|
const result = {
|
||||||
|
id: apiMatch.id, state: apiMatch.state, allowUndecided: allowUndecided,
|
||||||
|
winnerTeamId: apiMatch.winner === null ? null : apiMatch.winner.id
|
||||||
|
};
|
||||||
|
|
||||||
|
if (apiMatch.match_scores.length === 2) {
|
||||||
|
result.team1 = {
|
||||||
|
name: apiMatch.match_scores[0].team.name,
|
||||||
|
id: apiMatch.match_scores[0].team.id,
|
||||||
|
score: apiMatch.match_scores[0].points,
|
||||||
|
scoreId: apiMatch.match_scores[0].id
|
||||||
|
};
|
||||||
|
result.team2 = {
|
||||||
|
name: apiMatch.match_scores[1].team.name,
|
||||||
|
id: apiMatch.match_scores[1].team.id,
|
||||||
|
score: apiMatch.match_scores[1].points,
|
||||||
|
scoreId: apiMatch.match_scores[1].id
|
||||||
|
};
|
||||||
|
} else if (apiMatch.match_scores.length === 1) {
|
||||||
|
result.team1 = {
|
||||||
|
name: apiMatch.match_scores[0].team.name,
|
||||||
|
id: apiMatch.match_scores[0].team.id,
|
||||||
|
score: apiMatch.match_scores[0].points,
|
||||||
|
scoreId: apiMatch.match_scores[0].id
|
||||||
|
};
|
||||||
|
result.team2 = {
|
||||||
|
name: 'TBD',
|
||||||
|
id: null,
|
||||||
|
score: 0
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
result.team1 = {
|
||||||
|
name: 'TBD',
|
||||||
|
id: null,
|
||||||
|
score: 0
|
||||||
|
};
|
||||||
|
result.team2 = {
|
||||||
|
name: 'TBD',
|
||||||
|
id: null,
|
||||||
|
score: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
@ -1,25 +1,26 @@
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import {Col, Container, ListGroup, ListGroupItem, Row} from 'reactstrap';
|
import {Container, ListGroup, ListGroupItem} from 'reactstrap';
|
||||||
|
|
||||||
import {ErrorPageComponent} from '../js/components/ErrorComponents';
|
import {ErrorPageComponent} from '../js/components/ErrorComponents';
|
||||||
import {Footer} from '../js/components/Footer';
|
import {Footer} from '../js/components/Footer';
|
||||||
import {TurniereNavigation} from '../js/components/Navigation';
|
import {TurniereNavigation} from '../js/components/Navigation';
|
||||||
import {BigImage} from '../js/components/BigImage';
|
import {BigImage} from '../js/components/BigImage';
|
||||||
import {getState} from '../js/api';
|
|
||||||
import {getRequest} from '../js/redux/backendApi';
|
|
||||||
|
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
|
||||||
import '../static/css/everypage.css';
|
import '../static/css/everypage.css';
|
||||||
import '../static/css/tournament.css';
|
import '../static/css/tournament.css';
|
||||||
import {Match} from '../js/components/Match';
|
import {getTournament} from '../js/redux/tournamentApi';
|
||||||
|
import {PlayoffStages} from '../js/components/PlayoffStages';
|
||||||
|
import GroupStage from '../js/components/GroupStage';
|
||||||
|
|
||||||
class PrivateTournamentPage extends React.Component {
|
class PrivateTournamentPage extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const {id, description, isPublic, code, ownerUsername, playoffStages} = this.props.tournament;
|
const {id, description, isPublic, code, ownerUsername, playoffStages, groupStage} = this.props.tournament;
|
||||||
const {isSignedIn, username} = this.props;
|
const {isSignedIn, username} = this.props;
|
||||||
|
const isOwner = username === ownerUsername;
|
||||||
|
|
||||||
// TODO: Change href-prop of the anchor tag to contain the tournament code
|
// TODO: Change href-prop of the anchor tag to contain the tournament code
|
||||||
return (<div className='pb-5'>
|
return (<div className='pb-5'>
|
||||||
|
|
@ -35,9 +36,11 @@ class PrivateTournamentPage extends React.Component {
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
</Container>
|
</Container>
|
||||||
<div className='stages pt-5'>
|
<div className='stages pt-5'>
|
||||||
{playoffStages.map(stage => <Stage isSignedIn={isSignedIn} isOwner={username === ownerUsername}
|
{groupStage != null &&
|
||||||
level={getLevelName(stage.level)} matches={stage.matches}
|
<div><GroupStage groups={groupStage.groups} isSignedIn={isSignedIn} isOwner={isOwner}
|
||||||
key={stage.level}/>)}
|
showMatches={playoffStages !== null}/></div>}
|
||||||
|
<PlayoffStages playoffStages={playoffStages} isSignedIn={isSignedIn}
|
||||||
|
isOwner={isOwner}/>
|
||||||
</div>
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
@ -60,112 +63,6 @@ function EditButton(props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLevelName(levelNumber) {
|
|
||||||
const names = ['Finale', 'Halbfinale', 'Viertelfinale', 'Achtelfinale'];
|
|
||||||
if (levelNumber < names.length) {
|
|
||||||
return names[levelNumber];
|
|
||||||
} else {
|
|
||||||
return Math.pow(2, levelNumber) + 'tel-Finale';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Stage(props) {
|
|
||||||
const {isSignedIn, isOwner} = props;
|
|
||||||
|
|
||||||
return (<div>
|
|
||||||
<Container className='py-5'>
|
|
||||||
<h1 className='custom-font'>{props.level}</h1>
|
|
||||||
<Row>
|
|
||||||
{props.matches.map((match => (
|
|
||||||
<Col className='minw-25' key={match.id}><Match match={match} isSignedIn={isSignedIn}
|
|
||||||
isOwner={isOwner}/></Col>)))}
|
|
||||||
</Row>
|
|
||||||
</Container>
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertTournament(apiTournament) {
|
|
||||||
let groupStage = null;
|
|
||||||
const playoffStages = [];
|
|
||||||
for (const stage of apiTournament.stages) {
|
|
||||||
if (stage.groups.length > 0) {
|
|
||||||
// group stage
|
|
||||||
groupStage = {groups: stage.groups.map(group => convertGroup(group))};
|
|
||||||
} else {
|
|
||||||
// playoff stage
|
|
||||||
playoffStages.push({
|
|
||||||
id: stage.id, level: stage.level, matches: stage.matches.map(match => convertMatch(match, false))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
id: apiTournament.id,
|
|
||||||
code: apiTournament.code,
|
|
||||||
description: apiTournament.description,
|
|
||||||
name: apiTournament.name,
|
|
||||||
isPublic: apiTournament.public,
|
|
||||||
ownerUsername: apiTournament.owner_username,
|
|
||||||
groupStage: groupStage,
|
|
||||||
playoffStages: playoffStages
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertGroup(apiGroup) {
|
|
||||||
return {
|
|
||||||
id: apiGroup.id,
|
|
||||||
number: apiGroup.number,
|
|
||||||
scores: apiGroup.group_scores,
|
|
||||||
matches: apiGroup.matches.map(match => convertMatch(match, true))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertMatch(apiMatch, allowUndecided) {
|
|
||||||
const result = {
|
|
||||||
id: apiMatch.id, state: apiMatch.state, allowUndecided: allowUndecided,
|
|
||||||
winnerTeamId: apiMatch.winner === null ? null : apiMatch.winner.id
|
|
||||||
};
|
|
||||||
|
|
||||||
if (apiMatch.match_scores.length === 2) {
|
|
||||||
result.team1 = {
|
|
||||||
name: apiMatch.match_scores[0].team.name,
|
|
||||||
id: apiMatch.match_scores[0].team.id,
|
|
||||||
score: apiMatch.match_scores[0].points,
|
|
||||||
scoreId: apiMatch.match_scores[0].id
|
|
||||||
};
|
|
||||||
result.team2 = {
|
|
||||||
name: apiMatch.match_scores[1].team.name,
|
|
||||||
id: apiMatch.match_scores[1].team.id,
|
|
||||||
score: apiMatch.match_scores[1].points,
|
|
||||||
scoreId: apiMatch.match_scores[1].id
|
|
||||||
};
|
|
||||||
} else if (apiMatch.match_scores.length === 1) {
|
|
||||||
result.team1 = {
|
|
||||||
name: apiMatch.match_scores[0].team.name,
|
|
||||||
id: apiMatch.match_scores[0].team.id,
|
|
||||||
score: apiMatch.match_scores[0].points,
|
|
||||||
scoreId: apiMatch.match_scores[0].id
|
|
||||||
};
|
|
||||||
result.team2 = {
|
|
||||||
name: 'TBD',
|
|
||||||
id: null,
|
|
||||||
score: 0
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
result.team1 = {
|
|
||||||
name: 'TBD',
|
|
||||||
id: null,
|
|
||||||
score: 0
|
|
||||||
};
|
|
||||||
result.team2 = {
|
|
||||||
name: 'TBD',
|
|
||||||
id: null,
|
|
||||||
score: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Main extends React.Component {
|
class Main extends React.Component {
|
||||||
static async getInitialProps({query}) {
|
static async getInitialProps({query}) {
|
||||||
return {query};
|
return {query};
|
||||||
|
|
@ -177,22 +74,24 @@ class Main extends React.Component {
|
||||||
this.state = {
|
this.state = {
|
||||||
tournament: null
|
tournament: null
|
||||||
};
|
};
|
||||||
|
this.onTournamentRequestSuccess = this.onTournamentRequestSuccess.bind(this);
|
||||||
|
this.onTournamentRequestError = this.onTournamentRequestError.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const code = this.props.query.code;
|
getTournament(this.props.query.code, this.onTournamentRequestSuccess, this.onTournamentRequestError);
|
||||||
|
}
|
||||||
|
|
||||||
getRequest(getState(), '/tournaments/' + code)
|
onTournamentRequestSuccess(requestStatus, tournament) {
|
||||||
.then(response => {
|
this.setState({status: requestStatus, tournament: tournament});
|
||||||
this.setState({status: response.status, tournament: convertTournament(response.data)});
|
}
|
||||||
})
|
|
||||||
.catch(err => {
|
onTournamentRequestError(error) {
|
||||||
if (err.response) {
|
if (error.response) {
|
||||||
this.setState({status: err.response.status});
|
this.setState({status: error.response.status});
|
||||||
} else {
|
} else {
|
||||||
this.setState({status: -1});
|
this.setState({status: -1});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@
|
||||||
font-family: Halt, sans-serif;
|
font-family: Halt, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.default-font-family {
|
||||||
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
.navbar-brand {
|
.navbar-brand {
|
||||||
font-family: Halt, sans-serif;
|
font-family: Halt, sans-serif;
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stages > div:nth-child(odd) {
|
.stages > div > div:nth-child(odd) {
|
||||||
background-color: #f8f8f8;
|
background-color: #f8f8f8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue