瀏覽代碼

changed code to use redux.

Tomislav Cvetic 7 年之前
父節點
當前提交
e6c23f5110

+ 2 - 0
package.json

@@ -9,6 +9,8 @@
     "moment": "^2.18.1",
     "react": "^15.5.4",
     "react-dom": "^15.5.4",
+    "react-redux": "^5.0.5",
+    "redux": "^3.6.0",
     "xlsx": "^0.10.4"
   },
   "devDependencies": {

+ 2 - 2
src/App.js

@@ -34,7 +34,6 @@ import 'bootstrap/dist/css/bootstrap.css'
  * - Shows the calculated payment lists
  * - Points out problems
  *
- *
  */
 
 const FILTER_OFF = 'Alle'
@@ -55,7 +54,8 @@ class App extends React.Component {
       worksheets: {
         'PlayerList': null,
         'Calendar': null
-      }
+      },
+      activeTab: 1
     }
 
     // Bind 'this' to functions

+ 374 - 0
src/Main.js

@@ -0,0 +1,374 @@
+import React from 'react'           // React to manage the GUI
+import Player from './player'       // Everything that has to do with players
+import Match from './match'         // Everything that has to do with matches
+import Excel from './excel'         // Helper files to create Excel files
+import { date2s, time2s } from './helpers'
+import 'bootstrap/dist/css/bootstrap.css'
+
+/**
+ * General Application Design
+ *
+ * 4 Components:
+ * - PlayerList (representing the PlayerList Excel file)
+ * - Calendar (representing the Calendar Excel file)
+ * - MatchList (representing the Spielliste Excel file)
+ * - PaymentList (representing the Zahlliste Excel file)
+ *
+ * PlayerList
+ * - Shows the relevant information from the file
+ * - Shows calculated information from combination with Calendar
+ * - Points out potential problems
+ * - Allows access to player information
+ *
+ * Calendar
+ * - Shows the relevant information from the file
+ * - Shows calculated information from combination with PlayerList
+ * - Points out potential problems
+ * - Allows access to match information
+ *
+ * MatchList
+ * - Shows the calculated match lists
+ * - Points out problems
+ *
+ * PaymentList
+ * - Shows the calculated payment lists
+ * - Points out problems
+ *
+ *
+ */
+
+const FILTER_OFF = 'Alle'
+
+/** Main application */
+class Main extends React.Component {
+  /**
+   * Constructor
+   * Defines the state and binds all 'this' in all functions to the object.
+   */
+  constructor () {
+    super()
+
+    // Define the state
+    this.state = {
+      player: Player.state,
+      match: Match.state,
+      worksheets: {
+        'PlayerList': null,
+        'Calendar': null
+      },
+      activeTab: 1
+    }
+
+    // Bind 'this' to functions
+    this.handlePlayerList = this.handlePlayerList.bind(this)
+    this.handleCalendar = this.handleCalendar.bind(this)
+    this.generatePlayerList = this.generatePlayerList.bind(this)
+    this.generateCalendar = this.generateCalendar.bind(this)
+    this.filterMatches = this.filterMatches.bind(this)
+    this.filterPlayers = this.filterPlayers.bind(this)
+    this.generateSchedule = this.generateSchedule.bind(this)
+    this.generatePayTable = this.generatePayTable.bind(this)
+  }
+
+  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 })
+  }
+
+  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 })
+  }
+
+  handlePlayerList (event) {
+    const file = this.playerList.files[0]
+    Excel.readWorkbook(file, this.generatePlayerList)
+  }
+
+  handleCalendar (event) {
+    const file = this.calendar.files[0]
+    Excel.readWorkbook(file, this.generateCalendar)
+  }
+
+  generatePlayerList (worksheet) {
+    console.log('About to read the player list.')
+    const worksheets = { ...this.state.worksheets }
+    worksheets['PlayerList'] = worksheet
+    this.setState({ worksheets })
+
+    if (worksheet[4].length !== 32 & worksheet[3][0] !== 'Konkurrenz' & worksheet[3][31] !== 'bezahlt') {
+      throw Error('Wrong file structure.')
+    }
+    const players = worksheet.slice(4, worksheet.length).map((playerData) => new Player.Player(playerData))
+    const player = { ...this.state.player }
+    player.players = players
+    this.setState({ player })
+    this.calculatePayDate()
+    this.filterPlayers()
+    console.log('State after generating player list:', this.state)
+  }
+
+  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.MatchClass(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)
+  }
+
+  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 })
+  }
+
+  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 })
+  }
+
+  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 = [
+        [`Spielplan für den ${date} (${place})`],
+        [],
+        ['Ort', '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) =>
+        [match.Ort, 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, 'Spielliste.xlsx')
+  }
+
+  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
+          }
+        }
+      })
+    })
+  }
+
+  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 = [
+        [`Zahlliste für den ${date} (${place})`],
+        [],
+        ['bereits bez.', 'Kategorie', 'Zeit', 'Name', 'Betrag bez.', 'Quittung abgeben']
+      ]
+
+      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), `${matchPlayer} (${price}.-)` ])
+          }
+        })
+      })
+
+      let footer = [
+        [],
+        ['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')
+  }
+
+  render () {
+    const PlayerList = Player.components.PlayerList
+    const MatchList = Match.components.MatchList
+
+    const places = this.state.match.places
+    const dates = this.state.match.dates
+    const matchCategories = this.state.match.categories
+    const playerCategories = this.state.player.categories
+
+    return (
+      <div className='container'>
+        <form>
+          <fieldset>
+            <legend>Input Files</legend>
+            <label htmlFor='PlayerList'>Swisstennis PlayerList.xls</label>
+            <input type='file' id='PlayerList' ref={(input) => { this.playerList = input }} accept='.xls' placeholder='PlayerList File' onChange={this.handlePlayerList} />
+            <label htmlFor='Calendar'>Swisstennis Calendar.xls</label>
+            <input type='file' id='Calendar' ref={(input) => { this.calendar = input }} accept='.xls' placeholder='Calendar File' onChange={this.handleCalendar} />
+          </fieldset>
+          <fieldset><legend>Filter</legend>
+            <label htmlFor='Date'>Datum</label>
+            <select id='Date' ref={(input) => { this.matchDate = input }} onChange={this.filterMatches}>
+              <option>{FILTER_OFF}</option>
+              {Object.keys(dates).map((key) => (
+                <option key={key} value={dates[key]}>{key}</option>
+            ))}
+            </select>
+            <label htmlFor='Place'>Ort</label>
+            <select id='Place' ref={(input) => { this.matchPlace = input }} onChange={this.filterMatches}>
+              <option>{FILTER_OFF}</option>
+              {places.map((place, key) => (
+                <option key={key}>{place}</option>
+            ))}
+            </select>
+            <label htmlFor='MatchCategory'>Match Konkurrenz</label>
+            <select id='MatchCategory' ref={(input) => { this.matchCategory = input }} onChange={this.filterMatches}>
+              <option>{FILTER_OFF}</option>
+              {matchCategories.map((category, key) => (
+                <option key={key}>{category}</option>
+            ))}
+            </select>
+            <label htmlFor='Junior'>Junior</label>
+            <input id='Junior' ref={(input) => { this.playerJunior = input }} type='checkbox' onChange={this.filterPlayers} />
+            <label htmlFor='Paid'>Bezahlt</label>
+            <input id='Paid' ref={(input) => { this.playerPaid = input }} type='checkbox' onChange={this.filterPlayers} />
+            <label htmlFor='PlayerCategory'>Spieler Konkurrenz</label>
+            <select id='PlayerCategory' ref={(input) => { this.playerCategory = input }} onChange={this.filterPlayers}>
+              <option>{FILTER_OFF}</option>
+              {playerCategories.map((category, key) => (
+                <option key={key}>{category}</option>
+            ))}
+            </select>
+          </fieldset>
+          <fieldset><legend>Output Files</legend>
+            <button onClick={this.generateSchedule} disabled={!this.state.match.filtered.length}>Spielliste generieren</button>
+            <button onClick={this.generatePayTable} disabled={(!this.state.match.filtered.length | !this.state.player.filtered.length)}>Zahlliste generieren</button>
+          </fieldset>
+        </form>
+        <PlayerList player={this.state.player} />
+        <MatchList match={this.state.match} />
+      </div>
+    )
+  }
+}
+
+export default Main

+ 109 - 0
src/excel/SZTM_Zahlliste.bas

@@ -0,0 +1,109 @@
+Attribute VB_Name = "Module1"
+Sub SZTM_Zahlliste()
+Attribute SZTM_Zahlliste.VB_Description = "Formatiert die Zahlliste"
+Attribute SZTM_Zahlliste.VB_ProcData.VB_Invoke_Func = "F\n14"
+'
+' SZTM_Zahlliste Macro
+' Formatiert die Zahlliste
+'
+' Keyboard Shortcut: Ctrl+Shift+F
+'
+    Dim ws As Worksheet
+    For Each ws In ActiveWorkbook.Sheets
+        With ws
+        
+            ws.Activate
+                
+            Range("A3:F3").Select
+            With Selection.Interior
+                .Pattern = xlSolid
+                .PatternColorIndex = xlAutomatic
+                .ThemeColor = xlThemeColorDark1
+                .TintAndShade = -0.149998474074526
+                .PatternTintAndShade = 0
+            End With
+            Selection.Font.Bold = True
+            Range("A1:F1").Select
+            With Selection
+                .HorizontalAlignment = xlLeft
+                .VerticalAlignment = xlBottom
+                .WrapText = False
+                .Orientation = 0
+                .AddIndent = False
+                .IndentLevel = 0
+                .ShrinkToFit = False
+                .ReadingOrder = xlContext
+                .MergeCells = True
+            End With
+            Selection.Merge
+            With Selection.Font
+                .Name = "Calibri"
+                .Size = 14
+                .Strikethrough = False
+                .Superscript = False
+                .Subscript = False
+                .OutlineFont = False
+                .Shadow = False
+                .Underline = xlUnderlineStyleNone
+                .ThemeColor = xlThemeColorLight1
+                .TintAndShade = 0
+                .ThemeFont = xlThemeFontMinor
+            End With
+            Selection.Font.Bold = True
+            ActiveWindow.SmallScroll Down:=-3
+            Range("A3").Select
+            Range(Selection, ActiveCell.SpecialCells(xlLastCell)).Select
+            Selection.Borders(xlDiagonalDown).LineStyle = xlNone
+            Selection.Borders(xlDiagonalUp).LineStyle = xlNone
+            With Selection.Borders(xlEdgeLeft)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlEdgeTop)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlEdgeBottom)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlEdgeRight)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlInsideVertical)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlInsideHorizontal)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            Columns("A:A").EntireColumn.AutoFit
+            Columns("B:B").EntireColumn.AutoFit
+            Columns("C:C").EntireColumn.AutoFit
+            Columns("D:D").EntireColumn.AutoFit
+            Columns("E:E").EntireColumn.AutoFit
+            Columns("F:F").EntireColumn.AutoFit
+            Range("A4").Select
+            Range(Selection, ActiveCell.SpecialCells(xlLastCell)).Select
+            Range("A4").Select
+            Range(Selection, ActiveCell.SpecialCells(xlLastCell)).Select
+            Selection.RowHeight = 33.75
+            
+        End With
+    Next
+
+End Sub

+ 5 - 1
src/helpers.js

@@ -20,4 +20,8 @@ function sortTable (array, columns) {
   }
 }
 
-export { date2s, time2s, datetime2s }
+function normalize (item, type) {
+  return item ? String(item).replace(/\s+/g, ' ').trim() : null
+}
+
+export { date2s, time2s, datetime2s, sortTable, normalize }

+ 115 - 5
src/index.js

@@ -1,7 +1,117 @@
-import Dotenv from 'dotenv'
+/** @module SZTMExcel */
+
+// Import dependencies
 import React from 'react'
-import { render } from 'react-dom'
-import App from './App'
+import ReactDOM from 'react-dom'
+import { createStore, combineReducers, bindActionCreators, compose } from 'redux'
+import { Provider, connect } from 'react-redux'
+/** react-router is not used in this project.
+import { browserHistory, Router, Route, IndexRoute } from 'react-router'
+import { syncHistoryWithStore, routerReducer } from 'react-router-redux'
+*/
+
+// Import the main app
+import Main from './Main'
+
+// Import the submodules
+import player from './player'
+import match from './match'
+
+/**
+ * Redux Section
+ */
+
+/** The root reducer is combined from all sub-module reducers */
+const rootReducer = combineReducers({
+  player: player.reducer,
+  match: match.reducer,
+})
+console.log('Root reducer:', rootReducer)
+
+/** The default state is combined from all sub-module states */
+const defaultState = {
+  player: player.state,
+  match: match.state,
+}
+console.log('Default state:', defaultState)
+
+/** The enhancer allows to use Redux development tools in Chrome */
+const enhancers = compose(
+  window.devToolsExtension ? window.devToolsExtension() : f => f
+)
+console.log('Enhancers:', enhancers)
+
+/** Build the Redux store from the rootReducer, the defualtState and the enhancers. */
+const store = createStore(rootReducer, defaultState, enhancers)
+console.log('Store:', store)
+/** react-route is not used in this project.
+const history = syncHistoryWithStore(browserHistory, store)
+console.log('history:', history)
+*/
+
+/** Collect the action creators from all modules in actionCreators */
+const actionCreators = { 
+  player: player.actions,
+  match: match.actions,
+}
+
+/** Creates a function  */
+function mapStateToProps (state) {
+  const propState = {}
+  Object.keys(state).forEach(key => {
+    propState[key] = state[key]
+  })
+  console.log('Mapping state to props:', state, propState)
+  return propState
+}
+
+function mapDispatchToProps (dispatch) {
+  const boundActionCreators = {}
+  console.log('ActionCreators', actionCreators)
+  Object.keys(actionCreators).forEach(key => {
+    boundActionCreators[`${key}Actions`] = bindActionCreators(actionCreators[key], dispatch)
+  })
+  console.log('Mapping dispatch to props:', dispatch, boundActionCreators)
+  return boundActionCreators
+}
+
+const App = connect(mapStateToProps, mapDispatchToProps)(Main)
+
+
+/**
+ * React-Router Section
+ **/
+
+/** Combine the routes from all modules.
+const router = (
+  <Provider store={store}>
+    <Router history={history}>
+      <Route component={App}>
+        <Route path='/'>
+          <IndexRoute component={demo_module.components.DemoModule} />
+          <Route path='/project/:projectId' component={project.components.Project} />
+          <Route path='/project' component={project.components.Project} />
+          <Route path='/registermap/:registermapId' component={registermap.components.Registermap} />
+          <Route path='/registermap' component={registermap.components.Registermap} />
+          <Route path='/sample' component={Sample} />
+        </Route>
+        {demo_module.routes}
+        <Route path='/login' />
+      </Route>
+    </Router>
+  </Provider>
+)
+*/
+const provider = (
+  <Provider store={store}>
+    <App />
+  </Provider>
+)
 
