Merge pull request #22 from turniere/ticket/TURNIERE-148
Create statistics page for a tournament
This commit is contained in:
commit
f95e85991f
73
js/api.js
73
js/api.js
|
|
@ -8,6 +8,11 @@ import {actionTypesUserinfo, defaultStateUserinfo} from './redux/userInfo';
|
||||||
import {actionTypesTournamentinfo, defaultStateTournamentinfo} from './redux/tournamentInfo';
|
import {actionTypesTournamentinfo, defaultStateTournamentinfo} from './redux/tournamentInfo';
|
||||||
import {actionTypesTournamentlist, defaultStateTournamentlist} from './redux/tournamentList';
|
import {actionTypesTournamentlist, defaultStateTournamentlist} from './redux/tournamentList';
|
||||||
import {deleteRequest, getRequest, patchRequest, postRequest, putRequest} from './redux/backendApi';
|
import {deleteRequest, getRequest, patchRequest, postRequest, putRequest} from './redux/backendApi';
|
||||||
|
import {
|
||||||
|
actionTypesTournamentStatistics,
|
||||||
|
defaultStateTournamentStatistics,
|
||||||
|
transformTournamentInfoToStatistics, transformTournamentStatsToStatistics
|
||||||
|
} from './redux/tournamentStatistics';
|
||||||
|
|
||||||
|
|
||||||
function storeOptionalToken(response) {
|
function storeOptionalToken(response) {
|
||||||
|
|
@ -319,16 +324,64 @@ const reducerTournamentlist = (state = defaultStateTournamentlist, action) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const reducerTournamentStatistics = (state = defaultStateTournamentStatistics, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case actionTypesTournamentStatistics.REQUEST_TOURNAMENT_STATISTICS:
|
||||||
|
getRequest(action.state, '/tournaments/' + action.parameters.code).then(resp => {
|
||||||
|
storeOptionalToken(resp);
|
||||||
|
__store.dispatch({
|
||||||
|
type: actionTypesTournamentStatistics.INT_REQUEST_TOURNAMENT_STATISTICS,
|
||||||
|
state: action.state,
|
||||||
|
parameters: {
|
||||||
|
code: action.parameters.code,
|
||||||
|
tournamentInfo: transformTournamentInfoToStatistics(resp.data),
|
||||||
|
successCallback: action.parameters.successCallback,
|
||||||
|
errorCallback: action.parameters.errorCallback
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).catch(error => {
|
||||||
|
if (error.response) {
|
||||||
|
storeOptionalToken(error.response);
|
||||||
|
}
|
||||||
|
action.parameters.errorCallback();
|
||||||
|
});
|
||||||
|
return state;
|
||||||
|
case actionTypesTournamentStatistics.INT_REQUEST_TOURNAMENT_STATISTICS:
|
||||||
|
getRequest(action.state, '/tournaments/' + action.parameters.code + '/statistics').then(resp => {
|
||||||
|
storeOptionalToken(resp);
|
||||||
|
__store.dispatch({
|
||||||
|
type: actionTypesTournamentStatistics.REQUEST_TOURNAMENT_STATISTICS_SUCCESS,
|
||||||
|
parameters: {
|
||||||
|
tournamentStatistics: transformTournamentStatsToStatistics(resp.data),
|
||||||
|
successCallback: action.parameters.successCallback
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).catch(error => {
|
||||||
|
if (error.response) {
|
||||||
|
storeOptionalToken(error.response);
|
||||||
|
}
|
||||||
|
action.parameters.errorCallback();
|
||||||
|
});
|
||||||
|
return Object.assign({}, state, action.parameters.tournamentInfo);
|
||||||
|
case actionTypesTournamentStatistics.REQUEST_TOURNAMENT_STATISTICS_SUCCESS:
|
||||||
|
action.parameters.successCallback();
|
||||||
|
return Object.assign({}, state, action.parameters.tournamentStatistics);
|
||||||
|
default: return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const reducers = {
|
const reducers = {
|
||||||
userinfo: reducerUserinfo,
|
userinfo: reducerUserinfo,
|
||||||
tournamentinfo: reducerTournamentinfo,
|
tournamentinfo: reducerTournamentinfo,
|
||||||
tournamentlist: reducerTournamentlist
|
tournamentlist: reducerTournamentlist,
|
||||||
|
tournamentStatistics: reducerTournamentStatistics
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultApplicationState = {
|
const defaultApplicationState = {
|
||||||
userinfo: defaultStateUserinfo,
|
userinfo: defaultStateUserinfo,
|
||||||
tournamentinfo: defaultStateTournamentinfo,
|
tournamentinfo: defaultStateTournamentinfo,
|
||||||
tournamentlist: defaultStateTournamentlist
|
tournamentlist: defaultStateTournamentlist,
|
||||||
|
tournamentStatistics: defaultStateTournamentStatistics
|
||||||
};
|
};
|
||||||
|
|
||||||
let __store;
|
let __store;
|
||||||
|
|
@ -499,6 +552,18 @@ export function requestTournamentList(type, successCallback, errorCallback) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function requestTournamentStatistics(code, successCallback, errorCallback) {
|
||||||
|
__store.dispatch({
|
||||||
|
type: actionTypesTournamentStatistics.REQUEST_TOURNAMENT_STATISTICS,
|
||||||
|
parameters: {
|
||||||
|
code: code,
|
||||||
|
successCallback: successCallback,
|
||||||
|
errorCallback: errorCallback
|
||||||
|
},
|
||||||
|
state: __store.getState()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function rehydrateApplicationState() {
|
function rehydrateApplicationState() {
|
||||||
const persistedState = localStorage.getItem('reduxState') ?
|
const persistedState = localStorage.getItem('reduxState') ?
|
||||||
JSON.parse(localStorage.getItem('reduxState')) :
|
JSON.parse(localStorage.getItem('reduxState')) :
|
||||||
|
|
@ -517,6 +582,10 @@ function rehydrateApplicationState() {
|
||||||
type: actionTypesTournamentlist.REHYDRATE,
|
type: actionTypesTournamentlist.REHYDRATE,
|
||||||
parameters: Object.assign({}, persistedState.tournamentlist)
|
parameters: Object.assign({}, persistedState.tournamentlist)
|
||||||
});
|
});
|
||||||
|
__store.dispatch({
|
||||||
|
type: actionTypesTournamentStatistics.REHYDRATE,
|
||||||
|
parameters: Object.assign({}, persistedState.tournamentstatistics)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
applicationHydrated = true;
|
applicationHydrated = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardBody,
|
||||||
|
CardTitle,
|
||||||
|
Table
|
||||||
|
} from 'reactstrap';
|
||||||
|
|
||||||
|
export class DominanceShower extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Card className="shadow-sm">
|
||||||
|
<CardBody>
|
||||||
|
<CardTitle>{this.props.title}</CardTitle>
|
||||||
|
<Table borderless className="m-0">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th colSpan="2" className="h3 text-center">{this.props.stats.team_name}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="h4 text-success pb-0">{this.props.stats.points_made}</td>
|
||||||
|
<td className="h4 text-danger text-right pb-0">{this.props.stats.points_received}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="smaller pt-0">Punkte erzielt</td>
|
||||||
|
<td className="text-right smaller pt-0">Punkte kassiert</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardBody,
|
||||||
|
Collapse,
|
||||||
|
Table
|
||||||
|
} from 'reactstrap';
|
||||||
|
|
||||||
|
import {rangedmap} from '../utils/rangedmap';
|
||||||
|
|
||||||
|
export class StandingsTable extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
showFullTable: false
|
||||||
|
};
|
||||||
|
this.toggleShowFullTable = this.toggleShowFullTable.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const performances = this.props.data.group_phase_performances;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="shadow-sm">
|
||||||
|
<CardBody>
|
||||||
|
<h1 className="custom-font">Aktuelle Rangliste</h1>
|
||||||
|
<Table className="mt-3 mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Team Name</th>
|
||||||
|
<th className="text-center">Match Differenz</th>
|
||||||
|
<th className="text-center">Punkt Differenz</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{ rangedmap(performances, (team, index) => (
|
||||||
|
<TeamRow className={(index % 2 === 0)? 'bg-light' : 'bg-white'}
|
||||||
|
key={index} teamToShow={team}/>
|
||||||
|
), 0, 3) }
|
||||||
|
</tbody>
|
||||||
|
<Collapse isOpen={ this.state.showFullTable } tag="tbody">
|
||||||
|
{ rangedmap(performances, (team, index) => (
|
||||||
|
<TeamRow className={(index % 2 === 0)? 'bg-light' : 'bg-white'}
|
||||||
|
key={index} teamToShow={team}/>
|
||||||
|
), 3) }
|
||||||
|
</Collapse>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colSpan='4'>
|
||||||
|
<TableButton isFullTableShown={this.state.showFullTable}
|
||||||
|
onToggle={this.toggleShowFullTable}/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</Table>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleShowFullTable() {
|
||||||
|
this.setState({showFullTable: !this.state.showFullTable});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TeamRow extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<tr className={this.props.className}>
|
||||||
|
<td>{ this.props.teamToShow.rank }</td>
|
||||||
|
<td className="w-100">{ this.props.teamToShow.team_name }</td>
|
||||||
|
<td className="text-center">{ this.props.teamToShow.win_loss_differential }</td>
|
||||||
|
<td className="text-center">{ this.props.teamToShow.point_differential }</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TableButton extends React.Component {
|
||||||
|
render() {
|
||||||
|
const {isFullTableShown} = this.props;
|
||||||
|
|
||||||
|
if (isFullTableShown) {
|
||||||
|
return <Button className="w-100" onClick={this.props.onToggle}>Zeige nur die 3 besten Teams</Button>;
|
||||||
|
} else {
|
||||||
|
return <Button className="w-100" onClick={this.props.onToggle}>Zeige alle Teams</Button>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import {Container, ListGroup, ListGroupItem} from 'reactstrap';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export function TournamentBigImage(props) {
|
||||||
|
return (<div className="big-image mb-0">
|
||||||
|
<h1 className="display-1">{props.name}</h1>
|
||||||
|
<Container>
|
||||||
|
<TournamentProperties {...props}/>
|
||||||
|
</Container>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TournamentProperties(props) {
|
||||||
|
return (<ListGroup className='text-dark text-left shadow'>
|
||||||
|
{props.description && <ListGroupItem>{props.description}</ListGroupItem>}
|
||||||
|
<ListGroupItem>
|
||||||
|
{props.isPublic ? 'Das Turnier ist öffentlich.' : 'Das Turnier ist privat.'}
|
||||||
|
</ListGroupItem>
|
||||||
|
<ListGroupItem>Turnier-Code: <b>{props.code}</b></ListGroupItem>
|
||||||
|
<ListGroupItem>von <b>{props.ownerUsername}</b></ListGroupItem>
|
||||||
|
</ListGroup>);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import Navbar from 'react-bootstrap/Navbar';
|
||||||
|
import {Container} from 'reactstrap';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export function TournamentStatusBar(props) {
|
||||||
|
return (<Navbar sticky='top' bg='light' className='border-bottom border-top'>
|
||||||
|
<Container className='px-3'>
|
||||||
|
{props.children}
|
||||||
|
</Container>
|
||||||
|
</Navbar>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TournamentStatusBarButton(props) {
|
||||||
|
return (<a href={props.href} className='ml-3 btn btn-outline-secondary default-font-family'>
|
||||||
|
{props.children}
|
||||||
|
</a>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EditButton(props) {
|
||||||
|
const {tournamentId, isOwner, isSignedIn} = props;
|
||||||
|
|
||||||
|
if (isSignedIn && isOwner) {
|
||||||
|
return (<TournamentStatusBarButton href={'/t/' + tournamentId + '/edit'}>
|
||||||
|
Turnier bearbeiten
|
||||||
|
</TournamentStatusBarButton>);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
export const actionTypesTournamentStatistics = {
|
||||||
|
'REQUEST_TOURNAMENT_STATISTICS': 'REQUEST_TOURNAMENT_STATISTICS',
|
||||||
|
'INT_REQUEST_TOURNAMENT_STATISTICS': 'INT_REQUEST_TOURNAMENT_STATISTICS',
|
||||||
|
|
||||||
|
'REQUEST_TOURNAMENT_STATISTICS_SUCCESS': 'REQUEST_TOURNAMENT_STATISTICS_SUCCESS',
|
||||||
|
|
||||||
|
'REHYDRATE': 'TOURNAMENTINFO_REHYDRATE',
|
||||||
|
'CLEAR': 'TOURNAMENTINFO_CLEAR'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultStateTournamentStatistics = {
|
||||||
|
code: '',
|
||||||
|
description: '',
|
||||||
|
id: -1,
|
||||||
|
name: '',
|
||||||
|
owner_username: '',
|
||||||
|
isPublic: '',
|
||||||
|
|
||||||
|
statistics_available: false,
|
||||||
|
|
||||||
|
most_dominant_team: {},
|
||||||
|
least_dominant_team: {},
|
||||||
|
group_phase_performances: []
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export function transformTournamentInfoToStatistics(data) {
|
||||||
|
return {
|
||||||
|
code: data.code,
|
||||||
|
description: data.description,
|
||||||
|
id: data.id,
|
||||||
|
name: data.name,
|
||||||
|
ownerUsername: data.owner_username,
|
||||||
|
isPublic: data.public
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformTournamentStatsToStatistics(data) {
|
||||||
|
if (statisticsUnavailable(data)) {
|
||||||
|
return {
|
||||||
|
statistics_available: false,
|
||||||
|
most_dominant_team: {},
|
||||||
|
least_dominant_team: {},
|
||||||
|
group_phase_performances: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const statistics = {
|
||||||
|
statistics_available: true,
|
||||||
|
most_dominant_team: {
|
||||||
|
points_made: data.most_dominant_score.scored_points,
|
||||||
|
points_received: data.most_dominant_score.received_points,
|
||||||
|
team_name: data.most_dominant_score.team.name
|
||||||
|
},
|
||||||
|
least_dominant_team: {
|
||||||
|
points_made: data.least_dominant_score.scored_points,
|
||||||
|
points_received: data.least_dominant_score.received_points,
|
||||||
|
team_name: data.least_dominant_score.team.name
|
||||||
|
},
|
||||||
|
group_phase_performances: []
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < data.group_scores.length; i++) {
|
||||||
|
const score = data.group_scores[i];
|
||||||
|
|
||||||
|
statistics.group_phase_performances[i] = {
|
||||||
|
win_loss_differential: score.group_points,
|
||||||
|
point_differential: score.scored_points - score.received_points,
|
||||||
|
rank: i + 1,
|
||||||
|
team_name: score.team.name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return statistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
function statisticsUnavailable(data) {
|
||||||
|
return data === {} || data.most_dominant_score === null ||
|
||||||
|
data.least_dominant_score === null || data.group_scores === [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
export function rangedmap(arr, func, start, end) {
|
||||||
|
return arr.slice(start, end).map((element, index) => func(element, start + index));
|
||||||
|
}
|
||||||
|
|
@ -15,8 +15,8 @@ class TurniereApp extends App {
|
||||||
render() {
|
render() {
|
||||||
const {Component, pageProps, reduxStore} = this.props;
|
const {Component, pageProps, reduxStore} = this.props;
|
||||||
return (<Container>
|
return (<Container>
|
||||||
<Notifications/>
|
<Notifications />
|
||||||
<Favicon url="../static/icons/favicon.ico"/>
|
<Favicon url="/static/icons/favicon.ico"/>
|
||||||
<Provider store={reduxStore}>
|
<Provider store={reduxStore}>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
import Head from 'next/head';
|
||||||
|
import React from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import {Col, Container, Row} from 'reactstrap';
|
||||||
|
|
||||||
|
import {TurniereNavigation} from '../js/components/Navigation';
|
||||||
|
import {StandingsTable} from '../js/components/StandingsTable';
|
||||||
|
import {DominanceShower} from '../js/components/DominanceShower';
|
||||||
|
import {Footer} from '../js/components/Footer';
|
||||||
|
import {requestTournamentStatistics} from '../js/api';
|
||||||
|
import {EditButton, TournamentStatusBar, TournamentStatusBarButton} from '../js/components/TournamentStatusBar';
|
||||||
|
import Navbar from 'react-bootstrap/Navbar';
|
||||||
|
import {TournamentBigImage} from '../js/components/TournamentBigImage';
|
||||||
|
|
||||||
|
class StatisticsTournamentPage extends React.Component {
|
||||||
|
static async getInitialProps({query}) {
|
||||||
|
return {query};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
requestTournamentStatistics(this.props.query.code, () => {}, () => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {tournamentStatistics} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Head>
|
||||||
|
<title>{tournamentStatistics.name}: turnie.re</title>
|
||||||
|
</Head>
|
||||||
|
<TurniereNavigation/>
|
||||||
|
<TournamentBigImage {...tournamentStatistics}/>
|
||||||
|
<TournamentStatusBar>
|
||||||
|
<Navbar.Brand>
|
||||||
|
{tournamentStatistics.name}
|
||||||
|
<EditButton tournamentId={tournamentStatistics.id}
|
||||||
|
isOwner={this.props.username === tournamentStatistics.ownerUsername}
|
||||||
|
isSignedIn={this.props.isSignedIn}/>
|
||||||
|
<TournamentStatusBarButton href={'/t/' + tournamentStatistics.id}>
|
||||||
|
zurück zum Turnier
|
||||||
|
</TournamentStatusBarButton>
|
||||||
|
</Navbar.Brand>
|
||||||
|
</TournamentStatusBar>
|
||||||
|
<div className='pb-5'>
|
||||||
|
<StatisticsView tournamentStatistics={tournamentStatistics} />
|
||||||
|
</div>
|
||||||
|
<Footer/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatisticsView(props) {
|
||||||
|
if (props.tournamentStatistics.statistics_available) {
|
||||||
|
return (<div>
|
||||||
|
<Container className="py-5">
|
||||||
|
<Row>
|
||||||
|
<Col xs="6">
|
||||||
|
<DominanceShower stats={props.tournamentStatistics.most_dominant_team}
|
||||||
|
title="Stärkstes Team"/>
|
||||||
|
</Col>
|
||||||
|
<Col xs="6">
|
||||||
|
<DominanceShower stats={props.tournamentStatistics.least_dominant_team}
|
||||||
|
title="Schwächstes Team"/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
<Container className="pb-5">
|
||||||
|
<StandingsTable data={props.tournamentStatistics}/>
|
||||||
|
</Container>
|
||||||
|
</div>);
|
||||||
|
} else {
|
||||||
|
return (<Container className="py-5">
|
||||||
|
<h2 className="text-center">Statistiken sind für dieses Turnier leider nicht verfügbar.</h2>
|
||||||
|
</Container>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapTournamentStatisticsToProps(state) {
|
||||||
|
const {tournamentStatistics} = state;
|
||||||
|
const {isSignedIn, username} = state.userinfo;
|
||||||
|
return {tournamentStatistics, isSignedIn, username};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapTournamentStatisticsToProps
|
||||||
|
)(StatisticsTournamentPage);
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
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 {Container, ListGroup, ListGroupItem} from 'reactstrap';
|
|
||||||
import Navbar from 'react-bootstrap/Navbar';
|
import Navbar from 'react-bootstrap/Navbar';
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -16,6 +15,8 @@ import '../static/css/tournament.css';
|
||||||
import {getTournament} from '../js/redux/tournamentApi';
|
import {getTournament} from '../js/redux/tournamentApi';
|
||||||
import {PlayoffStages} from '../js/components/PlayoffStages';
|
import {PlayoffStages} from '../js/components/PlayoffStages';
|
||||||
import GroupStage from '../js/components/GroupStage';
|
import GroupStage from '../js/components/GroupStage';
|
||||||
|
import {TournamentBigImage} from '../js/components/TournamentBigImage';
|
||||||
|
import {EditButton, TournamentStatusBar, TournamentStatusBarButton} from '../js/components/TournamentStatusBar';
|
||||||
|
|
||||||
class PrivateTournamentPage extends React.Component {
|
class PrivateTournamentPage extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
|
|
@ -38,36 +39,21 @@ class PrivateTournamentPage extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
function StatusBar(props) {
|
function StatusBar(props) {
|
||||||
return (<Navbar sticky='top' bg='light' className='border-bottom border-top'>
|
return (<TournamentStatusBar>
|
||||||
<Container className='px-3'>
|
<Navbar.Brand>
|
||||||
<Navbar.Brand>
|
{props.tournament.name}
|
||||||
{props.tournament.name}
|
<EditButton tournamentId={props.tournament.id} isOwner={props.isOwner} isSignedIn={props.isSignedIn}/>
|
||||||
<EditButton id={props.id} isOwner={props.isOwner} isSignedIn={props.isSignedIn}/>
|
<StatisticsButton tournamentId={props.tournament.id}/>
|
||||||
</Navbar.Brand>
|
</Navbar.Brand>
|
||||||
</Container>
|
</TournamentStatusBar>);
|
||||||
</Navbar>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function StatisticsButton(props) {
|
||||||
function TournamentBigImage(props) {
|
return (<TournamentStatusBarButton href={'/t/' + props.tournamentId + '/statistics'}>
|
||||||
return (<div className="big-image mb-0">
|
Statistiken
|
||||||
<h1 className="display-1">{props.name}</h1>
|
</TournamentStatusBarButton>);
|
||||||
<Container>
|
|
||||||
<TournamentProperties {...props}/>
|
|
||||||
</Container>
|
|
||||||
</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function TournamentProperties(props) {
|
|
||||||
return (<ListGroup className='text-dark text-left shadow'>
|
|
||||||
{props.description && <ListGroupItem>{props.description}</ListGroupItem>}
|
|
||||||
<ListGroupItem>
|
|
||||||
{props.isPublic ? 'Das Turnier ist öffentlich.' : 'Das Turnier ist privat.'}
|
|
||||||
</ListGroupItem>
|
|
||||||
<ListGroupItem>Turnier-Code: <b>{props.code}</b></ListGroupItem>
|
|
||||||
<ListGroupItem>von <b>{props.ownerUsername}</b></ListGroupItem>
|
|
||||||
</ListGroup>);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapStateToTournamentPageProperties(state) {
|
function mapStateToTournamentPageProperties(state) {
|
||||||
const {isSignedIn, username} = state.userinfo;
|
const {isSignedIn, username} = state.userinfo;
|
||||||
|
|
@ -76,18 +62,6 @@ function mapStateToTournamentPageProperties(state) {
|
||||||
|
|
||||||
const TournamentPage = connect(mapStateToTournamentPageProperties)(PrivateTournamentPage);
|
const TournamentPage = connect(mapStateToTournamentPageProperties)(PrivateTournamentPage);
|
||||||
|
|
||||||
function EditButton(props) {
|
|
||||||
const {id, isOwner, isSignedIn} = props;
|
|
||||||
|
|
||||||
if (isSignedIn && isOwner) {
|
|
||||||
return (<a href={'/t/' + id + '/edit'} className='ml-3 btn btn-outline-secondary default-font-family'>
|
|
||||||
Turnier bearbeiten
|
|
||||||
</a>);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Main extends React.Component {
|
class Main extends React.Component {
|
||||||
static async getInitialProps({query}) {
|
static async getInitialProps({query}) {
|
||||||
return {query};
|
return {query};
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,12 @@ app.prepare()
|
||||||
app.render(req, res, actualPage, queryParam);
|
app.render(req, res, actualPage, queryParam);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.get('/t/:code/statistics', (req, res) => {
|
||||||
|
const actualPage = '/tournament-statistics';
|
||||||
|
const queryParam = {code: req.params.code};
|
||||||
|
app.render(req, res, actualPage, queryParam);
|
||||||
|
});
|
||||||
|
|
||||||
server.get('*', (req, res) => {
|
server.get('*', (req, res) => {
|
||||||
return handle(req, res);
|
return handle(req, res);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue