App.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. import React from 'react' // React to manage the GUI
  2. import Player from './player' // Everything that has to do with players
  3. import Match from './match' // Everything that has to do with matches
  4. import Excel from './excel' // Helper files to create Excel files
  5. import { date2s, time2s } from './helpers'
  6. import 'bootstrap/dist/css/bootstrap.css'
  7. import EmailList from './lists/components/EmailList'
  8. import PhoneList from './lists/components/PhoneList'
  9. /**
  10. * General Application Design
  11. *
  12. * 4 Components:
  13. * - PlayerList (representing the PlayerList Excel file)
  14. * - Calendar (representing the Calendar Excel file)
  15. * - MatchList (representing the Spielliste Excel file)
  16. * - PaymentList (representing the Zahlliste Excel file)
  17. *
  18. * PlayerList
  19. * - Shows the relevant information from the file
  20. * - Shows calculated information from combination with Calendar
  21. * - Points out potential problems
  22. * - Allows access to player information
  23. *
  24. * Calendar
  25. * - Shows the relevant information from the file
  26. * - Shows calculated information from combination with PlayerList
  27. * - Points out potential problems
  28. * - Allows access to match information
  29. *
  30. * MatchList
  31. * - Shows the calculated match lists
  32. * - Points out problems
  33. *
  34. * PaymentList
  35. * - Shows the calculated payment lists
  36. * - Points out problems
  37. *
  38. *
  39. */
  40. const FILTER_OFF = 'Alle'
  41. /** Main application */
  42. class App extends React.Component {
  43. /**
  44. * Constructor
  45. * Defines the state and binds all 'this' in all functions to the object.
  46. */
  47. constructor () {
  48. super()
  49. // Define the state
  50. this.state = {
  51. player: Player.state,
  52. match: Match.state,
  53. worksheets: {
  54. 'PlayerList': null,
  55. 'Calendar': null
  56. }
  57. }
  58. // Bind 'this' to functions
  59. this.handlePlayerList = this.handlePlayerList.bind(this)
  60. this.handleCalendar = this.handleCalendar.bind(this)
  61. this.generatePlayerList = this.generatePlayerList.bind(this)
  62. this.generateCalendar = this.generateCalendar.bind(this)
  63. this.filterMatches = this.filterMatches.bind(this)
  64. this.filterPlayers = this.filterPlayers.bind(this)
  65. this.generateSchedule = this.generateSchedule.bind(this)
  66. this.generatePayTable = this.generatePayTable.bind(this)
  67. this.generatePhoneList = this.generatePhoneList.bind(this)
  68. }
  69. calculateMatchFilters () {
  70. const dates = {}
  71. const places = []
  72. const categories = []
  73. this.state.match.matches.forEach((item) => {
  74. const dateString = date2s(item.Datum)
  75. if (!!item.Datum & !dates.hasOwnProperty(dateString)) {
  76. dates[dateString] = item.Datum
  77. }
  78. if (!!item.Ort & !places.includes(item.Ort)) {
  79. places.push(item.Ort)
  80. }
  81. if (!!item.Konkurrenz & !categories.includes(item.Konkurrenz)) {
  82. categories.push(item.Konkurrenz)
  83. }
  84. })
  85. const match = { ...this.state.match, dates, places, categories }
  86. this.setState({ match })
  87. }
  88. calculatePlayerFilters () {
  89. const categories = []
  90. this.state.player.players.forEach((item) => {
  91. if (!!item.Konkurrenz & !categories.includes(item.Konkurrenz)) {
  92. categories.push(item.Ort)
  93. }
  94. })
  95. const player = { ...this.state.player, categories }
  96. this.setState({ player })
  97. }
  98. handlePlayerList (event) {
  99. const file = this.playerList.files[0]
  100. Excel.readWorkbook(file, this.generatePlayerList)
  101. }
  102. handleCalendar (event) {
  103. const file = this.calendar.files[0]
  104. Excel.readWorkbook(file, this.generateCalendar)
  105. }
  106. generatePlayerList (worksheet) {
  107. console.log('About to read the player list.')
  108. const worksheets = { ...this.state.worksheets }
  109. worksheets['PlayerList'] = worksheet
  110. this.setState({ worksheets })
  111. if (worksheet[4].length !== 32 & worksheet[3][0] !== 'Konkurrenz' & worksheet[3][31] !== 'bezahlt') {
  112. throw Error('Wrong file structure.')
  113. }
  114. const players = worksheet.slice(4, worksheet.length).map((playerData) => new Player.Player(playerData))
  115. const player = { ...this.state.player }
  116. player.players = players
  117. this.setState({ player })
  118. this.calculatePayDate()
  119. this.filterPlayers()
  120. console.log('State after generating player list:', this.state)
  121. }
  122. generateCalendar (worksheet) {
  123. console.log('About to read the calendar.')
  124. const worksheets = { ...this.state.worksheets }
  125. worksheets['Calendar'] = worksheet
  126. this.setState({ worksheets })
  127. if (worksheet[2].length < 8 | worksheet[2].length > 9) {
  128. throw Error('Wrong file structure.')
  129. }
  130. const matches = worksheet.slice(2, worksheet.length).map((matchData) => new Match.MatchClass(matchData))
  131. const match = { ...this.state.match }
  132. match.matches = matches
  133. this.setState({ match })
  134. this.calculateMatchFilters()
  135. this.calculatePayDate()
  136. this.filterMatches()
  137. console.log('State after generating calendar:', this.state)
  138. }
  139. filterMatches () {
  140. const filters = {
  141. date: this.matchDate.value !== FILTER_OFF ? this.matchDate.value : null,
  142. place: this.matchPlace.value !== FILTER_OFF ? this.matchPlace.value : null,
  143. category: this.matchCategory.value !== FILTER_OFF ? this.matchCategory.value : null
  144. }
  145. console.log('New filter settings:', filters)
  146. const match = { ...this.state.match }
  147. match.filtered = match.matches.filter((match) => {
  148. const matchDate = new Date(match.Datum)
  149. matchDate.setHours(0, 0, 0, 0)
  150. const filtDate = new Date(filters.date)
  151. filtDate.setHours(0, 0, 0, 0)
  152. return (!filters.date | matchDate.getTime() === filtDate.getTime()) &
  153. (!filters.place | filters.place === match.Ort) &
  154. (!filters.category | filters.category === match.Konkurrenz)
  155. })
  156. this.setState({ match })
  157. const player = { ...this.state.player, filters }
  158. player.filtered = player.players
  159. this.setState({ player })
  160. }
  161. filterPlayers () {
  162. const filters = {
  163. junior: this.playerJunior.checked,
  164. paid: this.playerPaid.checked,
  165. category: this.playerCategory.value !== FILTER_OFF ? this.playerCategory.value : null
  166. }
  167. console.log('New filter settings:', filters)
  168. const player = { ...this.state.player, filters }
  169. player.filtered = player.players
  170. this.setState({ player })
  171. }
  172. generateSchedule (event) {
  173. event.preventDefault()
  174. const matchlist = new Excel.Workbook()
  175. matchlist.SheetNames = []
  176. matchlist.Sheets = {}
  177. const worksheets = {}
  178. let placeArray = this.state.match.places
  179. if (placeArray.length > 1) {
  180. placeArray = placeArray.concat([FILTER_OFF])
  181. }
  182. const date = Object.keys(this.state.match.dates).find((key) =>
  183. String(this.state.match.dates[key]) === this.matchDate.value
  184. )
  185. placeArray.forEach(place => {
  186. let header = [
  187. ['Stadtzürcher Tennismeisterschaft'],
  188. [`Spielplan für den ${date} (${place})`],
  189. [],
  190. ['Ort', 'Zeit', 'Kategorie', 'Spieler 1', '', 'Spieler 2', '', '1. Satz', '2. Satz', '3. Satz', 'WO Grund']
  191. ]
  192. let matchListPerPlace = this.state.match.filtered.filter((match) => (match.Ort === place | place === FILTER_OFF)).map((match) =>
  193. [match.Ort, time2s(match.Datum), match.Konkurrenz, match.Spieler1, match.Spieler1Klassierung, match.Spieler2, match.Spieler2Klassierung]
  194. )
  195. console.log('Generated match list per place:', matchListPerPlace)
  196. worksheets[place] = Excel.SheetFromArray(header.concat(matchListPerPlace))
  197. matchlist.SheetNames.push(place)
  198. matchlist.Sheets[place] = worksheets[place]
  199. })
  200. Excel.saveAs(matchlist, 'Spielliste.xlsx')
  201. }
  202. generatePhoneList (event) {
  203. event.preventDefault()
  204. const phoneMail = new Excel.Workbook()
  205. phoneMail.SheetNames = []
  206. phoneMail.Sheets = {}
  207. const dataList = [
  208. ['Vorname', 'Nachname', 'Anrede', 'Geschlecht', 'Handy', 'E-Mail']
  209. ]
  210. const phonePot = []
  211. const players = this.state.player.filtered
  212. players.forEach(player => {
  213. if (!player.phone.match(/^FEHLER/) && !phonePot.includes(player.phone)) {
  214. phonePot.push(player.phone)
  215. dataList.push([
  216. player.Vorname,
  217. player.Name,
  218. 2,
  219. player.geschlecht === 'w' ? 2 : 1,
  220. player.phone
  221. ])
  222. }
  223. })
  224. phoneMail.Sheets['Sheet1'] = Excel.SheetFromArray(dataList)
  225. phoneMail.SheetNames.push('Sheet1')
  226. Excel.saveAs(phoneMail, 'Telefon.xlsx')
  227. }
  228. calculatePayDate () {
  229. if ((this.state.player.players.length === 0) | (this.state.match.matches.length === 0)) {
  230. return
  231. }
  232. this.state.match.matches.forEach((match) => {
  233. [match.Spieler1, match.Spieler2].forEach((matchPlayer) => {
  234. if (matchPlayer) {
  235. let foundPlayer = this.state.player.players.find((player) =>
  236. (player.name === matchPlayer) & (player.Konkurrenz === match.Konkurrenz)
  237. )
  238. if (!foundPlayer) {
  239. console.log('Debug payerlist:', foundPlayer, match)
  240. throw Error('Match player not found in player list. This is an error!')
  241. }
  242. if (!foundPlayer.BezahltAm) {
  243. foundPlayer.BezahltAm = match.Datum
  244. }
  245. }
  246. })
  247. })
  248. }
  249. generatePayTable (event) {
  250. event.preventDefault()
  251. const paylist = new Excel.Workbook()
  252. paylist.SheetNames = []
  253. paylist.Sheets = {}
  254. const worksheets = {}
  255. let placeArray = this.state.match.places
  256. /* if (placeArray.length > 1) {
  257. placeArray = placeArray.concat([FILTER_OFF])
  258. } */
  259. const date = Object.keys(this.state.match.dates).find((key) =>
  260. String(this.state.match.dates[key]) === this.matchDate.value
  261. )
  262. placeArray.forEach((place) => {
  263. let header = [
  264. ['Stadtzürcher Tennismeisterschaft'],
  265. [`Nenngelder für ${date}`],
  266. [],
  267. [`${place}`, null, '50.- oder 30.- (Junioren Jg. 1999 oder jünger)'],
  268. [],
  269. ['bereits bez.', 'Kat.', 'Zeit', 'Name', 'Betrag bez.', 'Quittung abgeben']
  270. ]
  271. // Per place
  272. let payListPerPlace = []
  273. this.state.match.filtered.forEach((match) => {
  274. [match.Spieler1, match.Spieler2].forEach((matchPlayer) => {
  275. if (!!matchPlayer & (match.Ort === place | FILTER_OFF === place)) {
  276. const player = this.state.player.players.find((player) =>
  277. (player.Konkurrenz === match.Konkurrenz) & (player.name === matchPlayer)
  278. )
  279. let paid = null
  280. if (player.BezahltAm < this.matchDate.value) {
  281. paid = date2s(player.BezahltAm)
  282. }
  283. if (player.Bezahlt) {
  284. paid = 'OK'
  285. }
  286. let price
  287. if (player.isDoubles) {
  288. price = (player.isJunior ? 15 : 25) + (player.isJuniorDP ? 15 : 25)
  289. } else {
  290. price = player.isJunior ? 30 : 50
  291. }
  292. payListPerPlace.push([ paid, match.Konkurrenz, time2s(match.Datum), `${matchPlayer} (${price}.-)` ])
  293. }
  294. })
  295. })
  296. let footer = [
  297. [],
  298. ['Datum'],
  299. ['Turnierleiter', null, null, 'Kassierer'],
  300. ['Betrag von Spielern erhalten', null, null, 'Betrag von Turnierleiter erhalten']
  301. ]
  302. console.log(`Generated pay list per place ${place}:`, payListPerPlace)
  303. worksheets[place] = Excel.SheetFromArray(header.concat(payListPerPlace, footer))
  304. paylist.SheetNames.push(place)
  305. paylist.Sheets[place] = worksheets[place]
  306. })
  307. let payListPerPlace = []
  308. this.state.match.filtered.forEach((match) => {
  309. [match.Spieler1, match.Spieler2].forEach((matchPlayer) => {
  310. if (matchPlayer) {
  311. const player = this.state.player.players.find((player) =>
  312. (player.Konkurrenz === match.Konkurrenz) & (player.name === matchPlayer)
  313. )
  314. let price
  315. if (player.isDoubles) {
  316. price = (player.isJunior ? 15 : 25) + (player.isJuniorDP ? 15 : 25)
  317. } else {
  318. price = player.isJunior ? 30 : 50
  319. }
  320. payListPerPlace.push([ match.Ort, match.Konkurrenz, `${matchPlayer} (${price}.-)` ])
  321. }
  322. })
  323. })
  324. console.log(`Generated pay list for "Alle":`, payListPerPlace)
  325. worksheets[FILTER_OFF] = Excel.SheetFromArray(payListPerPlace)
  326. paylist.SheetNames.push(FILTER_OFF)
  327. paylist.Sheets[FILTER_OFF] = worksheets[FILTER_OFF]
  328. Excel.saveAs(paylist, 'Zahlliste.xlsx')
  329. }
  330. render () {
  331. const PlayerList = Player.components.PlayerList
  332. const MatchList = Match.components.MatchList
  333. const places = this.state.match.places
  334. const dates = this.state.match.dates
  335. const matchCategories = this.state.match.categories
  336. const playerCategories = this.state.player.categories
  337. return (
  338. <div className='container'>
  339. <form>
  340. <label htmlFor='PlayerList'>Swisstennis PlayerList.xls</label>
  341. <input type='file' id='PlayerList' ref={(input) => { this.playerList = input }} accept='.xls' placeholder='PlayerList File' onChange={this.handlePlayerList} />
  342. <label htmlFor='Calendar'>Swisstennis Calendar.xls</label>
  343. <input type='file' id='Calendar' ref={(input) => { this.calendar = input }} accept='.xls' placeholder='Calendar File' onChange={this.handleCalendar} />
  344. <label htmlFor='Date'>Datum</label>
  345. <select id='Date' ref={(input) => { this.matchDate = input }} onChange={this.filterMatches}>
  346. <option>{FILTER_OFF}</option>
  347. {Object.keys(dates).map((key) => (
  348. <option key={key} value={dates[key]}>{key}</option>
  349. ))}
  350. </select>
  351. <label htmlFor='Place'>Ort</label>
  352. <select id='Place' ref={(input) => { this.matchPlace = input }} onChange={this.filterMatches}>
  353. <option>{FILTER_OFF}</option>
  354. {places.map((place, key) => (
  355. <option key={key}>{place}</option>
  356. ))}
  357. </select>
  358. <label htmlFor='MatchCategory'>Match Konkurrenz</label>
  359. <select id='MatchCategory' ref={(input) => { this.matchCategory = input }} onChange={this.filterMatches}>
  360. <option>{FILTER_OFF}</option>
  361. {matchCategories.map((category, key) => (
  362. <option key={key}>{category}</option>
  363. ))}
  364. </select>
  365. <label htmlFor='Junior'>Junior</label>
  366. <input id='Junior' ref={(input) => { this.playerJunior = input }} type='checkbox' onChange={this.filterPlayers} />
  367. <label htmlFor='Paid'>Bezahlt</label>
  368. <input id='Paid' ref={(input) => { this.playerPaid = input }} type='checkbox' onChange={this.filterPlayers} />
  369. <label htmlFor='PlayerCategory'>Spieler Konkurrenz</label>
  370. <select id='PlayerCategory' ref={(input) => { this.playerCategory = input }} onChange={this.filterPlayers}>
  371. <option>{FILTER_OFF}</option>
  372. {playerCategories.map((category, key) => (
  373. <option key={key}>{category}</option>
  374. ))}
  375. </select>
  376. <button onClick={this.generateSchedule} disabled={!this.state.match.filtered.length}>Spielliste generieren</button>
  377. <button onClick={this.generatePayTable} disabled={(!this.state.match.filtered.length | !this.state.player.filtered.length)}>Zahlliste generieren</button>
  378. <button onClick={this.generatePhoneList} disabled={!this.state.player.players.length}>Telefonliste generieren</button>
  379. </form>
  380. <MatchList match={this.state.match} />
  381. <PhoneList filtered={this.state.match.filtered} players={this.state.player.players} />
  382. <EmailList filtered={this.state.match.filtered} players={this.state.player.players} />
  383. <PlayerList player={this.state.player} />
  384. </div>
  385. )
  386. }
  387. }
  388. export default App