浏览代码

added filtering and sorting to playerlist, added basic functionality for calendar.

Tomi Cvetic 7 年之前
父节点
当前提交
14884873af

+ 2 - 2
src/Main.js

@@ -3,7 +3,7 @@ import React from 'react'           // React to manage the GUI
 /** Import sub-modules */
 import StartPage from './startPage/components/StartPage'
 import PlayerList from './playerList/components/PlayerList'       // Everything that has to do with players
-import MatchTable from './matchList/components/MatchTable'         // Everything that has to do with matches
+import MatchList from './calendar/components/MatchList'         // Everything that has to do with matches
 import AppLayout from './layout/components/AppLayout'
 import AlertList from './alerts/components/AlertList'
 
@@ -22,7 +22,7 @@ class Main extends React.Component {
           state={this.props}
           components={{
             PlayerList,
-            MatchTable,
+            MatchList,
             StartPage
           }}
         />

+ 58 - 0
src/calendar/components/MatchForm.js

@@ -0,0 +1,58 @@
+import React from 'react'
+import { FormGroup, ControlLabel, FormControl, HelpBlock } from 'react-bootstrap'
+import { fileSize, date2s, time2s } from '../../helpers'
+
+function FieldGroup ({ id, label, help, file, ...props }) {
+  return (
+    <FormGroup controlId={id}>
+      <ControlLabel>{label}</ControlLabel>
+      <FormControl {...props} />
+      {help && <HelpBlock>{help}</HelpBlock>}
+      {file && <div>{fileSize(file.size)} {date2s(file.lastModified)} {time2s(file.lastModified)}</div>}
+    </FormGroup>
+  )
+}
+
+class MatchForm extends React.Component {
+  constructor () {
+    super()
+    this.handleFileUpload = this.handleFileUpload.bind(this)
+  }
+
+  handleFileUpload (event) {
+    event.preventDefault()
+    const { fileUploadStart } = this.props.actions
+    const { files } = this.calendarFile
+    // if (files.length === 0) {
+    //   alertAdd({ type: 'info', text: 'Datei entfernt' })
+    //   return
+    // }
+    // if (files.length > 1) {
+    //   alertAdd({ type: 'warning', text: 'Mehrere Dateien gesendet. Nur die erste wird verarbeitet.' })
+    // }
+    const file = files[0]
+    fileUploadStart(file)
+  }
+
+  render () {
+    const { fileUpload, file } = this.props.state
+
+    return (
+      <div>
+        <form>
+          <FieldGroup
+            id='calendarFile'
+            label='Calendar.xls File'
+            type='file'
+            file={file}
+            inputRef={input => { this.calendarFile = input }}
+            onChange={this.handleFileUpload}
+            disabled={(fileUpload === 'started')}
+          />
+        </form>
+      </div>
+    )
+  }
+}
+
+export default MatchForm

+ 4 - 21
src/calendar/components/MatchList.js

@@ -1,31 +1,14 @@
 import React from 'react'
-import MatchDisp from './MatchDisp'
+import MatchTable from './MatchTable'
 
 class MatchList extends React.Component {
   render () {
-    const matches = this.props.match.matches
-    const filtered = this.props.match.filtered
+    const { state, actions } = this.props
 
     return (
       <div>
-        <h2>Matches ({filtered.length}/{matches.length})</h2>
-        <table className='table table-bordered table-striped'>
-          <thead>
-            <tr>
-              <th>Ort</th>
-              <th>Datum</th>
-              <th>Zeit</th>
-              <th>Konkurrenz</th>
-              <th>Spieler 1</th>
-              <th>Spieler 2</th>
-            </tr>
-          </thead>
-          <tbody>
-            {filtered.map((match, key) =>
-              <MatchDisp key={key} match={match} />
-            )}
-          </tbody>
-        </table>
+        <h1>Matchliste</h1>
+        <MatchTable state={state} actions={actions} />
       </div>
     )
   }

+ 54 - 50
src/matchList/components/MatchTable.js → src/calendar/components/MatchTable.js

@@ -1,50 +1,54 @@
-import React from 'react'
-import { date2s, time2s } from '../../helpers.js'
-
-class MatchRow extends React.Component {
-  render () {
-    const match = this.props.match
-    return (
-      <tr>
-        <td>{match.Ort || <strong>Kein Platz zugeteilt</strong>}</td>
-        <td>{match.Datum ? date2s(match.Datum) : <strong>Kein Datum zugeteilt</strong>}</td>
-        <td>{match.Datum ? time2s(match.Datum) : <strong>Keine Zeit zugeteilt</strong>}</td>
-        <td>{match.Konkurrenz}</td>
-        <td>{match.Spieler1}</td>
-        <td>{match.Spieler2}</td>
-      </tr>
-    )
-  }
-}
-
-class MatchTable extends React.Component {
-  render () {
-    console.log('Match props', this.props.state)
-    const { allMatches, filteredMatches } = this.props.state || { allMatches: [], filteredMatches: [] }
-
-    return (
-      <div>
-        <h2>Matches ({filteredMatches.length}/{allMatches.length})</h2>
-        <table className='table table-bordered table-striped'>
-          <thead>
-            <tr>
-              <th>Ort</th>
-              <th>Datum</th>
-              <th>Zeit</th>
-              <th>Konkurrenz</th>
-              <th>Spieler 1</th>
-              <th>Spieler 2</th>
-            </tr>
-          </thead>
-          <tbody>
-            {filteredMatches.map((match, key) =>
-              <MatchRow key={key} match={match} />
-            )}
-          </tbody>
-        </table>
-      </div>
-    )
-  }
-}
-
-export default MatchTable
+import React from 'react'
+import { date2s, time2s } from '../../helpers'
+
+class MatchRow extends React.Component {
+  render () {
+    const match = this.props.match
+    console.log(match)
+    return (
+      <tr>
+        <td>{match.Ort}</td>
+        <td>{date2s(match.Datum)}</td>
+        <td>{match.Konkurrenz}</td>
+        <td>{match.Spieler1}</td>
+        <td>{match.Spieler1Klassierung}</td>
+        <td>{match.Spieler2}</td>
+        <td>{match.Spieler2Klassierung}</td>
+        <td>{match.Resultat}</td>
+      </tr>
+    )
+  }
+}
+
+class MatchTable extends React.Component {
+  render () {
+    const { allMatches, filteredMatches } = this.props.state || { allMatches: [], filteredMatches: [] }
+
+    return (
+      <div>
+        <h2>Spielerliste ({filteredMatches.length}/{allMatches.length})</h2>
+        <table className='table table-bordered table-striped'>
+          <thead>
+            <tr>
+              <th>Ort</th>
+              <th>Datum</th>
+              <th>Konkurrenz</th>
+              <th>Spieler1</th>
+              <th>Spieler1Klassierung</th>
+              <th>Spieler2</th>
+              <th>Spieler2Klassierung</th>
+              <th>Resultat</th>
+            </tr>
+          </thead>
+          <tbody>
+            {filteredMatches.map((match, key) =>
+              <MatchRow key={key} match={match} />
+            )}
+          </tbody>
+        </table>
+      </div>
+    )
+  }
+}
+
+export default MatchTable

+ 4 - 1
src/calendar/components/index.js

@@ -1,4 +1,7 @@
 import MatchDisp from './MatchDisp'
 import MatchList from './MatchList'
+import MatchForm from './MatchForm'
+import MatchTable from './MatchTable'
 
-export default { MatchDisp, MatchList }
+export { MatchDisp, MatchList, MatchForm, MatchTable }
+export default { MatchDisp, MatchList, MatchForm, MatchTable }

+ 20 - 0
src/calendar/functions.js

@@ -0,0 +1,20 @@
+import Excel from '../excel'         // Helper files to create Excel files
+import Match from '../classes/match'
+
+export function generateCalendar (file) {
+  return new Promise((resolve, reject) => {
+    console.log('About to read the calendar.')
+    Excel.readWorkbook(file).then(worksheets => {
+      console.log('got worksheets', worksheets)
+      const worksheet = worksheets.Sheet1
+      if (worksheet[2].length < 8 | worksheet[2].length > 9) {
+        reject(Error('Wrong file structure.'))
+      }
+      const calendar = worksheet.slice(2, worksheet.length).map((matchData) => new Match.Match(matchData))
+      console.log('State after generating calendar:', calendar)
+      resolve(calendar)
+    }).catch(error => {
+      reject(Error(error))
+    })
+  })
+}

+ 2 - 23
src/calendar/index.js

@@ -1,31 +1,10 @@
 import { actions, reducer, state, saga } from './state'
 import components from './components'
-import { normalize } from '../helpers.js'
 
 const filters = {
-  all: players => players
+  all: matches => matches
 }
 
 const selectors = {}
 
-/** Class representing a player */
-class MatchClass {
-  /**
-   * Create a player
-   * A player data item in the Swisstennis PlayerList.xlsx file has the following columns
-   * Ort | Datum | Zeit | Konkurrenz | Spieler 1 | Spieler 1 Klassierung | Spieler 2 |
-   * Spieler 2 Klassierung | [Resultat]
-   */
-  constructor (data) {
-    this.Ort = normalize(data[0])
-    this.Datum = data[1]
-    this.Konkurrenz = normalize(data[3])
-    this.Spieler1 = normalize(data[4])
-    this.Spieler1Klassierung = normalize(data[5])
-    this.Spieler2 = normalize(data[6])
-    this.Spieler2Klassierung = normalize(data[7])
-    this.isDoubles = this.Konkurrenz.match(/DM.*|[MW]D.*/)
-  }
-}
-
-export default { MatchClass, actions, components, filters, selectors, reducer, state, saga }
+export default { actions, components, filters, selectors, reducer, state, saga }

+ 99 - 8
src/calendar/state.js

@@ -1,4 +1,6 @@
-/** @module match/state */
+/** @module player/state */
+import { call, put, takeEvery } from 'redux-saga/effects'
+import { generateCalendar } from './functions'
 
 /**
  * state.js
@@ -8,22 +10,111 @@
 
 /** actionTypes define what actions are handeled by the reducer. */
 export const actions = {
-  setState: matches => {
-    type: 'SET_MATCHES',
-    matches
+  fileUploadStart: file => {
+    return {
+      type: 'CALENDAR_FILE_UPLOAD_START',
+      file
+    }
+  },
+  fileUploadSuccess: allMatches => {
+    return {
+      type: 'CALENDAR_FILE_UPLOAD_SUCCESS',
+      allMatches
+    }
+  },
+  fileUploadFailure: error => {
+    return {
+      type: 'CALENDAR_FILE_UPLOAD_FAILURE',
+      alert: { type: 'warning', text: error.toString() }
+    }
+  },
+  filterSortMatches: () => {
+    return {
+      type: 'CALENDAR_FILTER_SORT'
+    }
   }
 }
 console.log('State actions', actions)
 
 /** state definition */
-export const state = {}
+export const state = {
+  allMatches: [],
+  filteredMatches: [],
+  filters: [
+    {
+      filterFunction: (match, filterValue) => {
+        return match.Konkurrenz === filterValue
+      },
+      filterValue: 'MS R6/R7'
+    }
+  ],
+  sorting: [
+    (match1, match2) => {
+      if (match1.Datum > match2.Datum) {
+        return 1
+      }
+      if (match1.Datum === match2.Datum) {
+        return 0
+      }
+      return -1
+    },
+    (match1, match2) => {
+      if (match1.Ort > match2.Ort) {
+        return 1
+      }
+      if (match1.Ort === match2.Ort) {
+        return 0
+      }
+      return -1
+    }
+  ],
+  fileUpload: 'idle',
+  file: null
+}
 console.log('State state', state)
 
 /** reducer is called by the redux dispatcher and handles all component actions */
 export function reducer (state = [], action) {
-  let nextState = state
-  return nextState
+  switch (action.type) {
+    case 'CALENDAR_FILE_UPLOAD_START':
+      return { ...state, fileUpload: 'started', file: action.file }
+    case 'CALENDAR_FILE_UPLOAD_SUCCESS':
+      return { ...state, fileUpload: 'finished', allMatches: action.allMatches }
+    case 'CALENDAR_FILE_UPLOAD_FAILURE':
+      return { ...state, fileUpload: 'failure' }
+    case 'CALENDAR_FILTER_SORT':
+      const { allMatches, filters, sorting } = state
+      const sortedMatches = allMatches.sort((match1, match2) => {
+        return sorting.length ? sorting.reduce((acc, sortFun) => {
+          return acc === 0 ? sortFun(match1, match2) : acc
+        }, 0) : 0
+      })
+      console.log('Sorted matches', sortedMatches)
+      const filteredMatches = sortedMatches.filter(match => {
+        return filters.length ? filters.map(filter => filter.filterFunction(match, filter.filterValue)).every(value => !!value) : true
+      })
+      console.log('Filtered matches', filteredMatches)
+      return { ...state, filteredMatches }
+    default:
+      return state
+  }
+}
+
+function * uploadFile (action) {
+  try {
+    console.log('Calendar uploadFile', action.file)
+    const allMatches = yield call(generateCalendar, action.file)
+    console.log('Calendar success!', actions.fileUploadSuccess(allMatches))
+    yield put(actions.fileUploadSuccess(allMatches))
+    yield put(actions.filterSortMatches())
+  } catch (error) {
+    console.log('Calendar failure!', actions.fileUploadFailure(error))
+    yield put(actions.fileUploadFailure(error))
+  }
 }
 
 /** sagas are asynchronous workers (JS generators) to handle the state. */
-export function * saga () {}
+export function * saga () {
+  console.log('Calendar saga started.')
+  yield takeEvery('CALENDAR_FILE_UPLOAD_START', uploadFile)
+}

+ 23 - 1
src/helpers.js

@@ -49,4 +49,26 @@ function normalizePhone (item) {
   return phone
 }
 
-export { date2s, time2s, datetime2s, normalize, normalizePhone, fileSize }
+function filterFuzzy (item, filter) {
+  const lowerCaseItem = item.toLowerCase()
+  const substrings = filter.toLowerCase().split(/\s+/)
+  let lastIndex = 0
+  const indices = substrings.map(substring => {
+    const index = lowerCaseItem.subString(lastIndex).indexOf(substring)
+    if (index === -1) {
+      return null
+    } else {
+      const startIndex = lastIndex + index
+      const endIndex = startIndex + substring.length
+      lastIndex = endIndex
+      return {startIndex, endIndex}
+    }
+  })
+  return indices
+}
+
+function filterExact (item, filter) {
+  return item === filter
+}
+
+export { date2s, time2s, datetime2s, normalize, normalizePhone, fileSize, filterFuzzy, filterExact }

+ 5 - 5
src/index.js

@@ -18,7 +18,7 @@ import Main from './Main'
 
 // Import the submodules
 import playerList from './playerList'
-import matchList from './matchList'
+import calendar from './calendar'
 import layout from './layout'
 import alerts from './alerts'
 
@@ -29,7 +29,7 @@ import alerts from './alerts'
 /** The root reducer is combined from all sub-module reducers */
 const rootReducer = combineReducers({
   playerList: playerList.reducer,
-  matchList: matchList.reducer,
+  calendar: calendar.reducer,
   layout: layout.reducer,
   alerts: alerts.reducer
 })
@@ -38,7 +38,7 @@ console.log('Root reducer:', rootReducer)
 /** The default state is combined from all sub-module states */
 const defaultState = {
   playerList: playerList.state,
-  matchList: matchList.state,
+  calendar: calendar.state,
   layout: layout.state,
   alerts: alerts.state
 }
@@ -49,7 +49,7 @@ function * rootSaga () {
   console.log('rootSaga called')
   yield all([
     playerList.saga(),
-    matchList.saga(),
+    calendar.saga(),
     layout.saga(),
     alerts.saga()
   ])
@@ -113,7 +113,7 @@ console.log('history:', history)
 /** Collect the action creators from all modules in actionCreators */
 const actionCreators = {
   playerList: playerList.actions,
-  matchList: matchList.actions,
+  calendar: calendar.actions,
   layout: layout.actions,
   alerts: alerts.actions
 }

+ 2 - 2
src/layout/components/AppLayout.js

@@ -6,7 +6,7 @@ class AppLayout extends React.Component {
     const { state } = this.props
     const { activeTab } = this.props.state.layout
     const { changeTab } = this.props.state.layoutActions
-    const { PlayerList, MatchTable, StartPage } = this.props.components
+    const { PlayerList, MatchList, StartPage } = this.props.components
 
     return (
       <div>
@@ -18,7 +18,7 @@ class AppLayout extends React.Component {
             <PlayerList state={state.playerList} actions={state.playerListActions} />
           </Tab>
           <Tab eventKey={2} title='Calendar' >
-            <MatchTable state={state.matchList} actions={state.matchListActions} />
+            <MatchList state={state.calendar} actions={state.calendarActions} />
           </Tab>
           <Tab eventKey={3} title='Spielplan' />
           <Tab eventKey={4} title='Zahlliste' />

+ 0 - 41
src/lists/components/EmailList.js

@@ -1,41 +0,0 @@
-import React from 'react'
-
-class EmailList extends React.Component {
-  render () {
-    const filtered = this.props.filtered
-    const players = this.props.players
-
-    const emailList = []
-    filtered.forEach((match, key) => {
-      const player = players.find(player =>
-        (player.name === match.Spieler1) && (player.Konkurrenz === match.Konkurrenz)
-      )
-      if (!player) {
-        console.log('EmailList: Player not found!', player, key, match)
-      } else {
-        if (!emailList.includes(player.Email)) {
-          emailList.push(player.Email)
-        }
-      }
-      const player2 = players.find(player =>
-        (player.name === match.Spieler2) && (player.Konkurrenz === match.Konkurrenz)
-      )
-      if (!player2) {
-        console.log('EmailList: Player not found!', player2, key, match)
-      } else {
-        if (!emailList.includes(player2.Email)) {
-          emailList.push(player2.Email)
-        }
-      }
-    })
-
-    return (
-      <div>
-        <h2>Email Adressen</h2>
-        <p>{emailList.join('; ')}</p>
-      </div>
-    )
-  }
-}
-
-export default EmailList

+ 0 - 41
src/lists/components/PhoneList.js

@@ -1,41 +0,0 @@
-import React from 'react'
-
-class PhoneList extends React.Component {
-  render () {
-    const filtered = this.props.filtered
-    const players = this.props.players
-
-    const phoneList = []
-    filtered.forEach((match, key) => {
-        const player = players.find(player => 
-            (player.name === match.Spieler1) && (player.Konkurrenz === match.Konkurrenz)
-        )
-        if (!player) {
-            console.log('PhoneList: Player not found!', player, key, match)
-        } else {
-            if (!phoneList.includes(player.phone)) {
-                phoneList.push(player.phone)
-            }
-        }
-        const player2 = players.find(player => 
-            (player.name === match.Spieler2) && (player.Konkurrenz === match.Konkurrenz)
-        )
-        if (!player2) {
-            console.log('PhoneList: Player not found!', player2, key, match)
-        } else {
-            if (!phoneList.includes(player2.phone)) {
-                phoneList.push(player2.phone)
-            }
-        }
-    })
-
-    return (
-      <div>
-        <h2>Telefonnummern</h2>
-        <p>{phoneList.join('; ')}</p>
-      </div>
-    )
-  }
-}
-
-export default PhoneList

+ 0 - 3
src/matchList/components/index.js

@@ -1,3 +0,0 @@
-import MatchTable from './MatchTable'
-
-export default { MatchTable }

+ 0 - 247
src/matchList/index.js

@@ -1,247 +0,0 @@
-import { actions, reducer, state, saga } from './state'
-import components from './components'
-import { date2s, time2s } from '../helpers'
-import Match from '../classes/match'
-import Excel from '../excel'
-
-const filters = {
-  all: players => players
-}
-
-const selectors = {}
-
-export default { actions, components, filters, selectors, reducer, state, saga }
-
-const PLACES = {
-  'LE': 'TC Lerchenberg',
-  'WA': 'TC Waidberg',
-  'VA': 'TC Valsana',
-  'SE': 'TC Seebach',
-  'BU': 'TC Bührle',
-  'HO': 'TC Höngg',
-  'TS': 'Tennis-Sport Club',
-  'HA': 'Städtische Plätze Hardhof',
-  'AU': 'Auswärtig'
-}
-
-const FILTER_OFF = 'Alle'
-
-function calculateMatchFilters () {
-  const dates = {}
-  const places = []
-  const categories = []
-  this.state.match.matches.forEach((item) => {
-    const dateString = date2s(item.Datum)
-    if (!!item.Datum & !dates.hasOwnProperty(dateString)) {
-      dates[dateString] = item.Datum
-    }
-    if (!!item.Ort & !places.includes(item.Ort)) {
-      places.push(item.Ort)
-    }
-    if (!!item.Konkurrenz & !categories.includes(item.Konkurrenz)) {
-      categories.push(item.Konkurrenz)
-    }
-  })
-  const match = { ...this.state.match, dates, places, categories }
-  this.setState({ match })
-}
-
-function generateCalendar (worksheet) {
-  console.log('About to read the calendar.')
-  const worksheets = { ...this.state.worksheets }
-  worksheets['Calendar'] = worksheet
-  this.setState({ worksheets })
-
-  if (worksheet[2].length < 8 | worksheet[2].length > 9) {
-    throw Error('Wrong file structure.')
-  }
-  const matches = worksheet.slice(2, worksheet.length).map((matchData) => new Match.Match(matchData))
-  const match = { ...this.state.match }
-  match.matches = matches
-  this.setState({ match })
-  this.calculateMatchFilters()
-  this.calculatePayDate()
-  this.filterMatches()
-  console.log('State after generating calendar:', this.state)
-}
-
-function filterMatches () {
-  const filters = {
-    date: this.matchDate.value !== FILTER_OFF ? this.matchDate.value : null,
-    place: this.matchPlace.value !== FILTER_OFF ? this.matchPlace.value : null,
-    category: this.matchCategory.value !== FILTER_OFF ? this.matchCategory.value : null
-  }
-  console.log('New filter settings:', filters)
-
-  const match = { ...this.state.match }
-  match.filtered = match.matches.filter((match) => {
-    const matchDate = new Date(match.Datum)
-    matchDate.setHours(0, 0, 0, 0)
-    const filtDate = new Date(filters.date)
-    filtDate.setHours(0, 0, 0, 0)
-    return (!filters.date | matchDate.getTime() === filtDate.getTime()) &
-      (!filters.place | filters.place === match.Ort) &
-      (!filters.category | filters.category === match.Konkurrenz)
-  })
-  this.setState({ match })
-
-  const player = { ...this.state.player, filters }
-  player.filtered = player.players
-  this.setState({ player })
-}
-
-function generatePhoneList (event) {
-  event.preventDefault()
-
-  const phoneMail = new Excel.Workbook()
-  phoneMail.SheetNames = []
-  phoneMail.Sheets = {}
-
-  const dataList = [
-      ['Vorname', 'Nachname', 'Anrede', 'Geschlecht', 'Handy', 'E-Mail']
-  ]
-  const phonePot = []
-
-  const players = this.state.player.filtered
-  players.forEach(player => {
-    if (!player.phone.match(/^FEHLER/) && !phonePot.includes(player.phone)) {
-      phonePot.push(player.phone)
-      dataList.push([
-        player.Vorname,
-        player.Name,
-        2,
-        player.geschlecht === 'w' ? 2 : 1,
-        player.phone
-      ])
-    }
-  })
-  phoneMail.Sheets['Sheet1'] = Excel.SheetFromArray(dataList)
-  phoneMail.SheetNames.push('Sheet1')
-  Excel.saveAs(phoneMail, 'Telefon.xlsx')
-}
-
-function calculatePayDate () {
-  if ((this.state.player.players.length === 0) | (this.state.match.matches.length === 0)) {
-    return
-  }
-
-  this.state.match.matches.forEach((match) => {
-    [match.Spieler1, match.Spieler2].forEach((matchPlayer) => {
-      if (matchPlayer) {
-        let foundPlayer = this.state.player.players.find((player) =>
-            (player.name === matchPlayer) & (player.Konkurrenz === match.Konkurrenz)
-          )
-        if (!foundPlayer) {
-          console.log('Debug payerlist:', foundPlayer, match)
-          throw Error('Match player not found in player list. This is an error!')
-        }
-        if (!foundPlayer.BezahltAm) {
-          foundPlayer.BezahltAm = match.Datum
-        }
-      }
-    })
-  })
-}
-
-function generatePayTable (event) {
-  event.preventDefault()
-
-  const paylist = new Excel.Workbook()
-  paylist.SheetNames = []
-  paylist.Sheets = {}
-
-  const worksheets = {}
-
-  let placeArray = this.state.match.places
-    /* if (placeArray.length > 1) {
-      placeArray = placeArray.concat([FILTER_OFF])
-    } */
-  const date = Object.keys(this.state.match.dates).find((key) =>
-      String(this.state.match.dates[key]) === this.matchDate.value
-    )
-
-  placeArray.forEach((place) => {
-    let header = [
-        ['Stadtzürcher Tennismeisterschaft'],
-        [`Nenngelder für ${date}`],
-        [],
-        [`${PLACES[place] || place}`, null, '50.- oder 30.- (Junioren Jg. 1999 oder jünger)'],
-        [],
-        ['bezahlt', 'Kat.', 'Zeit', 'Name', 'Betrag bez.', 'Quittung']
-    ]
-
-      // Per place
-    let payListPerPlace = []
-    this.state.match.filtered.forEach((match) => {
-      [match.Spieler1, match.Spieler2].forEach((matchPlayer) => {
-        if (!!matchPlayer & (match.Ort === place | FILTER_OFF === place)) {
-          const player = this.state.player.players.find((player) =>
-              (player.Konkurrenz === match.Konkurrenz) & (player.name === matchPlayer)
-            )
-          let paid = null
-          if (player.BezahltAm < this.matchDate.value) {
-            paid = date2s(player.BezahltAm)
-          }
-          if (player.Bezahlt) {
-            paid = 'OK'
-          }
-          let price
-          if (player.isDoubles) {
-            price = (player.isJunior ? 15 : 25) + (player.isJuniorDP ? 15 : 25)
-          } else {
-            price = player.isJunior ? 30 : 50
-          }
-          payListPerPlace.push([ paid, match.Konkurrenz, time2s(match.Datum), `(${price}.-) ${matchPlayer}` ])
-        }
-      })
-    })
-
-    let footer = [
-        [],
-        ['Datum'],
-        ['Turnierleiter', null, null, 'Kassierer'],
-        ['Betrag von Spielern erhalten', null, null, 'Betrag von Turnierleiter erhalten']
-    ]
-    console.log(`Generated pay list per place ${place}:`, payListPerPlace)
-    worksheets[place] = Excel.SheetFromArray(header.concat(payListPerPlace, footer))
-    paylist.SheetNames.push(place)
-    paylist.Sheets[place] = worksheets[place]
-  })
-  Excel.saveAs(paylist, 'Zahlliste.xlsx')
-}
-
-function generateSchedule (event) {
-  event.preventDefault()
-
-  const matchlist = new Excel.Workbook()
-  matchlist.SheetNames = []
-  matchlist.Sheets = {}
-
-  const worksheets = {}
-
-  let placeArray = this.state.match.places
-  if (placeArray.length > 1) {
-      // placeArray = placeArray.concat([FILTER_OFF])
-  }
-  const date = Object.keys(this.state.match.dates).find((key) =>
-      String(this.state.match.dates[key]) === this.matchDate.value
-    )
-
-  placeArray.forEach(place => {
-    let header = [
-        ['Stadtzürcher Tennismeisterschaft'],
-        [`Spielplan für den ${date} (${PLACES[place] || place})`],
-        [],
-        ['Platz', 'Zeit', 'Kategorie', 'Spieler 1', '', 'Spieler 2', '', '1. Satz', '2. Satz', '3. Satz', 'WO Grund']
-    ]
-    let matchListPerPlace = this.state.match.filtered.filter((match) => (match.Ort === place | place === FILTER_OFF)).map((match) =>
-        [null, time2s(match.Datum), match.Konkurrenz, match.Spieler1, match.Spieler1Klassierung, match.Spieler2, match.Spieler2Klassierung]
-      )
-    console.log('Generated match list per place:', matchListPerPlace)
-    worksheets[place] = Excel.SheetFromArray(header.concat(matchListPerPlace))
-    matchlist.SheetNames.push(place)
-    matchlist.Sheets[place] = worksheets[place]
-  })
-
-  Excel.saveAs(matchlist, 'Spielplan.xlsx')
-}

+ 0 - 35
src/matchList/state.js

@@ -1,35 +0,0 @@
-/** @module matchList/state */
-
-/**
- * state.js
- *
- * Collection of everything which has to do with state changes.
- **/
-
-/** actionTypes define what actions are handeled by the reducer. */
-export const actions = {
-  setState: matches => {
-    return {
-      type: 'SET_MATCHES',
-      matches
-    }
-  }
-}
-console.log('State actions', actions)
-
-/** state definition */
-export const state = {
-  allMatches: [],
-  filteredMatches: [],
-  filters: {}
-}
-console.log('State state', state)
-
-/** reducer is called by the redux dispatcher and handles all component actions */
-export function reducer (state = [], action) {
-  let nextState = state
-  return nextState
-}
-
-/** sagas are asynchronous workers (JS generators) to handle the state. */
-export function * saga () {}

+ 7 - 9
src/playerList/components/PlayerForm.js

@@ -22,15 +22,14 @@ class PlayerForm extends React.Component {
   handleFileUpload (event) {
     event.preventDefault()
     const { fileUploadStart } = this.props.actions
-    const { alertAdd } = this.props.alerts
     const { files } = this.playerListFile
-    if (files.length === 0) {
-      alertAdd({ type: 'info', text: 'Datei entfernt' })
-      return
-    }
-    if (files.length > 1) {
-      alertAdd({ type: 'warning', text: 'Mehrere Dateien gesendet. Nur die erste wird verarbeitet.' })
-    }
+    // if (files.length === 0) {
+    //   alertAdd({ type: 'info', text: 'Datei entfernt' })
+    //   return
+    // }
+    // if (files.length > 1) {
+    //   alertAdd({ type: 'warning', text: 'Mehrere Dateien gesendet. Nur die erste wird verarbeitet.' })
+    // }
     const file = files[0]
     fileUploadStart(file)
   }
@@ -44,7 +43,6 @@ class PlayerForm extends React.Component {
           <FieldGroup
             id='playerListFile'
             label='PlayerList.xls File'
-            help='Die Datei wird von der Swisstennis Turniersoftware generiert.'
             type='file'
             file={file}
             inputRef={input => { this.playerListFile = input }}

+ 0 - 1
src/playerList/components/PlayerTable.js

@@ -22,7 +22,6 @@ class PlayerRow extends React.Component {
 
 class PlayerTable extends React.Component {
   render () {
-    console.log('PlayerTable state', this.props.state)
     const { allPlayers, filteredPlayers } = this.props.state || { allPlayers: [], filteredPlayers: [] }
 
     return (

+ 0 - 27
src/playerList/index.js

@@ -1,6 +1,5 @@
 import { actions, reducer, state, saga } from './state'
 import components from './components'
-import Player from '../classes/player'
 
 const filters = {
   all: players => players
@@ -9,29 +8,3 @@ const filters = {
 const selectors = {}
 
 export default { actions, components, filters, selectors, reducer, state, saga }
-
-const FILTER_OFF = 'Alle'
-
-function calculatePlayerFilters () {
-  const categories = []
-  this.state.player.players.forEach((item) => {
-    if (!!item.Konkurrenz & !categories.includes(item.Konkurrenz)) {
-      categories.push(item.Ort)
-    }
-  })
-  const player = { ...this.state.player, categories }
-  this.setState({ player })
-}
-
-function filterPlayers () {
-  const filters = {
-    junior: this.playerJunior.checked,
-    paid: this.playerPaid.checked,
-    category: this.playerCategory.value !== FILTER_OFF ? this.playerCategory.value : null
-  }
-  console.log('New filter settings:', filters)
-
-  const player = { ...this.state.player, filters }
-  player.filtered = player.players
-  this.setState({ player })
-}

+ 43 - 15
src/playerList/state.js

@@ -10,12 +10,6 @@ import { generatePlayerList } from './functions'
 
 /** actionTypes define what actions are handeled by the reducer. */
 export const actions = {
-  setState: players => {
-    return {
-      type: 'SET_PLAYERS',
-      players
-    }
-  },
   fileUploadStart: file => {
     return {
       type: 'PLAYER_FILE_UPLOAD_START',
@@ -34,9 +28,9 @@ export const actions = {
       alert: { type: 'warning', text: error.toString() }
     }
   },
-  filterPlayers: () => {
+  filterSortPlayers: () => {
     return {
-      type: 'PLAYER_FILTER'
+      type: 'PLAYER_FILTER_SORT'
     }
   }
 }
@@ -46,7 +40,34 @@ console.log('State actions', actions)
 export const state = {
   allPlayers: [],
   filteredPlayers: [],
-  filters: [],
+  filters: [
+    {
+      filterFunction: (player, filterValue) => {
+        return player.isJunior === filterValue
+      },
+      filterValue: true
+    }
+  ],
+  sorting: [
+    (player1, player2) => {
+      if (player1.Konkurrenz > player2.Konkurrenz) {
+        return 1
+      }
+      if (player1.Konkurrenz === player2.Konkurrenz) {
+        return 0
+      }
+      return -1
+    },
+    (player1, player2) => {
+      if (player1.Vorname > player2.Vorname) {
+        return 1
+      }
+      if (player1.Vorname === player2.Vorname) {
+        return 0
+      }
+      return -1
+    }
+  ],
   fileUpload: 'idle',
   file: null
 }
@@ -61,11 +82,18 @@ export function reducer (state = [], action) {
       return { ...state, fileUpload: 'finished', allPlayers: action.allPlayers }
     case 'PLAYER_FILE_UPLOAD_FAILURE':
       return { ...state, fileUpload: 'failure' }
-    case 'PLAYER_FILTER':
-      const { allPlayers, filters } = state
-      const filteredPlayers = allPlayers.filter(player =>
-        filters.length ? filters.map(filter => filter(player)).every() : true
-      )
+    case 'PLAYER_FILTER_SORT':
+      const { allPlayers, filters, sorting } = state
+      const sortedPlayers = allPlayers.sort((player1, player2) => {
+        return sorting.length ? sorting.reduce((acc, sortFun) => {
+          return acc === 0 ? sortFun(player1, player2) : acc
+        }, 0) : 0
+      })
+      console.log('Sorted players', sortedPlayers)
+      const filteredPlayers = sortedPlayers.filter(player => {
+        return filters.length ? filters.map(filter => filter.filterFunction(player, filter.filterValue)).every(value => !!value) : true
+      })
+      console.log('Filtered players', filteredPlayers)
       return { ...state, filteredPlayers }
     default:
       return state
@@ -78,7 +106,7 @@ function * uploadFile (action) {
     const allPlayers = yield call(generatePlayerList, action.file)
     console.log('PlayerList success!', actions.fileUploadSuccess(allPlayers))
     yield put(actions.fileUploadSuccess(allPlayers))
-    yield put(actions.filterPlayers())
+    yield put(actions.filterSortPlayers())
   } catch (error) {
     console.log('PlayerList failure!', actions.fileUploadFailure(error))
     yield put(actions.fileUploadFailure(error))

+ 3 - 1
src/startPage/components/StartPage.js

@@ -1,5 +1,6 @@
 import React from 'react'
 import { PlayerForm } from '../../playerList/components'
+import { MatchForm } from '../../calendar/components'
 
 class StartPage extends React.Component {
   render () {
@@ -7,7 +8,8 @@ class StartPage extends React.Component {
       <div>
         <h1>SZTM Planungshelfer</h1>
         <p>Willkommen beim SZTM Planungshelfer</p>
-        <PlayerForm state={this.props.playerList} actions={this.props.playerListActions} alerts={this.props.alertsActions} />
+        <PlayerForm state={this.props.playerList} actions={this.props.playerListActions} />
+        <MatchForm state={this.props.calendar} actions={this.props.calendarActions} />
       </div>
     )
   }