|
@@ -1,14 +1,46 @@
|
|
|
import React from 'react' // React to manage the GUI
|
|
|
-import XLSX from 'xlsx' // xlsx to read and write Excel XLSX Workbooks
|
|
|
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'
|
|
|
+
|
|
|
+/**
|
|
|
+ * 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 App extends React.Component {
|
|
|
- /**
|
|
|
+ /**
|
|
|
* Constructor
|
|
|
* Defines the state and binds all 'this' in all functions to the object.
|
|
|
*/
|
|
@@ -22,11 +54,6 @@ class App extends React.Component {
|
|
|
worksheets: {
|
|
|
'PlayerList': null,
|
|
|
'Calendar': null
|
|
|
- },
|
|
|
- filters: {
|
|
|
- date: null,
|
|
|
- place: null,
|
|
|
- category: null
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -35,11 +62,10 @@ class App extends React.Component {
|
|
|
this.handleCalendar = this.handleCalendar.bind(this)
|
|
|
this.generatePlayerList = this.generatePlayerList.bind(this)
|
|
|
this.generateCalendar = this.generateCalendar.bind(this)
|
|
|
- this.filterData = this.filterData.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)
|
|
|
-
|
|
|
- this.unpaid = 'off'
|
|
|
}
|
|
|
|
|
|
calculateMatchFilters () {
|
|
@@ -47,8 +73,9 @@ class App extends React.Component {
|
|
|
const places = []
|
|
|
const categories = []
|
|
|
this.state.match.matches.forEach((item) => {
|
|
|
- if (!!item.DatumString & !dates.hasOwnProperty(item.DatumString)) {
|
|
|
- dates[item.DatumString] = item.Datum
|
|
|
+ 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)
|
|
@@ -61,29 +88,25 @@ class App extends React.Component {
|
|
|
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]
|
|
|
- this.readWorkbook(file, this.generatePlayerList)
|
|
|
+ Excel.readWorkbook(file, this.generatePlayerList)
|
|
|
}
|
|
|
|
|
|
handleCalendar (event) {
|
|
|
const file = this.calendar.files[0]
|
|
|
- this.readWorkbook(file, this.generateCalendar)
|
|
|
- }
|
|
|
-
|
|
|
- readWorkbook (file, callback) {
|
|
|
- const reader = new FileReader()
|
|
|
- reader.onload = (e) => {
|
|
|
- const data = e.target.result
|
|
|
- const workbook = XLSX.read(data, {type: 'binary', cellDates: true})
|
|
|
- console.log('Workbook after read:', workbook)
|
|
|
- if (workbook.SheetNames.length !== 1) {
|
|
|
- throw Error(`Expected only one worksheet in the file, but found ${workbook.SheetNames.length}.`)
|
|
|
- }
|
|
|
- const worksheet = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]], { header: 1, raw: true })
|
|
|
- callback(worksheet)
|
|
|
- }
|
|
|
- reader.readAsBinaryString(file)
|
|
|
+ Excel.readWorkbook(file, this.generateCalendar)
|
|
|
}
|
|
|
|
|
|
generatePlayerList (worksheet) {
|
|
@@ -99,7 +122,8 @@ class App extends React.Component {
|
|
|
const player = { ...this.state.player }
|
|
|
player.players = players
|
|
|
this.setState({ player })
|
|
|
- this.filterData()
|
|
|
+ this.calculatePayDate()
|
|
|
+ this.filterPlayers()
|
|
|
console.log('State after generating player list:', this.state)
|
|
|
}
|
|
|
|
|
@@ -117,18 +141,18 @@ class App extends React.Component {
|
|
|
match.matches = matches
|
|
|
this.setState({ match })
|
|
|
this.calculateMatchFilters()
|
|
|
- this.filterData()
|
|
|
+ this.calculatePayDate()
|
|
|
+ this.filterMatches()
|
|
|
console.log('State after generating calendar:', this.state)
|
|
|
}
|
|
|
|
|
|
- filterData () {
|
|
|
+ filterMatches () {
|
|
|
const filters = {
|
|
|
- date: this.date.value !== FILTER_OFF ? this.date.value : null,
|
|
|
- place: this.place.value !== FILTER_OFF ? this.place.value : null,
|
|
|
- category: this.category.value !== FILTER_OFF ? this.category.value : null,
|
|
|
+ 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)
|
|
|
- this.setState({ filters })
|
|
|
|
|
|
const match = { ...this.state.match }
|
|
|
match.filtered = match.matches.filter((match) => {
|
|
@@ -142,7 +166,20 @@ class App extends React.Component {
|
|
|
})
|
|
|
this.setState({ match })
|
|
|
|
|
|
- const player = { ...this.state.player }
|
|
|
+ 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 })
|
|
|
}
|
|
@@ -161,7 +198,7 @@ class App extends React.Component {
|
|
|
placeArray = placeArray.concat([FILTER_OFF])
|
|
|
}
|
|
|
const date = Object.keys(this.state.match.dates).find((key) =>
|
|
|
- String(this.state.match.dates[key]) === this.date.value
|
|
|
+ String(this.state.match.dates[key]) === this.matchDate.value
|
|
|
)
|
|
|
|
|
|
placeArray.forEach((place) => {
|
|
@@ -171,7 +208,7 @@ class App extends React.Component {
|
|
|
['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, match.ZeitString, match.Konkurrenz, match.Spieler1, match.Spieler1Klassierung, match.Spieler2, match.Spieler2Klassierung]
|
|
|
+ [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))
|
|
@@ -182,6 +219,29 @@ class App extends React.Component {
|
|
|
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()
|
|
|
|
|
@@ -191,40 +251,12 @@ class App extends React.Component {
|
|
|
|
|
|
const worksheets = {}
|
|
|
|
|
|
- const payerList = {}
|
|
|
- this.state.match.matches.forEach((match) => {
|
|
|
- const id1 = `${match.Konkurrenz} ${match.Spieler1}`
|
|
|
- if (!!match.Spieler1 & !payerList.hasOwnProperty(id1)) {
|
|
|
- let foundPlayer = this.state.player.players.find((player) =>
|
|
|
- (player.name() === match.Spieler1) & (player.Konkurrenz === match.Konkurrenz)
|
|
|
- )
|
|
|
- if (!foundPlayer) {
|
|
|
- console.log('Debug payerlist:', foundPlayer, match)
|
|
|
- throw Error('Player 1 not found. This is an error!')
|
|
|
- }
|
|
|
- payerList[id1] = { ...match, Bezahlt: foundPlayer.Bezahlt }
|
|
|
- }
|
|
|
-
|
|
|
- const id2 = `${match.Konkurrenz} ${match.Spieler2}`
|
|
|
- if (!!match.Spieler2 & !payerList.hasOwnProperty(id2)) {
|
|
|
- let foundPlayer = this.state.player.players.find((player) =>
|
|
|
- (player.name() === match.Spieler2) & (player.Konkurrenz === match.Konkurrenz)
|
|
|
- )
|
|
|
- if (!foundPlayer) {
|
|
|
- console.log('Debug payerlist:', foundPlayer, match)
|
|
|
- throw Error('Player 2 not found. This is an error!')
|
|
|
- }
|
|
|
- payerList[id2] = { ...match, Bezahlt: foundPlayer.Bezahlt }
|
|
|
- }
|
|
|
- })
|
|
|
- console.log('Generated payerList', payerList)
|
|
|
-
|
|
|
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.date.value
|
|
|
+ String(this.state.match.dates[key]) === this.matchDate.value
|
|
|
)
|
|
|
|
|
|
placeArray.forEach((place) => {
|
|
@@ -236,37 +268,27 @@ class App extends React.Component {
|
|
|
|
|
|
let payListPerPlace = []
|
|
|
this.state.match.filtered.forEach((match) => {
|
|
|
- if (!!match.Spieler1 & (match.Ort === place | FILTER_OFF === place)) {
|
|
|
- const id1 = `${match.Konkurrenz} ${match.Spieler1}`
|
|
|
- const player = payerList[id1]
|
|
|
- let paid = null
|
|
|
- if (player.Datum < this.date.value) {
|
|
|
- paid = player.DatumString
|
|
|
- }
|
|
|
- if (player.Bezahlt) {
|
|
|
- paid = 'OK'
|
|
|
- }
|
|
|
- payListPerPlace.push([ paid, match.Konkurrenz, match.ZeitString, match.Spieler1 ])
|
|
|
- }
|
|
|
- if (!!match.Spieler2 & (match.Ort === place | FILTER_OFF === place)) {
|
|
|
- const id2 = `${match.Konkurrenz} ${match.Spieler2}`
|
|
|
- const player = payerList[id2]
|
|
|
- let paid = null
|
|
|
- if (player.Datum < this.date.value) {
|
|
|
- paid = player.Datum
|
|
|
- }
|
|
|
- if (player.Bezahlt) {
|
|
|
- paid = 'OK'
|
|
|
+ [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'
|
|
|
+ }
|
|
|
+ payListPerPlace.push([ paid, match.Konkurrenz, time2s(match.Datum), matchPlayer ])
|
|
|
}
|
|
|
- payListPerPlace.push([ paid, match.Konkurrenz, match.ZeitString, match.Spieler2 ])
|
|
|
- }
|
|
|
+ })
|
|
|
+ console.log('Generated pay list per place:', payListPerPlace)
|
|
|
+ worksheets[place] = Excel.SheetFromArray(header.concat(payListPerPlace))
|
|
|
+ paylist.SheetNames.push(place)
|
|
|
+ paylist.Sheets[place] = worksheets[place]
|
|
|
})
|
|
|
- console.log('Generated pay list per place:', payListPerPlace)
|
|
|
- worksheets[place] = Excel.SheetFromArray(header.concat(payListPerPlace))
|
|
|
- paylist.SheetNames.push(place)
|
|
|
- paylist.Sheets[place] = worksheets[place]
|
|
|
})
|
|
|
-
|
|
|
Excel.saveAs(paylist, 'Zahlliste.xlsx')
|
|
|
}
|
|
|
|
|
@@ -276,38 +298,48 @@ class App extends React.Component {
|
|
|
|
|
|
const places = this.state.match.places
|
|
|
const dates = this.state.match.dates
|
|
|
- const categories = this.state.match.categories
|
|
|
-
|
|
|
+ const matchCategories = this.state.match.categories
|
|
|
+ const playerCategories = this.state.player.categories
|
|
|
+
|
|
|
return (
|
|
|
- <div className='App'>
|
|
|
+ <div className='container'>
|
|
|
<form>
|
|
|
<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} />
|
|
|
<label htmlFor='Date'>Datum</label>
|
|
|
- <select id='Date' ref={(input) => { this.date = input }} onChange={this.filterData}>
|
|
|
+ <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.place = input }} onChange={this.filterData}>
|
|
|
+ <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='Category'>Konkurrenz</label>
|
|
|
- <select id='Category' ref={(input) => { this.category = input }} onChange={this.filterData}>
|
|
|
+ <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>
|
|
|
- {categories.map((category, key) => (
|
|
|
+ {playerCategories.map((category, key) => (
|
|
|
<option key={key}>{category}</option>
|
|
|
))}
|
|
|
</select>
|
|
|
- <label htmlFor='Unpaid'>Nur unbezahlte</label>
|
|
|
- <input type='checkbox' id='Unpaid' ref={(input) => { this.unpaid = input }} onChange={this.filterDate} />
|
|
|
<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>
|
|
|
</form>
|