Browse Source

State before 1. WE. Only thing missing is module to generate PDFs via web.

Tomi Cvetic 6 năm trước cách đây
mục cha
commit
811c4c077a

+ 11 - 0
client/src/Main.js

@@ -10,6 +10,17 @@ import './App.css'
 
 /** Main application */
 class Main extends React.Component {
+  componentDidMount () {
+    this.props.configActions.configGetRequest()
+    this.props.swisstennisActions.fileListRequest()
+    this.props.swisstennisActions.getPlayerListsRequest()
+    this.props.swisstennisActions.getPlayerListRequest()
+    this.props.swisstennisActions.getCalendarsRequest()
+    this.props.swisstennisActions.getCalendarRequest()
+    this.props.usersActions.getUserListRequest()
+    this.props.smsActions.getCreditRequest()
+  }
+
   render () {
     return (
       <div className='container'>

+ 21 - 7
client/src/api/state.js

@@ -1,5 +1,5 @@
 /** @module users/state */
-import { call, put, takeEvery, select } from 'redux-saga/effects'
+import { call, put, takeEvery, select, all } from 'redux-saga/effects'
 import { replace } from 'connected-react-router'
 import jwt from 'jwt-simple'
 import { SZTM_API } from '../local-constants'
@@ -42,8 +42,9 @@ console.log('State actions', actions)
 
 // Check, if there is a valid token.
 function validateToken (token) {
-    const tokenData = !!token && jwt.decode(token, null, true)
-    const tokenValid = !!tokenData && tokenData.expires > Date.now()
+    console.log(token)
+    const tokenData = token && jwt.decode(token, null, true)
+    const tokenValid = tokenData && tokenData.expires > Date.now()
     return { token, tokenData, tokenValid }
 }
 const { token, tokenData, tokenValid } = validateToken(localStorage.getItem('accessToken'))
@@ -82,15 +83,19 @@ function * login (action) {
             body: JSON.stringify(action.data)
         })
         const responseJson = yield response.json()
-        if (response.status != 200) {
+        if (response.status !== 200) {
             throw new Error(responseJson)
         }
         const { token, tokenData, tokenValid } = validateToken(responseJson)
         localStorage.setItem('accessToken', token)
         console.log('User login success!', token, tokenData)
         yield put(actions.loginSuccess({token, tokenData, tokenValid}))
-        state.router.location.originalAction ? yield put(state.router.location.originalAction) : null
-        state.router.location.originalPath ? yield put(replace({pathname: state.router.location.originalPath})) : yield put(replace({pathname: '/swisstennis'}))
+        if (state.router.location.originalAction) yield put(state.router.location.originalAction)
+        if (state.router.location.originalPath) {
+            yield put(replace({pathname: state.router.location.originalPath}))
+        } else {
+            yield put(replace({pathname: '/swisstennis'}))
+        }
     } catch (error) {
         console.log('User login failure!', error)
         yield put(actions.loginFailure(error))
@@ -116,7 +121,16 @@ function * api (action) {
     })
     console.log('API received response', response) 
     if (response.status === 200) {
-        return yield put(onSuccess(yield response.json()))
+        if (onSuccess instanceof Array) {
+            console.log('array of functions.')
+            const responseJson = yield response.json()
+            yield all(onSuccess.map((fn) => {
+                console.log('function', fn)
+                return put(fn(responseJson))
+            }))
+        } else {
+            return yield put(onSuccess(yield response.json()))
+        }
     } else if (response.status === 403) {
         yield put(onFailure(yield response.json()))
         return yield put(replace({pathname: '/login', originalAction: action, originalPath: state.router.location.pathname}))

+ 0 - 6
client/src/config/components/ConfigList.js

@@ -6,12 +6,6 @@ class ConfigList extends React.Component {
     this.handleChange = this.handleChange.bind(this)
   }
 
-  componentDidMount () {
-    console.log('ConfigList did mount', this)
-    const { configGetRequest } = this.props.configActions
-    configGetRequest()
-  }
-
   handleChange (event) {
     event.preventDefault()
     const { configChangeForm } = this.props.configActions

+ 1 - 2
client/src/config/state.js

@@ -1,6 +1,5 @@
 /** @module config/state */
-import { call, put, takeEvery, select } from 'redux-saga/effects'
-import { SZTM_API } from '../local-constants'
+import { put, takeEvery } from 'redux-saga/effects'
 import api from '../api'
 
 /**

+ 4 - 10
client/src/index.js

@@ -17,7 +17,6 @@ import Main from './Main'
 
 // Import the submodules
 import api from './api'
-import matches from './matches'
 import layout from './layout'
 import alerts from './alerts'
 import users from './users'
@@ -38,7 +37,6 @@ console.log('history:', history)
 /** The root reducer is combined from all sub-module reducers */
 const rootReducer = combineReducers({
   api: api.reducer,
-  matches: matches.reducer,
   layout: layout.reducer,
   alerts: alerts.reducer,
   users: users.reducer,
@@ -51,7 +49,6 @@ console.log('Root reducer:', rootReducer)
 /** The default state is combined from all sub-module states */
 const defaultState = {
   api: api.state,
-  matches: matches.state,
   layout: layout.state,
   alerts: alerts.state,
   users: users.state,
@@ -66,7 +63,6 @@ function * rootSaga () {
   console.log('rootSaga called')
   yield all([
     api.saga(),
-    matches.saga(),
     layout.saga(),
     alerts.saga(),
     users.saga(),
@@ -131,7 +127,6 @@ sagaMiddleware.run(rootSaga)
 /** Collect the action creators from all modules in actionCreators */
 const actionCreators = {
   api: api.actions,
-  matches: matches.actions,
   layout: layout.actions,
   alerts: alerts.actions,
   users: users.actions,
@@ -183,15 +178,14 @@ ReactDOM.render(
   document.getElementById('root')
 )
 
-/**
- * Hot module reloading
+
+/* Hot module reloading
 if (module.hot) {
-  module.hot.accept('./App', () => {
+  module.hot.accept('./Main', () => {
     render()
   })
 
   module.hot.accept('./reducers', () => {
     store.replaceReducer(connectedRouter(history)(rootReducer))
   })
-}
- */
+}*/

+ 1 - 1
client/src/layout/components/AppLayout.js

@@ -35,7 +35,7 @@ class AppLayout extends React.Component {
     const { changeTab } = this.props.state.layoutActions
     const { PlayerList, MatchList, StartPage, UserList } = this.props.components
 
-    const { token, tokenData } = state.users
+    const { tokenData } = state.users
 
     return (
       <div>

+ 0 - 1
client/src/layout/components/Footer.js

@@ -4,7 +4,6 @@ class Header extends React.Component {
     render () {
         return (
     <footer>
-        <p>So, das wars</p>
     </footer>
         )
     }

+ 0 - 1
client/src/layout/components/Header.js

@@ -16,7 +16,6 @@ class Header extends React.Component {
                 <li className="nav-item"><Link to='/matches'>Matches</Link></li>
                 <li className="nav-item"><Link to='/pdfs'>Listen</Link></li>
                 <li className="nav-item"><Link to='/sms'>SMS</Link></li>
-                <li className="nav-item"><Link to='/email'>Emails</Link></li>
             </ul>
         </nav>
     </header>

+ 0 - 4
client/src/matches/components/index.js

@@ -1,4 +0,0 @@
-import Matches from './Matches'
-
-export { Matches }
-export default { Matches }

+ 0 - 8
client/src/matches/index.js

@@ -1,8 +0,0 @@
-import { actions, reducer, state, saga } from './state'
-import components from './components'
-
-const filters = {}
-
-const selectors = {}
-
-export default { actions, components, filters, selectors, reducer, state, saga }

+ 0 - 207
client/src/matches/state.js

@@ -1,207 +0,0 @@
-/** @module matches/state */
-import { call, put, takeEvery, select } from 'redux-saga/effects'
-import moment from 'moment'
-import { SZTM_API } from '../local-constants'
-
-/**
- * state.js
- *
- * Collection of everything which has to do with state changes.
- **/
-
-/** actionTypes define what actions are handeled by the reducer. */
-export const actions = {
-  matchesSetFilter: (filter) => {
-    return {
-      type: 'MATCHES/SET_FILTER',
-      filter
-    }
-  },
-  matchesFilterApplied: (filteredMatches, filteredPlayers) => {
-    return {
-      type: 'MATCHES/FILTER_APPLIED',
-      filteredMatches, 
-      filteredPlayers
-    }
-  },
-  matchesGetMatchesRequest: (data) => {
-    return {
-      type: 'MATCHES/GET_MATCHES_REQUEST',
-      data
-    }
-  },
-  matchesGetMatchesSuccess: (data) => {
-    return {
-      type: 'MATCHES/GET_MATCHES_SUCCESS',
-      data
-    }
-  },
-  matchesGetMatchesFailure: (error) => {
-    return {
-      type: 'MATCHES/GET_MATCHES_FAILURE',
-      error
-    }
-  },
-  matchesGetPlayersRequest: (data) => {
-    return {
-      type: 'MATCHES/GET_PLAYERS_REQUEST',
-      data
-    }
-  },
-  matchesGetPlayersSuccess: (data) => {
-    return {
-      type: 'MATCHES/GET_PLAYERS_SUCCESS',
-      data
-    }
-  },
-  matchesGetPlayersFailure: (error) => {
-    return {
-      type: 'MATCHES/GET_PLAYERS_FAILURE',
-      error
-    }
-  },
-}
-console.log('State actions', actions)
-const emptyFilter = {
-  category: '',
-  date: '',
-  time: '',
-  place: '',
-  player: '',
-  result: '',
-  junior: '',
-  paid: '',
-  matchfilter: '',
-}
-
-/** state definition */
-export const state = {
-  matches: [],
-  players: [],
-  filteredMatches: [],
-  filteredPlayers: [],
-  filter: emptyFilter,
-  stats: {},
-  filteredStats: {},
-  matchGetRequested: false,
-}
-console.log('State state', state)
-
-/** reducer is called by the redux dispatcher and handles all component actions */
-export function reducer (state = [], action) {
-  switch (action.type) {
-    case 'MATCHES/SET_FILTER':
-      return { ...state, filter: { ...state.filter, ...action.filter } }
-    case 'MATCHES/FILTER_APPLIED':
-      return { ...state, filteredMatches: action.filteredMatches, filteredPlayers: action.filteredPlayers }
-    case 'MATCHES/GET_MATCHES_REQUEST':
-      return { ...state, matchGetRequested: true }
-    case 'MATCHES/GET_MATCHES_SUCCESS':
-      return { ...state, matches: action.data, matchGetRequested: false }
-    case 'MATCHES/GET_MATCHES_FAILURE':
-      return { ...state, matchGetRequested: false }
-    case 'MATCHES/GET_PLAYERS_REQUEST':
-      return { ...state, matchGetRequested: true }
-    case 'MATCHES/GET_PLAYERS_SUCCESS':
-      return { ...state, players: action.data, matchGetRequested: false }
-    case 'MATCHES/GET_PLAYERS_FAILURE':
-      return { ...state, matchGetRequested: false }
-    default:
-      return state
-  }
-}
-
-function * getMatches (action) {
-  try {
-    const token = localStorage.getItem('accessToken')
-    console.log('Get matches requested', action, token)
-    const { data } = action
-    
-    const queryParams = Object.keys(data || {})
-      .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(data[k]))
-      .join('&')
-    const response = yield call(fetch, `${SZTM_API}/api/sztm/schedule?${queryParams}`, {
-      method: 'GET',
-      headers: {
-        'Content-Type': 'application/json',
-        'x-access-token': token
-      }
-    })
-    if (response.status != 200) {
-      throw Error(yield response.json())
-    } else {
-      const responseJson = yield response.json()
-      console.log(responseJson)
-      yield put(actions.matchesGetMatchesSuccess(responseJson.matches))
-      yield put(actions.matchesSetFilter({}))
-    }
-  } catch (error) {
-    console.log('Matches failure!', actions.matchesGetMatchesFailure(error))
-    yield put(actions.matchesGetMatchesFailure(error))
-  }
-}
-
-function * getPlayers (action) {
-  try {
-    const token = localStorage.getItem('accessToken')
-    console.log('Get players requested', action, token)
-    const { data } = action
-    
-    const queryParams = Object.keys(data || {})
-      .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(data[k]))
-      .join('&')
-    const response = yield call(fetch, `${SZTM_API}/api/sztm/players?${queryParams}`, {
-      method: 'GET',
-      headers: {
-        'Content-Type': 'application/json',
-        'x-access-token': token
-      }
-    })
-    if (response.status != 200) {
-      throw Error(response.status)
-    } else {
-      const responseJson = yield response.json()
-      console.log(responseJson)
-      yield put(actions.matchesGetPlayersSuccess(responseJson.players))
-      yield put(actions.matchesSetFilter({}))
-    }
-  } catch (error) {
-    console.log('Players failure!', actions.matchesGetPlayersFailure(error))
-    yield put(actions.matchesGetPlayersFailure(error))
-  }
-}
-
-function * setFilter (action) {
-  const state = yield select()
-  const { matches, players, filter } = state.matches
-  const filteredMatches = matches.filter(match => {
-    return (
-      (filter.category ? match.category.includes(filter.category) : true) && 
-      (filter.place ? match.place.includes(filter.place) : true) && 
-      (filter.date ? moment(match.date).format('DD.MM.YYYY').includes(filter.date) : true) &&
-      (filter.time ? moment(match.date).format('HH:mm').includes(filter.time) : true) &&
-      (filter.player ? 
-        (match.player1 && match.player1.fullName.includes(filter.player)) || (match.player2 && match.player2.fullName.includes(filter.player)) :
-        true)
-    )
-  })
-
-  const filteredPlayers = players.filter(player => {
-    return (
-      (filter.player ? player.fullName.includes(filter.player) : true) &&
-      (filter.category ? player.category.includes(filter.category) : true) &&
-      (filter.junior ? player.junior : true) &&
-      (filter.paid ? player.paid : true)
-    )
-  })
-  
-  yield put(actions.matchesFilterApplied(filteredMatches, filteredPlayers))
-}
-
-/** sagas are asynchronous workers (JS generators) to handle the state. */
-export function * saga () {
-  console.log('Config saga started.')
-  yield takeEvery('MATCHES/GET_MATCHES_REQUEST', getMatches)
-  yield takeEvery('MATCHES/GET_PLAYERS_REQUEST', getPlayers)
-  yield takeEvery('MATCHES/SET_FILTER', setFilter)
-}

+ 3 - 3
client/src/routes.js

@@ -1,10 +1,10 @@
 import React from 'react'
 import { Switch, Route } from 'react-router'
 import UserList from './users/components/UserList'
-import LoginForm from './users/components/LoginForm'
+import LoginForm from './api/components/Login'
 import ConfigList from './config/components/ConfigList'
-import Matches from './matches/components/Matches'
-import Players from './matches/components/Players'
+import Matches from './swisstennis/components/Matches'
+import Players from './swisstennis/components/Players'
 import Swisstennis from './swisstennis/components/Swisstennis'
 import SMS from './sms/components/SMS'
 

+ 0 - 1
client/src/sms/components/SMS.js

@@ -38,7 +38,6 @@ class SMS extends React.Component {
 
   render () {
     const state = this.props.sms
-    const actions = this.props.smsActions
     const { sender, body, newRecipient } = state
 
     return (

+ 1 - 1
client/src/sms/state.js

@@ -131,7 +131,7 @@ function * sendSMS (action) {
         sender
       })
     })
-    if (response.status != 200) {
+    if (response.status !== 200) {
       console.log(response)
       throw Error(yield response.json())
     } else {

+ 11 - 18
client/src/matches/components/Matches.js → client/src/swisstennis/components/Matches.js

@@ -8,31 +8,24 @@ class Matches extends React.Component {
     this.handleChange = this.handleChange.bind(this)
   }
 
-  componentDidMount () {
-    console.log('Matches did mount', this)
-    const { matchesGetMatchesRequest } = this.props.matchesActions
-    matchesGetMatchesRequest()
-  }
-
   handleChange (event) {
     event.preventDefault()
-    const { matchesSetFilter } = this.props.matchesActions
+    const { filterMatches } = this.props.swisstennisActions
     const nextFilter = {
-      category: this.category.value,
-      date: this.date.value,
-      time: this.time.value,
-      place: this.place.value,
-      player: this.player.value,
-      result: this.result.value
+      m_category: this.category.value,
+      m_date: this.date.value,
+      m_time: this.time.value,
+      m_place: this.place.value,
+      m_player: this.player.value,
+      m_result: this.result.value
     }
-    matchesSetFilter(nextFilter)
+    filterMatches(nextFilter)
   }
 
   render () {
-    const state = this.props.matches
-    const actions = this.props.matchesActions
+    const state = this.props.swisstennis
     const { setRecipients } = this.props.smsActions
-    const { matches, filteredMatches, filter } = state
+    const { calendar, filteredMatches, filter } = state
     const participatingPlayers = []
     filteredMatches.forEach(match => {
       if (match.player1) participatingPlayers.push(match.player1)
@@ -41,7 +34,7 @@ class Matches extends React.Component {
 
     return (
       <div>
-        <p>{filteredMatches.length}/{matches.length} Spiele, {participatingPlayers.length} Spieler > <Link to="/sms" onClick={() => setRecipients(participatingPlayers)}>SMS</Link></p>
+        <p>{filteredMatches.length}/{calendar && calendar.matches.length} Spiele, {participatingPlayers.length} Spieler > <Link to="/sms" onClick={() => setRecipients(participatingPlayers)}>SMS</Link></p>
         <form>
           <table className='table table-bordered table-striped'>
             <thead>

+ 23 - 22
client/src/matches/components/Players.js → client/src/swisstennis/components/Players.js

@@ -1,5 +1,5 @@
 import React from 'react'
-import moment from 'moment'
+import { Link } from 'react-router-dom'
 
 class Players extends React.Component {
   constructor() {
@@ -7,52 +7,52 @@ class Players extends React.Component {
     this.handleChange = this.handleChange.bind(this)
   }
 
-  componentDidMount () {
-    console.log('Players did mount', this)
-    const { matchesGetPlayersRequest } = this.props.matchesActions
-    matchesGetPlayersRequest()
-  }
-
   handleChange (event) {
     event.preventDefault()
-    const { matchesSetFilter } = this.props.matchesActions
+    const { filterPlayers } = this.props.swisstennisActions
     const nextFilter = {
-      category: this.category.value,
-      player: this.player.value,
-      junior: this.junior.checked,
-      paid: this.paid.checked,
-      matchfilter: this.matchfilter.checked
+      p_name: this.name.value,
+      p_category: this.category.value,
+      p_ranking: this.ranking.value,
+      p_phone: this.phone.value,
+      p_email: this.email.value,
+      p_junior: this.junior.checked,
+      p_paid: this.paid.checked,
     }
-    matchesSetFilter(nextFilter)
+    filterPlayers(nextFilter)
   }
 
   render () {
-    const state = this.props.matches
-    console.log('sali', state)
-    const actions = this.props.matchesActions
-    const { players, filteredPlayers, filter } = state
+    const state = this.props.swisstennis
+    const { setRecipients } = this.props.smsActions
+    const { playerList, filteredPlayers, filter } = state
 
     return (
       <div>
         <form>
-          <p>{filteredPlayers.length}/{players.length} Spieler, {}/{} Kategorien, {}/{} Daten, {}/{} Zeiten, {}/{} Orte</p>
-          <label for="matchfilter">Match Filter benutzen</label>
+          <p>{filteredPlayers.length}/{playerList && playerList.players.length} Spieler > <Link to="/sms" onClick={() => setRecipients(filteredPlayers)}>SMS</Link></p>
+          <label htmlFor="matchfilter">Match Filter benutzen</label>
           <input type="checkbox" ref={(input) => {this.matchfilter = input}} id="matchfilter" checked={filter.matchfilter} onChange={this.handleChange} />
           <table className='table table-bordered table-striped'>
             <thead>
               <tr>
-                <th>Name</th><th>Kategorie</th><th>Telefon</th><th>E-Mail</th><th>Junior</th><th>Bezahlt</th>
+                <th>Name</th><th>Klassierung</th><th>Kategorie</th><th>Telefon</th><th>E-Mail</th><th>Junior</th><th>Bezahlt</th>
               </tr>
               <tr>
                 <td>
-                  <input type="input" ref={(input) => {this.player = input}} id="player" value={filter.player} placeholder="Name" onChange={this.handleChange}></input>
+                  <input type="input" ref={(input) => {this.name = input}} id="name" value={filter.name} placeholder="Name" onChange={this.handleChange}></input>
+                </td>
+                <td>
+                  <input type="input" ref={(input) => {this.ranking = input}} id="ranking" value={filter.ranking} placeholder="Name" onChange={this.handleChange}></input>
                 </td>
                 <td>
                   <input type="input" ref={(input) => {this.category = input}} id="category" value={filter.category} placeholder="Datum" onChange={this.handleChange}></input>
                 </td>
                 <td>
+                  <input type="input" ref={(input) => {this.phone = input}} id="phone" value={filter.phone} placeholder="Datum" onChange={this.handleChange}></input>
                 </td>
                 <td>
+                  <input type="input" ref={(input) => {this.email = input}} id="email" value={filter.email} placeholder="Datum" onChange={this.handleChange}></input>
                 </td>
                 <td>
                   <input type="checkbox" ref={(input) => {this.junior = input}} id="junior" checked={filter.junior} placeholder="Spieler" onChange={this.handleChange}></input>
@@ -66,6 +66,7 @@ class Players extends React.Component {
               {filteredPlayers.map((player, key) => 
               <tr key={key}>
                 <td>{player.fullName}</td>
+                <td>{player.ranking}</td>
                 <td>{player.category}</td>
                 <td>{player.phone}</td>
                 <td>{player.email}</td>

+ 33 - 24
client/src/swisstennis/components/Swisstennis.js

@@ -8,21 +8,12 @@ class Swisstennis extends React.Component {
     this.initDownload = this.initDownload.bind(this)
   }
 
-  componentDidMount () {
-    console.log('Swisstennis did mount', this)
-    const { fileListRequest, getCalendarRequest, getPlayerListRequest } = this.props.swisstennisActions
-    fileListRequest()
-    getCalendarRequest()
-    getPlayerListRequest()
-  }
-
   handleChange (event) {
     event.preventDefault()
   }
 
   initDownload (event) {
     event.preventDefault()
-    const state = this.props.swisstennis
     const { loginRequest } = this.props.swisstennisActions
     loginRequest({ sequence: true })
   }
@@ -85,20 +76,20 @@ class Swisstennis extends React.Component {
             </tr>
           </thead>
           <tbody>
-            {state.files.map((file, key) => {
-              return 
-            (<tr key={key}>
+            {state.files.map((file, key) => (
+            <tr key={key}>
               <td>{file.filename}</td>
               <td>{file.size/1024}kB</td>
               <td>{moment(file.ctime).format('DD.MM.YYYY HH:mm')}</td>
-              <td>{(state.calendars.find(matchList => matchList.file == file.filename) || 
-                           state.playerLists.find(playerList => playerList.file == file.filename)) ? 'Ja' : 'Nein'}</td>
+              <td>{(state.calendars.find(matchList => matchList.file === file.filename) || state.playerLists.find(playerList => playerList.file === file.filename)) ? 'Ja' : 'Nein'}</td>
               <td>
-                <a>benutzen</a>
-                <a>loeschen</a>
+                <a onClick={(event) => {
+                  event.preventDefault()
+                  if (file.filename.includes('Calendar')) actions.parseCalendarRequest({filename: file.filename})
+                  if (file.filename.includes('PlayerList')) actions.parsePlayerListRequest({filename: file.filename})
+                }}>parsen</a> <a>loeschen</a>
               </td>
-            </tr>)
-            })}
+            </tr>))}
           </tbody>
         </table>
 
@@ -106,13 +97,22 @@ class Swisstennis extends React.Component {
         <table className='table table-bordered table-striped'>
           <thead>
             <tr>
-              <th>Datei</th><th>Datum</th><th>Matches</th><th>Aktionen</th>
+              <th>Datei</th><th>Datum</th><th>Aktiv</th><th>Aktionen</th>
             </tr>
           </thead>
           <tbody>
-            {state.calendars.sort((a, b) => a.imported < b.imported).map((calendar, key) => (
+            {state.calendars && state.calendars.sort((a, b) => a.imported < b.imported).map((calendar, key) => (
             <tr key={key}>
-              <td>{calendar.file}</td><td>{moment(calendar.imported).format('DD.MM.YYYY HH:mm')}</td><td>{calendar.matches.length}</td><td><a>verwenden</a><a>loeschen</a></td>
+              <td>{calendar.file}</td>
+              <td>{moment(calendar.imported).format('DD.MM.YYYY HH:mm')}</td>
+              <td>{state.calendar && (state.calendar._id === calendar._id) ? 'Ja' : 'Nein'}</td>
+              <td><a onClick={(event) => {
+                event.preventDefault()
+                actions.getCalendarRequest({calendarId: calendar._id})
+              }}>verwenden</a> <a onClick={(event) => {
+                event.preventDefault()
+                actions.deleteCalendarRequest({calendarId: calendar._id})
+              }}>loeschen</a></td>
             </tr>
             ))}
           </tbody>
@@ -122,13 +122,22 @@ class Swisstennis extends React.Component {
         <table className='table table-bordered table-striped'>
           <thead>
             <tr>
-              <th>Datei</th><th>Datum</th><th>Spieler</th><th>Aktionen</th>
+              <th>Datei</th><th>Datum</th><th>Aktiv</th><th>Aktionen</th>
             </tr>
           </thead>
           <tbody>
-            {state.playerLists.sort((a, b) => a.imported < b.imported).map((playerList, key) => (
+            {state.playerLists && state.playerLists.sort((a, b) => a.imported < b.imported).map((playerList, key) => (
             <tr key={key}>
-              <td>{playerList.file}</td><td>{moment(playerList.imported).format('DD.MM.YYYY HH:mm')}</td><td>{playerList.players.length}</td><td><a>verwenden</a><a>loeschen</a></td>
+              <td>{playerList.file}</td>
+              <td>{moment(playerList.imported).format('DD.MM.YYYY HH:mm')}</td>
+              <td>{state.playerList && (state.playerList._id === playerList._id) ? 'Ja' : 'Nein'}</td>
+              <td><a onClick={(event) => {
+                event.preventDefault()
+                actions.getPlayerListRequest({playerListId: playerList._id})
+              }}>verwenden</a> <a onClick={(event) => {
+                event.preventDefault()
+                actions.deletePlayerListRequest({playerlistId: playerList._id})
+              }}>loeschen</a></td>
             </tr>
             ))}
           </tbody>

+ 278 - 91
client/src/swisstennis/state.js

@@ -1,6 +1,8 @@
 /** @module swisstennis/state */
-import { call, put, takeEvery } from 'redux-saga/effects'
+import { call, put, takeEvery, select } from 'redux-saga/effects'
 import { SZTM_API } from '../local-constants'
+import api from '../api'
+import moment from 'moment'
 
 /**
  * state.js
@@ -10,6 +12,36 @@ import { SZTM_API } from '../local-constants'
 
 /** actionTypes define what actions are handeled by the reducer. */
 export const actions = {
+  matchesSetFilter: (data) => {
+    return {
+      type: 'SWISSTENNIS/SET_FILTER',
+      data
+    }
+  },
+  filterMatches: (data) => {
+    return {
+      type: 'SWISSTENNIS/FILTER_MATCHES',
+      data
+    }
+  },
+  filterMatchesDone: (data) => {
+    return {
+      type: 'SWISSTENNIS/FILTER_MATCHES_DONE',
+      data
+    }
+  },
+  filterPlayers: (data) => {
+    return {
+      type: 'SWISSTENNIS/FILTER_PLAYERS',
+      data
+    }
+  },
+  filterPlayersDone: (data) => {
+    return {
+      type: 'SWISSTENNIS/FILTER_PLAYERS_DONE',
+      data
+    }
+  },
   fileListRequest: () => {
     return {
       type: 'SWISSTENNIS/FILE_LIST_REQUEST',
@@ -81,6 +113,24 @@ export const actions = {
       error
     }
   },
+  deletePlayerListRequest: (data) => {
+    return {
+      type: 'SWISSTENNIS/DELETE_PLAYERLIST_REQUEST',
+      data
+    }
+  },
+  deletePlayerListSuccess: (data) => {
+    return {
+      type: 'SWISSTENNIS/DELETE_PLAYERLIST_SUCCESS',
+      data
+    }
+  },
+  deletePlayerListFailure: error => {
+    return {
+      type: 'SWISSTENNIS/DELETE_PLAYERLIST_FAILURE',
+      error
+    }
+  },
   downloadCalendarRequest: (data) => {
     return {
       type: 'SWISSTENNIS/DOWNLOAD_CALENDAR_REQUEST',
@@ -117,6 +167,24 @@ export const actions = {
       error
     }
   },
+  deleteCalendarRequest: data => {
+    return {
+      type: 'SWISSTENNIS/DELETE_CALENDAR_REQUEST',
+      data
+    }
+  },
+  deleteCalendarSuccess: data => {
+    return {
+      type: 'SWISSTENNIS/DELETE_CALENDAR_SUCCESS',
+      data
+    }
+  },
+  deleteCalendarFailure: error => {
+    return {
+      type: 'SWISSTENNIS/DELETE_CALENDAR_FAILURE',
+      error
+    }
+  },
   getPlayerListRequest: () => {
     return {
       type: 'SWISSTENNIS/GET_PLAYERLIST_REQUEST'
@@ -134,6 +202,23 @@ export const actions = {
       error
     }
   },
+  getPlayerListsRequest: () => {
+    return {
+      type: 'SWISSTENNIS/GET_PLAYERLISTS_REQUEST'
+    }
+  },
+  getPlayerListsSuccess: (data) => {
+    return {
+      type: 'SWISSTENNIS/GET_PLAYERLISTS_SUCCESS',
+      data
+    }
+  },
+  getPlayerListsFailure: error => {
+    return {
+      type: 'SWISSTENNIS/GET_PLAYERLISTS_FAILURE',
+      error
+    }
+  },
   getCalendarRequest: (data) => {
     return {
       type: 'SWISSTENNIS/GET_CALENDAR_REQUEST',
@@ -152,28 +237,83 @@ export const actions = {
       error
     }
   },
+  getCalendarsRequest: (data) => {
+    return {
+      type: 'SWISSTENNIS/GET_CALENDARS_REQUEST',
+      data
+    }
+  },
+  getCalendarsSuccess: (data) => {
+    return {
+      type: 'SWISSTENNIS/GET_CALENDARS_SUCCESS',
+      data
+    }
+  },
+  getCalendarsFailure: error => {
+    return {
+      type: 'SWISSTENNIS/GET_CALENDARS_FAILURE',
+      error
+    }
+  },
 }
 console.log('State actions', actions)
 
 /** state definition */
+const emptyFilter = {
+  m_category: '',
+  m_date: '',
+  m_time: '',
+  m_place: '',
+  m_player: '',
+  m_result: '',
+  m_players: false,
+  p_name: '',
+  p_category: '',
+  p_ranking: '',
+  p_phone: '',
+  p_junior: 'off',
+  p_paid: 'off',
+  p_matches: false,
+}
+
 export const state = {
   files: [],
   fileListStatus: 'uninitialized',
   calendars: [],
+  playerList: null,
   calendarsStatus: 'uninitialized',
   playerLists: [],
+  calendar: null,
   playerListsStatus: 'uninitialized',
   playerListDownloadStatus: 'uninitialized',
   playerListParseStatus: 'uninitialized',
+  playerListDeleteStatus: 'uninitialized',
   calendarDownloadStatus: 'uninitialized',
   calendarParseStatus: 'uninitialized',
+  calendarDeleteStatus: 'uninitialized',
   loginStatus: 'uninitialized',
+  filteredMatches: [],
+  filteredPlayers: [],
+  filter: emptyFilter,
+  stats: {},
+  filteredStats: {},
 }
 console.log('State state', state)
 
 /** reducer is called by the redux dispatcher and handles all component actions */
 export function reducer (state = [], action) {
+  let index
   switch (action.type) {
+    case 'SWISSTENNIS/SET_FILTER':
+      return { ...state, filter: { ...state.filter, ...action.data } }
+    case 'SWISSTENNIS/FILTER_PLAYERS':
+      return { ...state }
+    case 'SWISSTENNIS/FILTER_PLAYERS_DONE':
+      return { ...state, ...action.data }
+    case 'SWISSTENNIS/FILTER_MATCHES':
+      return { ...state }
+    case 'SWISSTENNIS/FILTER_MATCHES_DONE':
+      return { ...state, ...action.data }
     case 'SWISSTENNIS/FILE_LIST_REQUEST':
       return { ...state, fileListStatus: 'request' }
     case 'SWISSTENNIS/FILE_LIST_SUCCESS':
@@ -204,6 +344,13 @@ export function reducer (state = [], action) {
       return { ...state, playerListParseStatus: 'success', playerLists: [ ...state.playerLists, action.data.playerList ] }
     case 'SWISSTENNIS/PARSE_PLAYERLIST_FAILURE':
       return { ...state, playerListParseStatus: 'failure' }
+    case 'SWISSTENNIS/DELETE_PLAYERLIST_REQUEST':
+      return { ...state, playerListDeleteStatus: 'request' }
+    case 'SWISSTENNIS/DELETE_PLAYERLIST_SUCCESS':
+      index = state.playerLists.findIndex(playerList => playerList._id === action.data._id)
+      return { ...state, playerListDeleteStatus: 'success', playerLists: [ ...state.playerLists.slice(0, index), ...state.playerLists.slice(index + 1) ] }
+    case 'SWISSTENNIS/DELETE_PLAYERLIST_FAILURE':
+      return { ...state, playerListDeleteStatus: 'failure' }
     case 'SWISSTENNIS/DOWNLOAD_CALENDAR_REQUEST':
       return { ...state, calendarDownloadStatus: 'request' }
     case 'SWISSTENNIS/DOWNLOAD_CALENDAR_SUCCESS':
@@ -216,45 +363,85 @@ export function reducer (state = [], action) {
       return { ...state, calendarParseStatus: 'success', calendars: [ ...state.calendars, action.data.matchList ] }
     case 'SWISSTENNIS/PARSE_CALENDAR_FAILURE':
       return { ...state, calendarParseStatus: 'failure' }
+    case 'SWISSTENNIS/DELETE_CALENDAR_REQUEST':
+      return { ...state, calendarDeleteStatus: 'request' }
+    case 'SWISSTENNIS/DELETE_CALENDAR_SUCCESS':
+      index = state.calendars.findIndex(calendar => calendar._id === action.data._id)
+      return { ...state, calendarDeleteStatus: 'success', calendars: [ ...state.calendars.slice(0, index), ...state.calendars.slice(index + 1) ] }
+    case 'SWISSTENNIS/DELETE_CALENDAR_FAILURE':
+      return { ...state, calendarDeleteStatus: 'failure' }
     case 'SWISSTENNIS/GET_CALENDAR_REQUEST':
       return { ...state, calendarsStatus: 'loading' }
     case 'SWISSTENNIS/GET_CALENDAR_SUCCESS':
-      return { ...state, calendarsStatus: 'loaded', calendars: action.data }
+      return { ...state, calendarsStatus: 'loaded', calendar: action.data }
     case 'SWISSTENNIS/GET_CALENDAR_FAILURE':
       return { ...state, calendarsStatus: 'failed' }
-      case 'SWISSTENNIS/GET_PLAYERLIST_REQUEST':
-        return { ...state, playerListsStatus: 'loading' }
-      case 'SWISSTENNIS/GET_PLAYERLIST_SUCCESS':
-        return { ...state, playerListsStatus: 'loaded', playerLists: action.data }
-      case 'SWISSTENNIS/GET_PLAYERLIST_FAILURE':
-        return { ...state, playerListsStatus: 'failed' }
+    case 'SWISSTENNIS/GET_CALENDARS_REQUEST':
+      return { ...state, calendarsStatus: 'loading' }
+    case 'SWISSTENNIS/GET_CALENDARS_SUCCESS':
+      return { ...state, calendarsStatus: 'loaded', calendars: action.data }
+    case 'SWISSTENNIS/GET_CALENDARS_FAILURE':
+      return { ...state, calendarsStatus: 'failed' }
+    case 'SWISSTENNIS/GET_PLAYERLIST_REQUEST':
+      return { ...state, playerListsStatus: 'loading' }
+    case 'SWISSTENNIS/GET_PLAYERLIST_SUCCESS':
+      return { ...state, playerListsStatus: 'loaded', playerList: action.data }
+    case 'SWISSTENNIS/GET_PLAYERLIST_FAILURE':
+      return { ...state, playerListsStatus: 'failed' }
+    case 'SWISSTENNIS/GET_PLAYERLISTS_REQUEST':
+      return { ...state, playerListsStatus: 'loading' }
+    case 'SWISSTENNIS/GET_PLAYERLISTS_SUCCESS':
+      return { ...state, playerListsStatus: 'loaded', playerLists: action.data }
+    case 'SWISSTENNIS/GET_PLAYERLISTS_FAILURE':
+      return { ...state, playerListsStatus: 'failed' }
     default:
       return state
   }
 }
 
+function * filterPlayers (action) {
+  const state = yield select()
+  const { playerList, calendar } = state.swisstennis
+  const filter = action.data
+  const filteredPlayers = playerList ? playerList.players.filter(player => {
+    return (
+      (filter.p_name ? player.fullName && player.fullName.toLowerCase().includes(filter.p_name.toLowerCase()) : true) && 
+      (filter.p_category ? player.category && player.category.toLowerCase().includes(filter.p_category.toLowerCase()) : true) && 
+      (filter.p_ranking ? player.ranking && player.ranking.toLowerCase().includes(filter.p_ranking.toLowerCase()) : true) && 
+      (filter.p_phone ? player.phone && player.phone.toLowerCase().includes(filter.p_phone.toLowerCase()) : true) &&
+      /*(filter.p_junior !== 'off' ? player.junior === (filter.p_junior === 'Ja') : true) &&
+      (filter.p_paid !== 'off' ? player.paid === (filter.p_paid === 'Ja') : true)*/ true
+    )
+  }) : []
+  yield put(actions.filterPlayersDone({filteredPlayers}))
+}
+
+function * filterMatches (action) {
+  const state = yield select()
+  const { playerList, calendar } = state.swisstennis
+  const filter = action.data
+  const filteredMatches = calendar ? calendar.matches.filter(match => {
+    return (
+      (filter.m_category ? match.category && match.category.toLowerCase().includes(filter.m_category.toLowerCase()) : true) && 
+      (filter.m_place ? match.place && match.place.toLowerCase().includes(filter.m_place.toLowerCase()) : true) && 
+      (filter.m_date ? match.date && moment(match.date).format('DD.MM.YYYY').includes(filter.m_date) : true) &&
+      (filter.m_time ? match.date && moment(match.date).format('HH:mm').includes(filter.m_time) : true) &&
+      (filter.m_player ? 
+        (match.player1 && match.player1.fullName.toLowerCase().includes(filter.m_player.toLowerCase())) || (match.player2 && match.player2.fullName.toLowerCase().includes(filter.m_player.toLowerCase())) :
+        true) &&
+      (filter.m_result ? match.result.toLowerCase().includes(filter.m_result) : true)
+    )
+  }) : []
+  yield put(actions.filterMatchesDone({filteredMatches}))
+}
+
 function * fileList (action) {
-  try {
-    const token = localStorage.getItem('accessToken')
-    console.log('File list requested', action, token)
-    const response = yield call(fetch, `${SZTM_API}/api/swisstennis/files`, {
-      method: 'GET',
-      headers: {
-        'Content-Type': 'application/json',
-        'x-access-token': token
-      }
-    })
-    if (response.status != 200) {
-      console.log(response)
-      throw Error(response.status)
-    } else {
-      const responseJson = yield response.json()
-      yield put(actions.fileListSuccess(responseJson.fileList))
-    }
-  } catch (error) {
-    console.log('Config failure!', actions.fileListFailure(error))
-    yield put(actions.fileListFailure(error))
-  }
+  yield put(api.actions.apiRequest({
+    path: `api/swisstennis/files`,
+    method: 'GET',
+    onSuccess: actions.fileListSuccess,
+    onFailure: actions.fileListFailure
+  }))
 }
 
 function * login (action) {
@@ -283,26 +470,21 @@ function * login (action) {
 }
 
 function * getCalendars (action) {
-  try {
-    const token = localStorage.getItem('accessToken')
-    console.log('Get config requested', action, token)
-    const response = yield call(fetch, `${SZTM_API}/api/swisstennis/calendar`, {
-      method: 'GET',
-      headers: {
-        'Content-Type': 'application/json',
-        'x-access-token': token
-      }
-    }) 
-    if (response.status != 200) {
-      throw Error(response.status)
-    } else {
-      const responseJson = yield response.json()
-      yield put(actions.getCalendarSuccess(responseJson.calendars))
-    }
-  } catch (error) {
-    console.log('Config failure!', actions.getCalendarFailure(error))
-    yield put(actions.getCalendarFailure(error))
-  }
+  yield put(api.actions.apiRequest({
+    path: `api/swisstennis/calendars`,
+    method: 'GET',
+    onSuccess: actions.getCalendarsSuccess,
+    onFailure: actions.getCalendarsFailure
+  }))
+}
+
+function * getCalendar (action) {
+  yield put(api.actions.apiRequest({
+    path: `api/swisstennis/calendar${(action.data && action.data.calendarId) ? `?calendarId=${action.data.calendarId}` : ''}`,
+    method: 'GET',
+    onSuccess: [actions.getCalendarSuccess, actions.filterMatches],
+    onFailure: actions.getCalendarFailure
+  }))
 }
 
 function * downloadCalendar (action) {
@@ -330,49 +512,39 @@ function * downloadCalendar (action) {
 }
 
 function * parseCalendar (action) {
-  try {
-    const token = localStorage.getItem('accessToken')
-    console.log('Download calendar requested', action, token)
-    const response = yield call(fetch, `${SZTM_API}/api/swisstennis/calendar/parse/${action.data.filename}`, {
-      method: 'GET',
-      headers: {
-        'Content-Type': 'application/json',
-        'x-access-token': token
-      }
-    }) 
-    if (response.status != 200) {
-      throw Error(response.status)
-    } else {
-      const responseJson = yield response.json()
-      yield put(actions.parseCalendarSuccess(responseJson))
-    }
-  } catch (error) {
-    console.log('Config failure!', actions.parseCalendarFailure(error))
-    yield put(actions.parseCalendarFailure(error))
-  }
+  yield put(api.actions.apiRequest({
+    path: `api/swisstennis/calendar/parse/${action.data.filename}`,
+    method: 'GET',
+    onSuccess: actions.parseCalendarSuccess,
+    onFailure: actions.parseCalendarFailure
+  }))
+}
+
+function * deleteCalendar (action) {
+  yield put(api.actions.apiRequest({
+    path: `api/swisstennis/calendar/${action.data.calendarId}`,
+    method: 'DELETE',
+    onSuccess: actions.deleteCalendarSuccess,
+    onFailure: actions.deleteCalendarFailure
+  }))
 }
 
 function * getPlayerLists (action) {
-  try {
-    const token = localStorage.getItem('accessToken')
-    console.log('Get config requested', action, token)
-    const response = yield call(fetch, `${SZTM_API}/api/swisstennis/playerlist`, {
-      method: 'GET',
-      headers: {
-        'Content-Type': 'application/json',
-        'x-access-token': token
-      }
-    }) 
-    if (response.status != 200) {
-      throw Error(response.status)
-    } else {
-      const responseJson = yield response.json()
-      yield put(actions.getPlayerListSuccess(responseJson.playerLists))
-    }
-  } catch (error) {
-    console.log('Config failure!', actions.getPlayerListFailure(error))
-    yield put(actions.getPlayerListFailure(error))
-  }
+  yield put(api.actions.apiRequest({
+    path: `api/swisstennis/playerlists`,
+    method: 'GET',
+    onSuccess: actions.getPlayerListsSuccess,
+    onFailure: actions.getPlayerListsFailure
+  }))
+}
+
+function * getPlayerList (action) {
+  yield put(api.actions.apiRequest({
+    path: `api/swisstennis/playerlist${(action.data && action.data.playerlistId) ? `?playerlistId=${action.data.playerlistId}` : ''}`,
+    method: 'GET',
+    onSuccess: [actions.getPlayerListSuccess, actions.filterPlayers],
+    onFailure: actions.getPlayerListFailure
+  }))
 }
 
 function * downloadPlayerList (action) {
@@ -421,15 +593,30 @@ function * parsePlayerList (action) {
   }
 }
 
+function * deletePlayerList (action) {
+  yield put(api.actions.apiRequest({
+    path: `api/swisstennis/playerlist/${action.data.playerlistId}`,
+    method: 'DELETE',
+    onSuccess: actions.deletePlayerListSuccess,
+    onFailure: actions.deletePlayerListFailure
+  }))
+}
+
 /** sagas are asynchronous workers (JS generators) to handle the state. */
 export function * saga () {
   console.log('Config saga started.')
   yield takeEvery('SWISSTENNIS/FILE_LIST_REQUEST', fileList)
   yield takeEvery('SWISSTENNIS/DOWNLOAD_PLAYERLIST_REQUEST', downloadPlayerList)
   yield takeEvery('SWISSTENNIS/PARSE_PLAYERLIST_REQUEST', parsePlayerList)
+  yield takeEvery('SWISSTENNIS/DELETE_PLAYERLIST_REQUEST', deletePlayerList)
   yield takeEvery('SWISSTENNIS/DOWNLOAD_CALENDAR_REQUEST', downloadCalendar)
   yield takeEvery('SWISSTENNIS/PARSE_CALENDAR_REQUEST', parseCalendar)
-  yield takeEvery('SWISSTENNIS/GET_CALENDAR_REQUEST', getCalendars)
-  yield takeEvery('SWISSTENNIS/GET_PLAYERLIST_REQUEST', getPlayerLists)
+  yield takeEvery('SWISSTENNIS/DELETE_CALENDAR_REQUEST', deleteCalendar)
+  yield takeEvery('SWISSTENNIS/GET_CALENDAR_REQUEST', getCalendar)
+  yield takeEvery('SWISSTENNIS/GET_CALENDARS_REQUEST', getCalendars)
+  yield takeEvery('SWISSTENNIS/GET_PLAYERLIST_REQUEST', getPlayerList)
+  yield takeEvery('SWISSTENNIS/GET_PLAYERLISTS_REQUEST', getPlayerLists)
   yield takeEvery('SWISSTENNIS/LOGIN_REQUEST', login)
+  yield takeEvery('SWISSTENNIS/FILTER_PLAYERS', filterPlayers)
+  yield takeEvery('SWISSTENNIS/FILTER_MATCHES', filterMatches)
 }

+ 0 - 1
client/src/users/components/LoginForm.js

@@ -1,5 +1,4 @@
 import React from 'react'
-import { FormGroup, ControlLabel, FormControl, HelpBlock } from 'react-bootstrap'
 
 class LoginForm extends React.Component {
 

+ 0 - 7
client/src/users/components/UserList.js

@@ -19,12 +19,6 @@ class UserList extends React.Component {
     this.state = { user: emptyUser }
   }
 
-  componentDidMount () {
-    console.log('UserList did mount', this)
-    const { getUserList } = this.props.usersActions
-    getUserList()
-  }
-
   loadUser (key, event) {
     event.preventDefault()
     const { _id, name, username } = this.props.users.users[key]
@@ -74,7 +68,6 @@ class UserList extends React.Component {
 
   render () {
     const state = this.props.users
-    const actions = this.props.usersActions
     const { user } = this.state
 
     return (

+ 68 - 122
client/src/users/state.js

@@ -1,8 +1,6 @@
 /** @module users/state */
-import { call, put, takeEvery } from 'redux-saga/effects'
-import jwt from 'jwt-simple'
-import bcrypt from 'bcryptjs'
-import { SZTM_API } from '../local-constants'
+import { put, takeEvery } from 'redux-saga/effects'
+import api from '../api'
 
 /**
 * state.js
@@ -18,32 +16,34 @@ export const actions = {
             data
         }
     },
-    loginSuccess: (token, tokenData) => {
+    loginSuccess: (data) => {
         return {
             type: 'USER/LOGIN_SUCCESS',
-            token,
-            tokenData
+            data
         }
     },
-    loginFailure: () => {
+    loginFailure: (error) => {
         return {
-            type: 'USER/LOGIN_FAILURE'
+            type: 'USER/LOGIN_FAILURE',
+            error
         }
     },
-    getUserList: () => {
+    getUserListRequest: (data) => {
         return {
-            type: 'USER/GET_USER_LIST'
+            type: 'USER/GET_USER_LIST_REQUEST',
+            data
         }
     },
-    userListSuccess: (users) => {
+    getUserListSuccess: (data) => {
         return {
             type: 'USER/GET_USER_LIST_SUCCESS',
-            users
+            data
         }
     },
-    userListFailure: () => {
+    getUserListFailure: (error) => {
         return {
-            type: 'USER/GET_USER_LIST_FAILURE'
+            type: 'USER/GET_USER_LIST_FAILURE',
+            error
         }
     },
     addUserRequest: (data) => {
@@ -52,14 +52,16 @@ export const actions = {
             data
         }
     },
-    addUserSuccess: () => {
+    addUserSuccess: (data) => {
         return {
-            type: 'USER/ADD_USER_SUCCESS'
+            type: 'USER/ADD_USER_SUCCESS',
+            data
         }
     },
-    addUserFailure: () => {
+    addUserFailure: (error) => {
         return {
-            type: 'USER/ADD_USER_FAILURE'
+            type: 'USER/ADD_USER_FAILURE',
+            error
         }
     }
 }
@@ -77,31 +79,39 @@ export const state = {
     userListRequested: false,
     userListInitialized: false,
     users: [],
-    token: tokenValid ? token : null,
-    tokenData: tokenValid ? tokenData : null
+    token,
+    tokenData,
+    tokenValid
 }
 console.log('State state', state)
 
 /** reducer is called by the redux dispatcher and handles all component actions */
 export function reducer (state = [], action) {
+    let token
+    let tokenData
+    let tokenValid
     switch (action.type) {
         case 'USER/LOGIN_REQUEST':
             return { ...state, loginRequested: true }
         case 'USER/LOGIN_SUCCESS':
-            return { ...state, loginRequested: false, token: action.token, tokenData: action.tokenData }
+            localStorage.setItem('accessToken', action.data.token)
+            token = action.data.token
+            tokenData = token ? JSON.parse(localStorage.getItem('accessTokenData')) : null
+            tokenValid = tokenData ? tokenData.expires > Date.now() : false
+            return { ...state, loginRequested: false, token, tokenData, tokenValid }
         case 'USER/LOGIN_FAILURE':
             return { ...state, loginRequested: false }
-        case 'USER/GET_USER_LIST':
+        case 'USER/GET_USER_LIST_REQUEST':
             return { ...state, userListRequested: true, userListInitialized: true }
         case 'USER/GET_USER_LIST_SUCCESS':
-            return { ...state, userListRequested: false, users: action.users }
+            return { ...state, userListRequested: false, users: action.data }
         case 'USER/GET_USER_LIST_FAILURE':
             return { ...state, userListRequested: false }
-        case 'USER/GET_USER_LIST':
+        case 'USER/ADD_USER_REQUEST':
             return { ...state, userListRequested: true, userListInitialized: true }
-        case 'USER/GET_USER_LIST_SUCCESS':
-            return { ...state, userListRequested: false, users: action.users }
-        case 'USER/GET_USER_LIST_FAILURE':
+        case 'USER/ADD_USER_SUCCESS':
+            return { ...state, userListRequested: false, users: [ ...state.users, action.data ] }
+        case 'USER/ADD_USER_FAILRE':
             return { ...state, userListRequested: false }
         default:
             return state
@@ -109,113 +119,49 @@ export function reducer (state = [], action) {
 }
 
 function * login (action) {
-    try {
-        console.log('User login requested', action.data)
-        const response = yield call(fetch, `${SZTM_API}/authenticate/login`, {
-            method: 'POST',
-            headers: {
-                'Content-Type': 'application/json'
-            },
-            body: JSON.stringify(action.data)
-        })
-        const responseJson = yield response.json()
-        if (response.status != 200) {
-            console.log(responseJson.msg)
-            throw new Error(responseJson.msg)
-        }
-        const { token } = responseJson
-        const tokenData = jwt.decode(token, null, true)
-        localStorage.setItem('accessToken', token)
-        localStorage.setItem('accessTokenData', JSON.stringify(tokenData))
-        console.log('User login success!', token, tokenData)
-        yield put(actions.loginSuccess(token, tokenData))
-    } catch (error) {
-        console.log('User login failure!', error)
-        yield put(actions.loginFailure(error))
-    }
+    yield put(api.actions.apiRequest({
+      path: `authenticate/login`,
+      method: 'POST',
+      body: JSON.stringify(action.data),
+      onSuccess: actions.loginSuccess,
+      onFailure: actions.loginFailure
+    }))
 }
 
 function * getUserList (action) {
-    try {
-        const token = localStorage.getItem('accessToken')
-        console.log('User list requested', action, token)
-        const response = yield call(fetch, `${SZTM_API}/api/users`, {
-            method: 'GET',
-            headers: new Headers({
-                'Content-Type': 'application/json',
-                'x-access-token': token
-            })
-        })
-        console.log('Received response')
-        const responseJson = yield response.json()
-        if (response.status != 200) {
-            console.log('User list received error', responseJson.msg)
-            throw new Error(responseJson.msg)
-        }
-        console.log('Get user list success!', responseJson.users)
-        yield put(actions.userListSuccess(responseJson.users))
-    } catch (error) {
-        console.log('Get user list failure!', error)
-        yield put(actions.userListFailure(error.toString()))
-    }
+    yield put(api.actions.apiRequest({
+      path: `api/users`,
+      method: 'GET',
+      onSuccess: actions.getUserListSuccess,
+      onFailure: actions.getUserListFailure
+    }))
 }
 
 function * addUser (action) {
-    try {
-        const token = localStorage.getItem('accessToken')
-        console.log('Add user requested', action, token)
-        const response = yield call(fetch, `${SZTM_API}/api/users`, {
-            method: 'POST',
-            headers: new Headers({
-                'Content-Type': 'application/json',
-                'x-access-token': token
-            }),
-            body: JSON.stringify(action.data)
-        }) 
-        console.log('Received response')
-        const responseJson = yield response.json()
-        if (response.status != 200) {
-            console.log('Add user error', responseJson.msg)
-            throw new Error(responseJson.msg)
-        }
-        console.log('Add user success!', responseJson)
-        yield put(actions.addUserSuccess(responseJson))
-    } catch (error) {
-        console.log('Get user list failure!', error)
-        yield put(actions.addUserFailure(error.toString()))
-    }
+    yield put(api.actions.apiRequest({
+      path: `api/users`,
+      method: 'POST',
+      body: JSON.stringify(action.data),
+      onSuccess: actions.addUserSuccess,
+      onFailure: actions.addUserFailure
+    }))
 }
 
-function * saveUser (action) {
-    try {
-        const token = localStorage.getItem('accessToken')
-        console.log('Save user requested', action, token)
-        const response = yield call(fetch, `${SZTM_API}/api/users`, {
-            method: 'PUT',
-            headers: new Headers({
-                'Content-Type': 'application/json',
-                'x-access-token': token
-            }),
-            body: JSON.stringify(action.data)
-        }) 
-        console.log('Received response')
-        const responseJson = yield response.json()
-        if (response.status != 200) {
-            console.log('Add user error', responseJson.msg)
-            throw new Error(responseJson.msg)
-        }
-        console.log('Add user success!', responseJson)
-        yield put(actions.addUserSuccess(responseJson))
-    } catch (error) {
-        console.log('Get user list failure!', error)
-        yield put(actions.addUserFailure(error.toString()))
-    }
+function * editUser (action) {
+    yield put(api.actions.apiRequest({
+      path: `api/users`,
+      method: 'PUT',
+      body: JSON.stringify(action.data),
+      onSuccess: actions.editUserSuccess,
+      onFailure: actions.editUserFailure
+    }))
 }
 
 /** sagas are asynchronous workers (JS generators) to handle the state. */
 export function * saga () {
     console.log('User saga started.')
     yield takeEvery('USER/LOGIN_REQUEST', login)
-    yield takeEvery('USER/GET_USER_LIST', getUserList)
+    yield takeEvery('USER/GET_USER_LIST_REQUEST', getUserList)
     yield takeEvery('USER/ADD_USER_REQUEST', addUser)
+    yield takeEvery('USER/EDIT_USER_REQUEST', editUser)
 }

+ 1 - 1
server/src/restServer/models/match.js

@@ -2,7 +2,7 @@ import mongoose from 'mongoose'
 
 const MatchSchema = new mongoose.Schema({
   created: Date,
-  idString: { type: String, unique: true },
+  idString: String,
   fileLine: Number,
   category: String,
   place: String,

+ 1 - 1
server/src/restServer/models/matchList.js

@@ -3,7 +3,7 @@ import mongoose from 'mongoose'
 const MatchListSchema = new mongoose.Schema({
     imported: Date,
     parsed: Date,
-    file: { type: String, unique: true },
+    file: String,
     fileSize: Number,
     matches: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Match' }]
 })

+ 1 - 1
server/src/restServer/models/playerList.js

@@ -3,7 +3,7 @@ import mongoose from 'mongoose'
 const PlayerListSchema = new mongoose.Schema({
     imported: Date,
     parsed: Date,
-    file: { type: String, unique: true },
+    file: String,
     fileSize: Number,
     players: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Player' }]
 })

+ 0 - 3
server/src/restServer/routes/authenticate.js

@@ -40,7 +40,6 @@ function initDB() {
 initDB()
 
 authenticate.post('/login', (req, res) => {
-  console.log("Trying to log in.")
   const { username, password } = req.body
   if (!username || !password) {
     console.log('parameters missing', username, password)
@@ -83,9 +82,7 @@ authenticate.post('/login', (req, res) => {
 const verify = express.Router()
 
 verify.use( (req, res, next) => {
-  console.log("Trying to authenticate token.")
   const token = req.headers['x-access-token']
-  console.log('Got token', token)
 
   if (token) {
     const decoded = jwt.decode(token, config.secret)

+ 0 - 2
server/src/restServer/routes/sms.js

@@ -9,7 +9,6 @@ const sms = express.Router()
 sms.post('/send', async (req, res) => {
     try {
         const { recipients, body, sender } = req.body
-        console.log(req.body, recipients, body, sender)
         const Authorization = `Basic ${base64.encode(`${bulksms.tokenId}:${bulksms.tokenSecret}`)}`
         const response = await fetch(`https://api.bulksms.com/v1/messages`, {
             method: 'POST',
@@ -52,7 +51,6 @@ sms.get('/credits', async (req, res) => {
                 Authorization
             }
         })
-        console.log('response', response)
         if (response.status != 200) {
             throw Error(`Received status code ${response.status}`)
         }

+ 80 - 55
server/src/restServer/routes/swisstennis.js

@@ -43,7 +43,6 @@ async function checkLogin () {
 swisstennis.post('/login', async (req, res) => {
   try {
     const currentState = await checkLogin()
-    console.log(currentState)
     if (currentState) {
       return res.json({ msg: 'Already logged in!' })
     }
@@ -83,9 +82,9 @@ swisstennis.get('/tournaments', async (req, res) => {
     if (!myTournamentsPage) throw Error('Not logged in!')
 
     const tournamentRegexp = /<a href=".*ProtectedDisplayTournament.*tournament=Id(\d+)">([^<]+)<\/a>/gm
-    
+
     do {
-      match = tournamentRegexp.exec(strMyTournamentsPage)
+      match = tournamentRegexp.exec(myTournamentsPage)
       if (match) {
         tournaments[match[1]] = match[2]
       }
@@ -125,15 +124,20 @@ swisstennis.get('/draws', async (req, res) => {
   }
 })
 
+swisstennis.get('/playerlists', async (req, res) => {
+  try {
+    const playerLists = await PlayerList.find().select({_id: 1, imported: 1, file: 1, filesize: 1})
+    return res.json(playerLists)
+  } catch (error) {
+    return res.status(400).json({ msg: error.toString() })
+  }
+})
+
 swisstennis.get('/playerlist', async (req, res) => {
   try {
-    const tournament = req.query.tournament || config.tournamentId
-    
-    if (!tournament) {
-      throw Error('No tournament given.')
-    }
-    const playerLists = await PlayerList.find()
-    return res.json({ playerLists })
+    const key = req.query.playerlistId ? { _id: req.query.playerlistId } : {}
+    const playerList = await PlayerList.findOne(key).sort({ imported: -1 }).populate('players')
+    return res.json(playerList)
   } catch (error) {
     return res.status(400).json({ msg: error.toString() })
   }
@@ -170,7 +174,6 @@ swisstennis.get('/playerlist/parse/:filename', async (req, res) => {
     const filePath = `swisstennis_files/${req.params.filename}`
     const dateString = req.params.filename.match(/(\d{4}\d{2}\d{2}T\d{2}\d{2}\d{2})/)[1]
     const fileDate = moment(dateString)
-    console.log(fileDate)
     const stat = fs.statSync(filePath)
 
     const fileNewer = await PlayerList.find({ imported: { $gte: fileDate.toDate() } })
@@ -187,10 +190,7 @@ swisstennis.get('/playerlist/parse/:filename', async (req, res) => {
 
     const headers = worksheet.slice(3, 4)
 
-    const dbPlayers = await Player.find()
-    console.log('bump')
-
-    const allPlayers = worksheet.slice(4, worksheet.length).map(data => {
+    const allPlayers = await Promise.all(worksheet.slice(4, worksheet.length).map(async data => {
       const filePlayer = {
         created: fileDate.toDate(),
         category: normalize(data[0]),
@@ -211,7 +211,6 @@ swisstennis.get('/playerlist/parse/:filename', async (req, res) => {
         confirmed: !!data[29],
         paid: !!data[31],
       }
-      console.log('bump2')
 
       filePlayer.gender = filePlayer.category ? 
         filePlayer.category[0] === 'M' ? 'm' : 
@@ -236,22 +235,11 @@ swisstennis.get('/playerlist/parse/:filename', async (req, res) => {
       } else if (filePlayer.phoneMobile && filePlayer.phoneMobile.match(reMobile)) {
         filePlayer.phone = filePlayer.phoneMobile
       }
-      console.log('bump3')
-
-      const dbPlayer = dbPlayers.filter(player => player.idString == filePlayer.idString).sort((a, b) => 
-        a.created > b.created ? 1 : a.created == b.created ? 0 : -1
-      )[0]
-      //console.log('sorted:', dbPlayer)
-
-      if (dbPlayer && dbPlayer.equal(filePlayer)) {
-        return dbPlayer._id
-      } else {
-        const player = new Player(filePlayer)
-        player.save()
-        return player._id
-      }
-    })
-    console.log('bump3')
+
+      const player = new Player(filePlayer)
+      player.save()
+      return player._id
+    }))
 
     const playerList = new PlayerList({
       imported: fileDate,
@@ -267,6 +255,25 @@ swisstennis.get('/playerlist/parse/:filename', async (req, res) => {
   }
 })
 
+// Delete a playerlist
+swisstennis.delete('/playerlist/:playerlistId', async (req, res) => {
+  try {
+    const { playerlistId } = req.params
+    
+    if (!playerlistId) {
+      throw Error('No playerlistId given.')
+    }
+    const playerList = await PlayerList.findOne({ _id: playerlistId })
+    if (!playerList) {
+      throw Error('PlayerList not found.')
+    }
+    playerList.remove()
+    return res.json({ _id: playerlistId })
+  } catch (error) {
+    return res.status(400).json({ msg: error.toString() })
+  }
+})
+
 // List downloaded files
 swisstennis.get('/files', async (req, res) => {
   const tournament = req.query.tournament
@@ -277,7 +284,7 @@ swisstennis.get('/files', async (req, res) => {
     const stats = fs.statSync(`swisstennis_files/${filename}`)
     return { filename, size: stats.size, ctime: stats.ctime }
   })
-  return res.json({ fileList })
+  return res.json(fileList)
 })
 
 swisstennis.delete('/files', async (req, res) => {
@@ -292,15 +299,24 @@ swisstennis.delete('/files', async (req, res) => {
   }
 })
 
+swisstennis.get('/calendars', async (req, res) => {
+  try {
+    const calendars = await MatchList.find().select({_id: 1, imported: 1, file: 1, fileSize: 1})
+    return res.json(calendars)
+  } catch (error) {
+    return res.status(400).json({ msg: error.toString() })
+  }
+})
+
 swisstennis.get('/calendar', async (req, res) => {
   try {
-    const tournament = req.query.tournament || config.tournamentId
-    
-    if (!tournament) {
-      throw Error('No tournament given.')
-    }
-    const calendars = await MatchList.find()
-    return res.json({ calendars })
+    const key = req.query.calendarId ? { _id: req.query.calendarId } : {}
+    const calendar = await MatchList.findOne(key).sort({ imported: -1 }).populate('matches')
+    await Promise.all(calendar.matches.map(async match => {
+      await Player.populate(match, 'player1')
+      await Player.populate(match, 'player2')
+    }))
+    return res.json(calendar)
   } catch (error) {
     return res.status(400).json({ msg: error.toString() })
   }
@@ -350,8 +366,6 @@ swisstennis.get('/calendar/parse/:filename', async (req,res) => {
       throw Error(`Wrong file structure.`)
     }
 
-    const dbMatches = await Match.find()
-
     const allMatches = await Promise.all(worksheet.slice(2, worksheet.length).map(async (data, key) => {
       const fileMatch = {
         created: fileDate,
@@ -362,23 +376,15 @@ swisstennis.get('/calendar/parse/:filename', async (req,res) => {
         result: normalize(data[8]),
       }
       fileMatch.doubles = !!fileMatch.category.match(/DM.*|[MW]D.*/)
-      const player1 = await Player.findOne({idString: `${fileMatch.category} % ${normalize(data[4])}`})
-      const player2 = await Player.findOne({idString: `${fileMatch.category} % ${normalize(data[6])}`})
+      const player1 = await Player.findOne({idString: `${fileMatch.category} % ${normalize(data[4])}`}).sort({created: -1})
+      const player2 = await Player.findOne({idString: `${fileMatch.category} % ${normalize(data[6])}`}).sort({created: -1})
       fileMatch.idString = `${fileMatch.category} % ${player1 ? player1.idString : `${key}_1`} - ${player2 ? player2.idString : `${key}_2`}`
       fileMatch.player1 = player1 ? player1._id : null
       fileMatch.player2 = player2 ? player2._id : null
 
-      const dbMatch = dbMatches.filter(match => match.idString == fileMatch.idString).sort((a, b) => 
-        a.created > b.created ? 1 : a.created == b.created ? 0 : -1
-      )[0]
-
-      if (dbMatch && dbMatch.equal(fileMatch)) {
-        return dbMatch._id
-      } else {
-        const match = new Match(fileMatch)
-        match.save()
-        return match._id
-      }
+      const match = new Match(fileMatch)
+      await match.save()
+      return match._id
     }))
     const matchList = new MatchList({
       imported: fileDate,
@@ -394,6 +400,25 @@ swisstennis.get('/calendar/parse/:filename', async (req,res) => {
   }
 })
 
+// Delete a calendar
+swisstennis.delete('/calendar/:calendarId', async (req, res) => {
+  try {
+    const { calendarId } = req.params
+    
+    if (!calendarId) {
+      throw Error('No calendarId given.')
+    }
+    const calendar = await MatchList.findOne({ _id: calendarId })
+    if (!calendar) {
+      throw Error('Calendar not found.')
+    }
+    calendar.remove()
+    return res.json({ _id: calendarId })
+  } catch (error) {
+    return res.status(400).json({ msg: error.toString() })
+  }
+})
+
 swisstennis.get('/download/draw', async (req, res) => {
   try {
     const { draw } = req.query

+ 7 - 7
server/src/restServer/routes/sztm.js

@@ -6,6 +6,9 @@ import Match from '../models/match'
 import Player from '../models/player'
 import MatchList from '../models/matchList'
 import PlayerList from '../models/playerList'
+import pdfMake from "pdfmake/build/pdfmake";
+import pdfFonts from "pdfmake/build/vfs_fonts";
+pdfMake.vfs = pdfFonts.pdfMake.vfs;
 
 const sztm = express.Router() 
 
@@ -76,7 +79,6 @@ sztm.get('/players', async (req, res) => {
 
 sztm.post('/pdf', async (req, res) => {
     try {
-        console.log(req.body)
         const { matchListId, date, place, category } = req.body
         
         const matchList = matchListId ? 
@@ -84,7 +86,7 @@ sztm.post('/pdf', async (req, res) => {
             await MatchList.findOne().sort({ imported: -1 })
         console.log(matchList._id)
         
-        const query = {}//{ _id: { $in: matchList.matches } }
+        const query = { _id: { $in: matchList.matches } }
         if (place) query.place = place
         if (category) query.category = category
         if (date) { 
@@ -97,13 +99,11 @@ sztm.post('/pdf', async (req, res) => {
         }
         
         const matches = await Match.find(query).sort({ date: 1 }).populate('player1').populate('player2')
-        console.log(matches)
         const players = []
         matches.forEach(match => {
             if (match.player1) players.push(match.player1)
             if (match.player2) players.push(match.player2)
         })
-        console.log('matches', matches, players)
 
         const allPlaces = []
         matches.forEach(match => {
@@ -192,8 +192,8 @@ sztm.post('/pdf', async (req, res) => {
                              {text: 'WO Grund', fillColor: '#eeeeee'}],
                             ...matches.filter(match => match.place && (match.place == place)).map((match) => {
                                 return ['', moment(match.date).format('HH:mm'), match.category, 
-                                match.player1 && match.player1.fullName, match.player1.ranking, 
-                                match.player2 && match.player2.fullName, match.player2.ranking, 
+                                match.player1 && match.player1.fullName, match.player1 && match.player1.ranking, 
+                                match.player2 && match.player2.fullName, match.player2 && match.player2.ranking, 
                                 '', '', '', '']
                             })
                         ]},
@@ -211,7 +211,7 @@ sztm.post('/pdf', async (req, res) => {
                     },
                     tableStyle: {
                         margin: [0, 15, 0, 5]
-                    }
+                    } 
                 }
             }
     

+ 4 - 48
server/src/restServer/routes/users.js

@@ -1,58 +1,14 @@
 import express from 'express'
-import pdfMake from 'pdfmake/build/pdfmake'
-import pdfFonts from 'pdfmake/build/vfs_fonts'
-import fs from 'fs'
 import User from '../models/user'
-pdfMake.vfs = pdfFonts.pdfMake.vfs
 
 const users = express.Router()
 
-users.get('/', (req, res) => {
-  const dbUsers = User.find().lean()
-  dbUsers.exec((err, users) => {
-      if (err) {
-        res.status(400).json({
-            msg: err.toString()
-        })
-        return
-      }
-
-      return res.json({
-          users
-      })
-  })
-})
-
-users.get('/pdf', async (req, res) => {
+users.get('/', async (req, res) => {
     try {
-        const dbUsers = await User.find().lean().exec()
-        const docDefinition = {
-            content: [
-                { text: 'Benutzerliste', style: 'header'},
-                { table: {
-                    headerRows: 1,
-                    widths: ['20%', '*', '30%'],
-                    body: Array.concat(
-                        [['Benutzername', 'Name', 'Passwort-Hash']],
-                        dbUsers.map((user) => {
-                            return [user.username, user.name, user.password]
-                        }))
-                } }
-            ],
-            styles: {
-                header: {
-                    fontSize: 22,
-                    bold: true
-                }
-            }
-        }
-        const pdfDocGenerator = pdfMake.createPdf(docDefinition)
-        pdfDocGenerator.getBuffer((buffer) => {
-            fs.writeFileSync('/usr/src/sztm_files/users.pdf', new Buffer(new Uint8Array(buffer)))
-        })
-        res.send("Alles paletti")
+      const users = await User.find()
+      return res.json(users)
     } catch (error) {
-        return res.status(400).json({ msg: error.toString() })
+      return res.status(400).json({ msg: error.toString() })
     }
 })