diff --git a/js/api.js b/js/api.js
index ad1d6df..6112c0d 100644
--- a/js/api.js
+++ b/js/api.js
@@ -8,6 +8,11 @@ import {actionTypesUserinfo, defaultStateUserinfo} from './redux/userInfo';
import {actionTypesTournamentinfo, defaultStateTournamentinfo} from './redux/tournamentInfo';
import {actionTypesTournamentlist, defaultStateTournamentlist} from './redux/tournamentList';
import {deleteRequest, getRequest, patchRequest, postRequest, putRequest} from './redux/backendApi';
+import {
+ actionTypesTournamentStatistics,
+ defaultStateTournamentStatistics,
+ transformTournamentInfoToStatistics, transformTournamentStatsToStatistics
+} from './redux/tournamentStatistics';
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 = {
userinfo: reducerUserinfo,
tournamentinfo: reducerTournamentinfo,
- tournamentlist: reducerTournamentlist
+ tournamentlist: reducerTournamentlist,
+ tournamentStatistics: reducerTournamentStatistics
};
const defaultApplicationState = {
userinfo: defaultStateUserinfo,
tournamentinfo: defaultStateTournamentinfo,
- tournamentlist: defaultStateTournamentlist
+ tournamentlist: defaultStateTournamentlist,
+ tournamentStatistics: defaultStateTournamentStatistics
};
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() {
const persistedState = localStorage.getItem('reduxState') ?
JSON.parse(localStorage.getItem('reduxState')) :
@@ -517,6 +582,10 @@ function rehydrateApplicationState() {
type: actionTypesTournamentlist.REHYDRATE,
parameters: Object.assign({}, persistedState.tournamentlist)
});
+ __store.dispatch({
+ type: actionTypesTournamentStatistics.REHYDRATE,
+ parameters: Object.assign({}, persistedState.tournamentstatistics)
+ });
}
applicationHydrated = true;
}
diff --git a/js/components/DominanceShower.js b/js/components/DominanceShower.js
new file mode 100644
index 0000000..5861858
--- /dev/null
+++ b/js/components/DominanceShower.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import {
+ Card,
+ CardBody,
+ CardTitle,
+ Table
+} from 'reactstrap';
+
+export class DominanceShower extends React.Component {
+ render() {
+ return (
+
+
+ {this.props.title}
+
+
+
+ | {this.props.stats.team_name} |
+
+
+ | {this.props.stats.points_made} |
+ {this.props.stats.points_received} |
+
+
+ | Punkte erzielt |
+ Punkte kassiert |
+
+
+
+
+
+ );
+ }
+}
diff --git a/js/components/StandingsTable.js b/js/components/StandingsTable.js
new file mode 100644
index 0000000..c82d8ad
--- /dev/null
+++ b/js/components/StandingsTable.js
@@ -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 (
+
+
+ Aktuelle Rangliste
+
+
+
+ | # |
+ Team Name |
+ Match Differenz |
+ Punkt Differenz |
+
+
+
+ { rangedmap(performances, (team, index) => (
+
+ ), 0, 3) }
+
+
+ { rangedmap(performances, (team, index) => (
+
+ ), 3) }
+
+
+
+ |
+
+ |
+
+
+
+
+
+ );
+ }
+
+ toggleShowFullTable() {
+ this.setState({showFullTable: !this.state.showFullTable});
+ }
+}
+
+class TeamRow extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ return (
+
+ | { this.props.teamToShow.rank } |
+ { this.props.teamToShow.team_name } |
+ { this.props.teamToShow.win_loss_differential } |
+ { this.props.teamToShow.point_differential } |
+
+ );
+ }
+}
+
+class TableButton extends React.Component {
+ render() {
+ const {isFullTableShown} = this.props;
+
+ if (isFullTableShown) {
+ return ;
+ } else {
+ return ;
+ }
+ }
+}
diff --git a/js/components/TournamentBigImage.js b/js/components/TournamentBigImage.js
new file mode 100644
index 0000000..ed304f0
--- /dev/null
+++ b/js/components/TournamentBigImage.js
@@ -0,0 +1,22 @@
+import {Container, ListGroup, ListGroupItem} from 'reactstrap';
+import React from 'react';
+
+export function TournamentBigImage(props) {
+ return (
+
{props.name}
+
+
+
+ );
+}
+
+function TournamentProperties(props) {
+ return (
+ {props.description && {props.description}}
+
+ {props.isPublic ? 'Das Turnier ist öffentlich.' : 'Das Turnier ist privat.'}
+
+ Turnier-Code: {props.code}
+ von {props.ownerUsername}
+ );
+}
diff --git a/js/components/TournamentStatusBar.js b/js/components/TournamentStatusBar.js
new file mode 100644
index 0000000..0fd018a
--- /dev/null
+++ b/js/components/TournamentStatusBar.js
@@ -0,0 +1,29 @@
+import Navbar from 'react-bootstrap/Navbar';
+import {Container} from 'reactstrap';
+import React from 'react';
+
+export function TournamentStatusBar(props) {
+ return (
+
+ {props.children}
+
+ );
+}
+
+export function TournamentStatusBarButton(props) {
+ return (
+ {props.children}
+ );
+}
+
+export function EditButton(props) {
+ const {tournamentId, isOwner, isSignedIn} = props;
+
+ if (isSignedIn && isOwner) {
+ return (
+ Turnier bearbeiten
+ );
+ } else {
+ return null;
+ }
+}
diff --git a/js/redux/tournamentStatistics.js b/js/redux/tournamentStatistics.js
new file mode 100644
index 0000000..4004982
--- /dev/null
+++ b/js/redux/tournamentStatistics.js
@@ -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 === [];
+}
+
diff --git a/js/utils/rangedmap.js b/js/utils/rangedmap.js
new file mode 100644
index 0000000..adedcb6
--- /dev/null
+++ b/js/utils/rangedmap.js
@@ -0,0 +1,4 @@
+
+export function rangedmap(arr, func, start, end) {
+ return arr.slice(start, end).map((element, index) => func(element, start + index));
+}
diff --git a/pages/_app.js b/pages/_app.js
index ac91bd6..6bc4dcb 100644
--- a/pages/_app.js
+++ b/pages/_app.js
@@ -15,8 +15,8 @@ class TurniereApp extends App {
render() {
const {Component, pageProps, reduxStore} = this.props;
return (
-
-
+
+
diff --git a/pages/tournament-statistics.js b/pages/tournament-statistics.js
new file mode 100644
index 0000000..0beb23b
--- /dev/null
+++ b/pages/tournament-statistics.js
@@ -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 (
+
+
+
{tournamentStatistics.name}: turnie.re
+
+
+
+
+
+ {tournamentStatistics.name}
+
+
+ zurück zum Turnier
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+function StatisticsView(props) {
+ if (props.tournamentStatistics.statistics_available) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
+ } else {
+ return (
+ Statistiken sind für dieses Turnier leider nicht verfügbar.
+ );
+ }
+}
+
+function mapTournamentStatisticsToProps(state) {
+ const {tournamentStatistics} = state;
+ const {isSignedIn, username} = state.userinfo;
+ return {tournamentStatistics, isSignedIn, username};
+}
+
+export default connect(
+ mapTournamentStatisticsToProps
+)(StatisticsTournamentPage);
diff --git a/pages/tournament.js b/pages/tournament.js
index 5496d38..341adfe 100644
--- a/pages/tournament.js
+++ b/pages/tournament.js
@@ -1,7 +1,6 @@
import Head from 'next/head';
import React from 'react';
import {connect} from 'react-redux';
-import {Container, ListGroup, ListGroupItem} from 'reactstrap';
import Navbar from 'react-bootstrap/Navbar';
@@ -16,6 +15,8 @@ import '../static/css/tournament.css';
import {getTournament} from '../js/redux/tournamentApi';
import {PlayoffStages} from '../js/components/PlayoffStages';
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 {
render() {
@@ -38,36 +39,21 @@ class PrivateTournamentPage extends React.Component {
}
function StatusBar(props) {
- return (
-
-
- {props.tournament.name}
-
-
-
- );
+ return (
+
+ {props.tournament.name}
+
+
+
+ );
}
-
-function TournamentBigImage(props) {
- return (
-
{props.name}
-
-
-
- );
+function StatisticsButton(props) {
+ return (
+ Statistiken
+ );
}
-function TournamentProperties(props) {
- return (
- {props.description && {props.description}}
-
- {props.isPublic ? 'Das Turnier ist öffentlich.' : 'Das Turnier ist privat.'}
-
- Turnier-Code: {props.code}
- von {props.ownerUsername}
- );
-}
function mapStateToTournamentPageProperties(state) {
const {isSignedIn, username} = state.userinfo;
@@ -76,18 +62,6 @@ function mapStateToTournamentPageProperties(state) {
const TournamentPage = connect(mapStateToTournamentPageProperties)(PrivateTournamentPage);
-function EditButton(props) {
- const {id, isOwner, isSignedIn} = props;
-
- if (isSignedIn && isOwner) {
- return (
- Turnier bearbeiten
- );
- } else {
- return null;
- }
-}
-
class Main extends React.Component {
static async getInitialProps({query}) {
return {query};
diff --git a/server.js b/server.js
index 6c90888..8772603 100644
--- a/server.js
+++ b/server.js
@@ -27,6 +27,12 @@ app.prepare()
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) => {
return handle(req, res);
});