Main.js 16 KB

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