-Dotenv.config()
-render(<App />, document.getElementById('root'))
+/**
+ * Render the app
+ **/
+ReactDOM.render(
+  provider,
+  document.getElementById('root')
+)

+ 0 - 0
src/match/components/match-disp.js → src/match/components/MatchDisp.js


+ 1 - 1
src/match/components/match-list.js → src/match/components/MatchList.js

@@ -1,5 +1,5 @@
 import React from 'react'
-import MatchDisp from './match-disp'
+import MatchDisp from './MatchDisp'
 
 class MatchList extends React.Component {
   render () {

+ 3 - 2
src/match/components/index.js

@@ -1,4 +1,5 @@
-import MatchDisp from './match-disp'
-import MatchList from './match-list'
+import MatchDisp from './MatchDisp'
+import MatchList from './MatchList'
 
 export default { MatchDisp, MatchList }
+ 

+ 11 - 17
src/match/index.js

@@ -1,15 +1,21 @@
+import { actions, reducer, state } from './state'
 import components from './components'
+import { normalize } from '../helpers.js'
 
-function normalize (item, type) {
-  return item ? String(item).replace(/\s+/g, ' ').trim() : null
+const filters = {
+  all: players => players,
 }
 
+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
+   * Ort | Datum | Zeit | Konkurrenz | Spieler 1 | Spieler 1 Klassierung | Spieler 2 | 
+   * Spieler 2 Klassierung | [Resultat]
    */
   constructor (data) {
     this.Ort = normalize(data[0])
@@ -19,20 +25,8 @@ class MatchClass {
     this.Spieler1Klassierung = normalize(data[5])
     this.Spieler2 = normalize(data[6])
     this.Spieler2Klassierung = normalize(data[7])
+    this.isDoubles = this.Konkurrenz.match(/DM.*|[MW]D.*/)
   }
-
-  isDoubles () {
-    return this.Konkurrenz.match(/DM.*|[MW]D.*/)
-  }
-}
-
-const state = {
-  matches: [],
-  filtered: [],
-  places: [],
-  dates: [],
-  categories: [],
-  filters: {}
 }
 
-export default { MatchClass, components, state }
+export default { MatchClass, actions, components, filters, selectors, reducer, state }

+ 30 - 0
src/match/state.js

@@ -0,0 +1,30 @@
+/** @module match/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 => {
+    type: 'SET_MATCHES',
+    matches
+  }
+}
+console.log('State actions', actions)
+
+/** state definition */
+export const state = {}
+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 () {}

+ 0 - 0
src/player/components/player-disp.js → src/player/components/PlayerDisp.js


+ 1 - 1
src/player/components/player-list.js → src/player/components/PlayerList.js

@@ -1,5 +1,5 @@
 import React from 'react'
-import PlayerDisp from './player-disp'
+import PlayerDisp from './PlayerDisp'
 
 class PlayerList extends React.Component {
   render () {

+ 2 - 2
src/player/components/index.js

@@ -1,4 +1,4 @@
-import PlayerDisp from './player-disp'
-import PlayerList from './player-list'
+import PlayerDisp from './PlayerDisp'
+import PlayerList from './PlayerList'
 
 export default { PlayerDisp, PlayerList }

+ 0 - 13
src/player/components/player-filters.js

@@ -1,13 +0,0 @@
-import React from 'react'
-import { date2s } from '../../helpers'
-
-class PlayerFilters extends React.Component {
-  render () {
-    const filters = this.props.filters
-    return (
-      <form />
-    )
-  }
-}
-
-export default PlayerFilters

+ 6 - 9
src/player/index.js

@@ -1,15 +1,12 @@
+import { actions, reducer, state } from './state'
 import components from './components'
+import { normalize } from '../helpers.js'
 
-function normalize (item, type) {
-  return item ? String(item).replace(/\s+/g, ' ').trim() : null
+const filters = {
+  all: players => players,
 }
 
-const state = {
-  players: [],
-  filtered: [],
-  categories: [],
-  filters: {}
-}
+const selectors = {}
 
 /** Class representing a player */
 class Player {
@@ -44,4 +41,4 @@ class Player {
   }
 }
 
-export default { Player, components, state }
+export default { Player, actions, components, filters, selectors, reducer, state }

+ 30 - 0
src/player/state.js

@@ -0,0 +1,30 @@
+/** @module player/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: players => {
+    type: 'SET_PLAYERS',
+    players
+  }
+}
+console.log('State actions', actions)
+
+/** state definition */
+export const state = {}
+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 () {}

+ 45 - 4
yarn.lock

@@ -1559,6 +1559,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
 
+create-react-class@^15.5.3:
+  version "15.5.4"
+  resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.5.4.tgz#188875cb15e2fbe4ca595b6f43eb0e4a1f00fe50"
+  dependencies:
+    fbjs "^0.8.9"
+    loose-envify "^1.3.1"
+    object-assign "^4.1.1"
+
 cross-spawn@4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41"
@@ -2809,6 +2817,10 @@ hoek@2.x.x:
   version "2.16.3"
   resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
 
+hoist-non-react-statics@^1.0.3:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
+
 home-or-tmp@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
@@ -3044,7 +3056,7 @@ interpret@^1.0.0:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90"
 
-invariant@^2.1.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
+invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
   dependencies:
@@ -3771,6 +3783,10 @@ locate-path@^2.0.0:
     p-locate "^2.0.0"
     path-exists "^3.0.0"
 
+lodash-es@^4.2.0, lodash-es@^4.2.1:
+  version "4.17.4"
+  resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7"
+
 lodash._reinterpolate@~3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@@ -3808,7 +3824,7 @@ lodash.uniq@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
 
-"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.2.0, lodash@^4.3.0:
+"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
   version "4.17.4"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
 
@@ -4169,7 +4185,7 @@ oauth-sign@~0.8.1:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
 
-object-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0:
+object-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
 
@@ -4786,7 +4802,7 @@ promise@7.1.1, promise@^7.1.1:
   dependencies:
     asap "~2.0.3"
 
-prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@~15.5.7:
+prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@~15.5.7:
   version "15.5.10"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154"
   dependencies:
@@ -4956,6 +4972,18 @@ react-prop-types@^0.4.0:
   dependencies:
     warning "^3.0.0"
 
+react-redux@^5.0.5:
+  version "5.0.5"
+  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.5.tgz#f8e8c7b239422576e52d6b7db06439469be9846a"
+  dependencies:
+    create-react-class "^15.5.3"
+    hoist-non-react-statics "^1.0.3"
+    invariant "^2.0.0"
+    lodash "^4.2.0"
+    lodash-es "^4.2.0"
+    loose-envify "^1.1.0"
+    prop-types "^15.5.10"
+
 react-scripts@^1.0.7:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-1.0.7.tgz#fe1436dda03bb45465c76d097cfea4f32eb7cbbb"
@@ -5116,6 +5144,15 @@ reduce-function-call@^1.0.1:
   dependencies:
     balanced-match "^0.4.2"
 
+redux@^3.6.0:
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/redux/-/redux-3.6.0.tgz#887c2b3d0b9bd86eca2be70571c27654c19e188d"
+  dependencies:
+    lodash "^4.2.1"
+    lodash-es "^4.2.1"
+    loose-envify "^1.1.0"
+    symbol-observable "^1.0.2"
+
 regenerate@^1.2.1:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
@@ -5772,6 +5809,10 @@ sw-toolbox@^3.4.0:
     path-to-regexp "^1.0.1"
     serviceworker-cache-polyfill "^4.0.0"
 
+symbol-observable@^1.0.2:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d"
+
 symbol-tree@^3.2.1:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"