Major update

This commit is contained in:
2023-06-01 08:52:05 -05:00
parent d7c0f373f4
commit df84287eb2
264 changed files with 8614 additions and 964 deletions

292
app/package-lock.json generated
View File

@@ -11,6 +11,7 @@
"dependencies": {
"@apollo/client": "^3.7.3",
"@fluentui/react": "^8.104.2",
"@microsoft/signalr": "^7.0.3",
"classnames": "^2.3.2",
"graphql": "^16.6.0",
"react": "^18.2.0",
@@ -2562,6 +2563,38 @@
"resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.10.295.tgz",
"integrity": "sha512-W+IzEBw8a6LOOfRJM02dTT7BDZijxm+Z7lhtOAz1+y9vQm1Kdz9jlAO+qCEKsfxtUOmKilW8DIRqFw2aUgKeGg=="
},
"node_modules/@microsoft/signalr": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-7.0.3.tgz",
"integrity": "sha512-gQbrCQxvC/djy+/GBHFpNnAqubGLe0Hrp3ZO8x8NRGAU8jhj3QcHuxhq6N5Z3A0zI6IE/fMfX2xSheQxOwA3ZA==",
"dependencies": {
"abort-controller": "^3.0.0",
"eventsource": "^2.0.2",
"fetch-cookie": "^2.0.3",
"node-fetch": "^2.6.7",
"ws": "^7.4.5"
}
},
"node_modules/@microsoft/signalr/node_modules/ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -3517,6 +3550,17 @@
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -6277,7 +6321,6 @@
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"dev": true,
"optional": true,
"dependencies": {
"iconv-lite": "^0.6.2"
@@ -6860,6 +6903,14 @@
"through": "~2.3.1"
}
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/eventemitter2": {
"version": "6.4.7",
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz",
@@ -6881,6 +6932,14 @@
"node": ">=0.4.x"
}
},
"node_modules/eventsource": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz",
"integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/evp_bytestokey": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
@@ -7150,6 +7209,45 @@
"pend": "~1.2.0"
}
},
"node_modules/fetch-cookie": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.1.0.tgz",
"integrity": "sha512-39+cZRbWfbibmj22R2Jy6dmTbAWC+oqun1f1FzQaNurkPDUP4C38jpeZbiXCR88RKRVDp8UcDrbFXkNhN+NjYg==",
"dependencies": {
"set-cookie-parser": "^2.4.8",
"tough-cookie": "^4.0.0"
}
},
"node_modules/fetch-cookie/node_modules/punycode": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
"engines": {
"node": ">=6"
}
},
"node_modules/fetch-cookie/node_modules/tough-cookie": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
"integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/fetch-cookie/node_modules/universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@@ -8190,7 +8288,6 @@
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"optional": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -10324,6 +10421,25 @@
"tslib": "^2.0.3"
}
},
"node_modules/node-fetch": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
"integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
@@ -11660,8 +11776,7 @@
"node_modules/psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
"dev": true
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
},
"node_modules/public-encrypt": {
"version": "4.0.3",
@@ -11733,6 +11848,11 @@
"node": ">=0.4.x"
}
},
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -12225,8 +12345,7 @@
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"dev": true
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
"node_modules/resolve": {
"version": "1.22.1",
@@ -12393,7 +12512,7 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
"devOptional": true
},
"node_modules/sass-graph": {
"version": "4.0.1",
@@ -12793,6 +12912,11 @@
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"dev": true
},
"node_modules/set-cookie-parser": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz",
"integrity": "sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ=="
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -13758,6 +13882,11 @@
"node": ">=6"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/trim-newlines": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
@@ -14252,6 +14381,15 @@
"querystring": "0.2.0"
}
},
"node_modules/url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"dependencies": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"node_modules/url/node_modules/punycode": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
@@ -14581,6 +14719,11 @@
"defaults": "^1.0.3"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/webpack": {
"version": "5.75.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
@@ -14832,6 +14975,15 @@
"node": ">=0.8.0"
}
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -16908,6 +17060,26 @@
"resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.10.295.tgz",
"integrity": "sha512-W+IzEBw8a6LOOfRJM02dTT7BDZijxm+Z7lhtOAz1+y9vQm1Kdz9jlAO+qCEKsfxtUOmKilW8DIRqFw2aUgKeGg=="
},
"@microsoft/signalr": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-7.0.3.tgz",
"integrity": "sha512-gQbrCQxvC/djy+/GBHFpNnAqubGLe0Hrp3ZO8x8NRGAU8jhj3QcHuxhq6N5Z3A0zI6IE/fMfX2xSheQxOwA3ZA==",
"requires": {
"abort-controller": "^3.0.0",
"eventsource": "^2.0.2",
"fetch-cookie": "^2.0.3",
"node-fetch": "^2.6.7",
"ws": "^7.4.5"
},
"dependencies": {
"ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"requires": {}
}
}
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -17684,6 +17856,14 @@
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true
},
"abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"requires": {
"event-target-shim": "^5.0.0"
}
},
"accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -19859,7 +20039,6 @@
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"dev": true,
"optional": true,
"requires": {
"iconv-lite": "^0.6.2"
@@ -20276,6 +20455,11 @@
"through": "~2.3.1"
}
},
"event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
},
"eventemitter2": {
"version": "6.4.7",
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz",
@@ -20294,6 +20478,11 @@
"integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==",
"dev": true
},
"eventsource": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz",
"integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA=="
},
"evp_bytestokey": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
@@ -20523,6 +20712,38 @@
"pend": "~1.2.0"
}
},
"fetch-cookie": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.1.0.tgz",
"integrity": "sha512-39+cZRbWfbibmj22R2Jy6dmTbAWC+oqun1f1FzQaNurkPDUP4C38jpeZbiXCR88RKRVDp8UcDrbFXkNhN+NjYg==",
"requires": {
"set-cookie-parser": "^2.4.8",
"tough-cookie": "^4.0.0"
},
"dependencies": {
"punycode": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA=="
},
"tough-cookie": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
"integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
"requires": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
}
},
"universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="
}
}
},
"figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@@ -21309,7 +21530,6 @@
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"optional": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -22894,6 +23114,14 @@
"tslib": "^2.0.3"
}
},
"node-fetch": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
"integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
"requires": {
"whatwg-url": "^5.0.0"
}
},
"node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
@@ -23916,8 +24144,7 @@
"psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
"dev": true
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
},
"public-encrypt": {
"version": "4.0.3",
@@ -23978,6 +24205,11 @@
"integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==",
"dev": true
},
"querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
},
"queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -24367,8 +24599,7 @@
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"dev": true
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
"resolve": {
"version": "1.22.1",
@@ -24476,7 +24707,7 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
"devOptional": true
},
"sass-graph": {
"version": "4.0.1",
@@ -24797,6 +25028,11 @@
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"dev": true
},
"set-cookie-parser": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz",
"integrity": "sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ=="
},
"setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -25550,6 +25786,11 @@
}
}
},
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"trim-newlines": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
@@ -25909,6 +26150,15 @@
}
}
},
"url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"requires": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"util": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
@@ -26202,6 +26452,11 @@
"defaults": "^1.0.3"
}
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"webpack": {
"version": "5.75.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
@@ -26365,6 +26620,15 @@
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -64,6 +64,7 @@
"dependencies": {
"@apollo/client": "^3.7.3",
"@fluentui/react": "^8.104.2",
"@microsoft/signalr": "^7.0.3",
"classnames": "^2.3.2",
"graphql": "^16.6.0",
"react": "^18.2.0",

View File

@@ -1,3 +1,3 @@
.content {
padding: 30px;
}
padding: 30px;
}

View File

@@ -1,14 +1,19 @@
import React from 'react';
import styles from './App.module.scss'
import styles from './App.module.scss';
import { Routes, Route } from 'react-router-dom';
import { initializeIcons } from '@fluentui/font-icons-mdl2';
import { Grid, Sidebar } from 'semantic-ui-react';
import Header from '../header/Header';
import Footer from '../footer/Footer';
import Navigation from '../navigation/Navigation';
import Divisions from '../divisions/Divisions';
import Leagues from '../leagues/Leagues';
import Seasons from '../seasons/Seasons';
import Sports from '../sports/Sports';
import Teams from '../teams/Teams';
import Venues from '../venues/Venues';
import { useSidebarContext } from '../../hooks/sidebarContext/UseSidebarContext';
import { usePagination } from '../..//hooks/usePaginiation/UsePagination';
initializeIcons();
@@ -18,7 +23,7 @@ const App: React.FC<unknown> = () => {
return (
<Sidebar.Pushable>
<Sidebar.Pusher dimmed={sidebarContext.state.sidebar?.visible}>
<Header />
<Header />
<Grid columns={16}>
<Grid.Row>
<Grid.Column width={3}>
@@ -26,6 +31,18 @@ const App: React.FC<unknown> = () => {
</Grid.Column>
<Grid.Column width={12}>
<div className={styles.content}>
<Routes>
<Route path='divisions' element={<Divisions />} />
</Routes>
<Routes>
<Route path='leagues' element={<Leagues />} />
</Routes>
<Routes>
<Route path='seasons' element={<Seasons />} />
</Routes>
<Routes>
<Route path='sports' element={<Sports />} />
</Routes>
<Routes>
<Route path='teams' element={<Teams />} />
</Routes>
@@ -36,7 +53,7 @@ const App: React.FC<unknown> = () => {
</Grid.Column>
</Grid.Row>
</Grid>
<Footer />
{/* <Footer /> */}
</Sidebar.Pusher>
</Sidebar.Pushable>
);

View File

@@ -0,0 +1,171 @@
import React, { SyntheticEvent, useEffect, useReducer } from 'react';
import { useQuery } from '@apollo/client';
import { MessageBar, MessageBarType } from '@fluentui/react/lib/MessageBar';
import { initialState, reducer } from './reducer';
import { useAppContext } from '../../hooks/appContext/UseAppContext';
import { useSidebarContext } from '../../hooks/sidebarContext/UseSidebarContext';
import { GET_DIVISIONS } from '../../constants/graphQL';
import { INotification } from '../../models/INotification';
import {
Button,
Grid,
Header,
Icon,
Loader,
Pagination,
Sidebar,
Table
} from 'semantic-ui-react';
import { usePagination } from '../../hooks/usePaginiation/UsePagination';
const Venues: React.FC<unknown> = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const appContext = useAppContext();
const sidebarContext = useSidebarContext();
const currentPageNumber = state.currentPageNumber;
const pageSize = state.pageSize;
const { data, loading, error, refetch } = useQuery(GET_DIVISIONS, {
variables: {
pageNumber: currentPageNumber,
pageSize: pageSize
}
});
const { pagination } = usePagination();
const columns: string[] = ['Name', ''];
const onDismissMessageBar = () => {
const notification: INotification = {
...appContext.state.notification,
isVisible: false
};
appContext.dispatch({ type: 'SET_NOTIFICATION', payload: notification });
};
const onPageChange = (event: SyntheticEvent, data: any) => {
const newCurrentPage: number = data.activePage;
dispatch({ type: 'SET_CURRENT_PAGE_NUMBER', payload: newCurrentPage });
};
const onDetails = (id: number) => {
// sidebarContext.dispatch({
// type: 'SET_SIDEBAR',
// payload: {
// sidebar: {
// visible: true,
// animation: 'overlay',
// width: 'very wide',
// direction: 'right'
// },
// content: <VenueDetails venueId={id} />
// }
// });
};
useEffect(() => {
if (data) {
dispatch({
type: 'SET_ITEMS',
payload: data.divisions
});
}
}, [data]);
useEffect(() => {
// console.log(error);
const notification: INotification = {
messageBarType: MessageBarType.error,
message: error?.message,
isMultiline: false,
isVisible: false
};
appContext.dispatch({ type: 'SET_NOTIFICATION', payload: notification });
}, [error]);
useEffect(() => {
refetch({
pageNumber: currentPageNumber,
pageSize: pageSize
});
}, [currentPageNumber]);
return (
<Grid columns={1}>
<Grid.Row>
<Header as='h1'>Divisions</Header>
</Grid.Row>
{appContext.state.notification?.isVisible && (
<Grid.Row>
<Grid.Column>
<MessageBar
messageBarType={appContext.state.notification?.messageBarType}
isMultiline={appContext.state.notification?.isMultiline}
onDismiss={onDismissMessageBar}
>
{appContext.state.notification?.message}
</MessageBar>
</Grid.Column>
</Grid.Row>
)}
<Grid.Row>
<Grid.Column>
{loading && (
<Loader size='massive' active={loading}>
Loading...
</Loader>
)}
{!loading && (
<Table selectable>
<Table.Header>
<Table.Row>
{columns.map((column: string, index: number) => {
return (
<Table.HeaderCell key={index}>{column}</Table.HeaderCell>
);
})}
</Table.Row>
</Table.Header>
<Table.Body>
{state.items?.map((item) => {
return (
<Table.Row key={item.id}>
<Table.Cell>{item.name}</Table.Cell>
<Table.Cell textAlign='right'>
<Button basic icon onClick={() => onDetails(item.id)}>
<Icon name='info circle' />
</Button>
</Table.Cell>
</Table.Row>
);
})}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan='2'>
<Grid columns={1}>
<Grid.Row>
<Grid.Column textAlign='right'>
<Pagination
totalPages={pagination ? pagination.TotalPages : ''}
activePage={state.currentPageNumber}
onPageChange={onPageChange}
size='mini'
/>
</Grid.Column>
</Grid.Row>
</Grid>
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
)}
</Grid.Column>
</Grid.Row>
</Grid>
);
};
export default Venues;

View File

@@ -0,0 +1,8 @@
import { IDivision } from '../../models/IDivision';
export interface IDivisionsState {
items: IDivision[];
currentPageNumber: number;
pageSize: number;
isPanelOpen: boolean;
}

View File

@@ -0,0 +1,50 @@
import { IDivisionsState } from './IDivisionsState';
import { IDivision } from '../../models/IDivision';
export type Action =
| { type: 'SET_ITEMS'; payload: IDivision[] }
| { type: 'SET_CURRENT_PAGE_NUMBER'; payload: number }
| { type: 'SET_PAGE_SIZE'; payload: number }
| { type: 'SET_IS_PANEL_OPEN'; payload: boolean };
export const initialState: IDivisionsState = {
items: [],
currentPageNumber: 1,
pageSize: 10,
isPanelOpen: false
};
export const reducer = (
state: IDivisionsState,
action: Action
): IDivisionsState => {
switch (action.type) {
case 'SET_ITEMS': {
return {
...state,
items: action.payload
};
}
case 'SET_CURRENT_PAGE_NUMBER': {
return {
...state,
currentPageNumber: action.payload
};
}
case 'SET_PAGE_SIZE': {
return {
...state,
pageSize: action.payload
};
}
case 'SET_IS_PANEL_OPEN': {
return {
...state,
isPanelOpen: action.payload
};
}
default: {
return state;
}
}
};

View File

@@ -1,4 +1,4 @@
.siteFooter {
background-color: #e0e1e2;
height: 150px;
}
background-color: #e0e1e2;
height: 150px;
}

View File

@@ -3,20 +3,18 @@ import styles from './Footer.module.scss';
import { Grid } from 'semantic-ui-react';
const Footer: React.FC<unknown> = () => {
return (
<div className={styles.siteFooter}>
<Grid columns={16}>
<Grid.Row>
<Grid.Column width={8}>
Hello!
</Grid.Column>
<Grid.Column width={8} textAlign='right'>
Goodbye!
</Grid.Column>
</Grid.Row>
</Grid>
</div>
)
}
return (
<div className={styles.siteFooter}>
<Grid columns={16}>
<Grid.Row>
<Grid.Column width={8}>Hello!</Grid.Column>
<Grid.Column width={8} textAlign='right'>
Goodbye!
</Grid.Column>
</Grid.Row>
</Grid>
</div>
);
};
export default Footer;
export default Footer;

View File

@@ -1,4 +1,4 @@
.siteHeader {
background-color: #e0e1e2;
height: 100px;
}
background-color: #e0e1e2;
height: 100px;
}

View File

@@ -3,20 +3,20 @@ import styles from './Header.module.scss';
import { Grid, Icon } from 'semantic-ui-react';
const Header: React.FC<unknown> = () => {
return (
<div className={styles.siteHeader}>
<Grid columns={16}>
<Grid.Row>
<Grid.Column width={8}>
<Icon name='baseball ball' size='huge' />
</Grid.Column>
<Grid.Column width={8} textAlign='right'>
Goodbye!
</Grid.Column>
</Grid.Row>
</Grid>
</div>
)
}
return (
<div className={styles.siteHeader}>
<Grid columns={16}>
<Grid.Row>
<Grid.Column width={8}>
<Icon name='baseball ball' size='huge' />
</Grid.Column>
<Grid.Column width={8} textAlign='right'>
Goodbye!
</Grid.Column>
</Grid.Row>
</Grid>
</div>
);
};
export default Header;
export default Header;

View File

@@ -0,0 +1,8 @@
import { ILeague } from '../../models/ILeague';
export interface ILeaguesState {
items: ILeague[];
currentPageNumber: number;
pageSize: number;
isPanelOpen: boolean;
}

View File

@@ -0,0 +1,173 @@
import React, { SyntheticEvent, useEffect, useReducer } from 'react';
import { useQuery } from '@apollo/client';
import { MessageBar, MessageBarType } from '@fluentui/react/lib/MessageBar';
import { initialState, reducer } from './reducer';
import { useAppContext } from '../../hooks/appContext/UseAppContext';
import { useSidebarContext } from '../../hooks/sidebarContext/UseSidebarContext';
import { GET_LEAGUES } from '../../constants/graphQL';
import { INotification } from '../../models/INotification';
import {
Button,
Grid,
Header,
Icon,
Loader,
Pagination,
Sidebar,
Table
} from 'semantic-ui-react';
// import VenueDetails from '../venueDetails/VenueDetails';
import { usePagination } from '../../hooks/usePaginiation/UsePagination';
const Leagues: React.FC<unknown> = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const appContext = useAppContext();
const sidebarContext = useSidebarContext();
const currentPageNumber = state.currentPageNumber;
const pageSize = state.pageSize;
const { data, loading, error, refetch } = useQuery(GET_LEAGUES, {
variables: {
pageNumber: currentPageNumber,
pageSize: pageSize
}
});
const { pagination } = usePagination();
const columns: string[] = ['Name', ''];
const onDismissMessageBar = () => {
const notification: INotification = {
...appContext.state.notification,
isVisible: false
};
appContext.dispatch({ type: 'SET_NOTIFICATION', payload: notification });
};
const onPageChange = (event: SyntheticEvent, data: any) => {
const newCurrentPage: number = data.activePage;
dispatch({ type: 'SET_CURRENT_PAGE_NUMBER', payload: newCurrentPage });
};
const onDetails = (id: number) => {
// sidebarContext.dispatch({
// type: 'SET_SIDEBAR',
// payload: {
// sidebar: {
// visible: true,
// animation: 'overlay',
// width: 'very wide',
// direction: 'right'
// },
// content: <VenueDetails venueId={id} />
// }
// });
};
useEffect(() => {
console.log(data)
if (data) {
dispatch({
type: 'SET_ITEMS',
payload: data.leagues
});
}
}, [data]);
useEffect(() => {
// console.log(error);
const notification: INotification = {
messageBarType: MessageBarType.error,
message: error?.message,
isMultiline: false,
isVisible: false
};
appContext.dispatch({ type: 'SET_NOTIFICATION', payload: notification });
}, [error]);
useEffect(() => {
refetch({
pageNumber: currentPageNumber,
pageSize: pageSize
});
}, [currentPageNumber]);
return (
<Grid columns={1}>
<Grid.Row>
<Header as='h1'>Leagues</Header>
</Grid.Row>
{appContext.state.notification?.isVisible && (
<Grid.Row>
<Grid.Column>
<MessageBar
messageBarType={appContext.state.notification?.messageBarType}
isMultiline={appContext.state.notification?.isMultiline}
onDismiss={onDismissMessageBar}
>
{appContext.state.notification?.message}
</MessageBar>
</Grid.Column>
</Grid.Row>
)}
<Grid.Row>
<Grid.Column>
{loading && (
<Loader size='massive' active={loading}>
Loading...
</Loader>
)}
{!loading && (
<Table selectable>
<Table.Header>
<Table.Row>
{columns.map((column: string, index: number) => {
return (
<Table.HeaderCell key={index}>{column}</Table.HeaderCell>
);
})}
</Table.Row>
</Table.Header>
<Table.Body>
{state.items.map((item) => {
return (
<Table.Row key={item.id}>
<Table.Cell>{item.name}</Table.Cell>
<Table.Cell textAlign='right'>
<Button basic icon onClick={() => onDetails(item.id)}>
<Icon name='info circle' />
</Button>
</Table.Cell>
</Table.Row>
);
})}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan='2'>
<Grid columns={1}>
<Grid.Row>
<Grid.Column textAlign='right'>
<Pagination
totalPages={pagination ? pagination.TotalPages : ''}
activePage={state.currentPageNumber}
onPageChange={onPageChange}
size='mini'
/>
</Grid.Column>
</Grid.Row>
</Grid>
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
)}
</Grid.Column>
</Grid.Row>
</Grid>
);
};
export default Leagues;

View File

@@ -0,0 +1,50 @@
import { ILeaguesState } from './ILeaguesState';
import { ILeague } from '../../models/ILeague';
export type Action =
| { type: 'SET_ITEMS'; payload: ILeague[] }
| { type: 'SET_CURRENT_PAGE_NUMBER'; payload: number }
| { type: 'SET_PAGE_SIZE'; payload: number }
| { type: 'SET_IS_PANEL_OPEN'; payload: boolean };
export const initialState: ILeaguesState = {
items: [],
currentPageNumber: 1,
pageSize: 10,
isPanelOpen: false
};
export const reducer = (
state: ILeaguesState,
action: Action
): ILeaguesState => {
switch (action.type) {
case 'SET_ITEMS': {
return {
...state,
items: action.payload
};
}
case 'SET_CURRENT_PAGE_NUMBER': {
return {
...state,
currentPageNumber: action.payload
};
}
case 'SET_PAGE_SIZE': {
return {
...state,
pageSize: action.payload
};
}
case 'SET_IS_PANEL_OPEN': {
return {
...state,
isPanelOpen: action.payload
};
}
default: {
return state;
}
}
};

View File

@@ -35,29 +35,22 @@ const Navigation: React.FC<unknown> = (props) => {
const [state, dispatch] = useReducer(reducer, initialState);
const location = useLocation();
const navigate = useNavigate();
const menuItems: string[] = [
'gameday',
'teams',
'venues'
];
const menuItems: string[] = ['gameday', 'divisions', 'leagues', 'seasons', 'sports', 'teams', 'venues'];
const onMenuItemClicked = (event: any, { name }: any) => {
navigate(name);
dispatch({ type: 'SET_ACTIVE_MENU_ITEM', payload: name });
}
};
useEffect(() => {
const activeLink: string = location.pathname === '/' ? 'home' : location.pathname.replace('/', '')
const activeLink: string =
location.pathname === '/' ? 'home' : location.pathname.replace('/', '');
dispatch({ type: 'SET_ACTIVE_MENU_ITEM', payload: activeLink });
}, [])
}, []);
return (
<Menu
pointing
secondary
vertical
>
<Menu pointing secondary vertical>
{menuItems.map((menuItem: string) => {
return (
<Menu.Item
@@ -66,10 +59,10 @@ const Navigation: React.FC<unknown> = (props) => {
active={state.activeLink === menuItem}
onClick={onMenuItemClicked}
/>
)
);
})}
</Menu>
)
}
);
};
export default Navigation;

View File

@@ -1,19 +1,21 @@
import { INavigationState } from "./INavigationState"
import { INavigationState } from './INavigationState';
export type Action =
| { type: 'SET_ACTIVE_MENU_ITEM', payload: string }
export type Action = { type: 'SET_ACTIVE_MENU_ITEM'; payload: string };
export const initialState: INavigationState = {
activeLink: ''
}
activeLink: ''
};
export const reducer = (state: INavigationState, action: Action): INavigationState => {
switch (action.type) {
case 'SET_ACTIVE_MENU_ITEM': {
return {
...state,
activeLink: action.payload
}
}
export const reducer = (
state: INavigationState,
action: Action
): INavigationState => {
switch (action.type) {
case 'SET_ACTIVE_MENU_ITEM': {
return {
...state,
activeLink: action.payload
};
}
}
}
};

View File

@@ -0,0 +1,4 @@
export interface INewSeasonModalProps {
isModalOpen: boolean;
onCloseModal: () => void;
}

View File

@@ -0,0 +1,8 @@
import { HubConnection } from "@microsoft/signalr";
export interface INewSeasonModalState {
connection: HubConnection | undefined,
year: string;
percentComplete: number;
loading: boolean;
}

View File

@@ -0,0 +1,134 @@
import React, { SyntheticEvent, useEffect, useReducer } from 'react';
import { initialState, reducer } from './reducer';
import {
Button,
Grid,
Icon,
Input,
Modal,
Progress
} from 'semantic-ui-react';
import { INewSeasonModalProps } from './INewSeasonModalProps';
import { HubConnection, HubConnectionBuilder } from '@microsoft/signalr';
import { ISeasonMessage } from '../../models/ISeasonMessage';
const NewSeasonModal: React.FC<INewSeasonModalProps> = ({ isModalOpen, onCloseModal }: INewSeasonModalProps) => {
const [state, dispatch] = useReducer(reducer, initialState);
const topic = 'ReceiveSeasonMessage';
const onYearChanged = (event: SyntheticEvent, data: any) => {
dispatch({ type: 'SET_YEAR', payload: data['value']})
}
const onOk = () => {
dispatch({ type: 'SET_IS_LOADING', payload: true });
}
useEffect(() => {
if (isModalOpen) {
const newConnection: HubConnection = new HubConnectionBuilder()
.withUrl('http://localhost:5580/hubs/season')
.withAutomaticReconnect()
.build();
dispatch({ type: 'SET_CONNECTION', payload: newConnection });
} else {
if (state.connection) {
state.connection.stop();
dispatch({ type: 'SET_CONNECTION', payload: undefined });
}
}
}, [isModalOpen])
useEffect(() => {
if (state.connection) {
const connection: HubConnection = state.connection;
connection.start().then((result) => {
console.log('connected');
connection.on(topic, (message: ISeasonMessage) => {
console.log(message);
dispatch({ type: 'SET_PERCENT_COMPLETE', payload: Math.floor((message.datesCompleted / message.totalDates) * 100) })
})
}).catch((error) => {
console.log(`Connection failed with the following message: ${error}`);
})
}
}, [state.connection]);
useEffect(() => {
if (state.percentComplete === 100) {
onCloseModal();
dispatch({ type: 'SET_PERCENT_COMPLETE', payload: 0 })
}
}, [state.percentComplete])
return (
<Modal
open={isModalOpen}
onClose={onCloseModal}
closeOnEscape={false}
closeOnDimmerClick={false}
>
<Modal.Header>
Add New Season
</Modal.Header>
<Modal.Content>
<Grid columns={1}>
{!state.loading &&
<React.Fragment>
<Grid.Row>
<span>Enter a year below and click <strong>OK</strong> to load games.</span>
</Grid.Row>
<Grid.Row>
<Input
label='Year'
onChange={onYearChanged}
/>
</Grid.Row>
</React.Fragment>
}
{state.loading &&
<Grid.Row>
<Grid.Column>
<Progress
percent={state.percentComplete}
indicating
progress
>
Loading games...
</Progress>
</Grid.Column>
</Grid.Row>
}
</Grid>
</Modal.Content>
<Modal.Actions>
<Button
icon
labelPosition='left'
onClick={onCloseModal}
disabled={state.loading}
>
<Icon name='cancel' />
Cancel
</Button>
<Button
primary
icon
labelPosition='left'
disabled={state.loading}
onClick={onOk}
>
<Icon name='check' />
OK
</Button>
</Modal.Actions>
</Modal>
)
}
export default NewSeasonModal;

View File

@@ -0,0 +1,47 @@
import { HubConnection } from "@microsoft/signalr";
import { INewSeasonModalState } from "./INewSeasonModalState";
type Action =
| { type: 'SET_CONNECTION'; payload: HubConnection | undefined }
| { type: 'SET_YEAR'; payload: string }
| { type: 'SET_PERCENT_COMPLETE'; payload: number }
| { type: 'SET_IS_LOADING'; payload: boolean };
export const initialState: INewSeasonModalState = {
connection: undefined,
year: '',
percentComplete: 0,
loading: false
}
export const reducer = (state: INewSeasonModalState, action: Action): INewSeasonModalState => {
switch (action.type) {
case 'SET_CONNECTION': {
return {
...state,
connection: action.payload
}
}
case 'SET_YEAR': {
return {
...state,
year: action.payload
}
}
case 'SET_PERCENT_COMPLETE': {
return {
...state,
percentComplete: action.payload
}
}
case 'SET_IS_LOADING': {
return {
...state,
loading: action.payload
}
}
default: {
return state;
}
}
}

View File

@@ -1,9 +1,7 @@
import React from 'react';
import { IPaginatorProps } from './IPaginatorProps';
import styles from './Paginator.module.scss';
import {
IconButton
} from '@fluentui/react/lib/Button';
import { IconButton } from '@fluentui/react/lib/Button';
import { Link } from '@fluentui/react/lib/Link';
const range = (start: number, end: number) => {

View File

@@ -0,0 +1,9 @@
import { ISeason } from '../../models/ISeason';
export interface ISeasonsState {
items: ISeason[];
currentPageNumber: number;
pageSize: number;
isPanelOpen: boolean;
isModalOpen: boolean;
}

View File

@@ -0,0 +1,202 @@
import React, { SyntheticEvent, useEffect, useReducer } from 'react';
import { useQuery } from '@apollo/client';
import { MessageBar, MessageBarType } from '@fluentui/react/lib/MessageBar';
import { initialState, reducer } from './reducer';
import { useAppContext } from '../../hooks/appContext/UseAppContext';
import { useSidebarContext } from '../../hooks/sidebarContext/UseSidebarContext';
import { GET_SEASONS } from '../../constants/graphQL';
import { INotification } from '../../models/INotification';
import {
Button,
Grid,
Header,
Icon,
Loader,
Pagination,
Sidebar,
Table
} from 'semantic-ui-react';
// import VenueDetails from '../venueDetails/VenueDetails';
import { usePagination } from '../../hooks/usePaginiation/UsePagination';
import NewSeasonModal from '../newSeasonDialog/NewSeasonModal';
const Seasons: React.FC<unknown> = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const appContext = useAppContext();
const sidebarContext = useSidebarContext();
const currentPageNumber = state.currentPageNumber;
const pageSize = state.pageSize;
const { data, loading, error, refetch } = useQuery(GET_SEASONS, {
variables: {
pageNumber: currentPageNumber,
pageSize: pageSize
}
});
const { pagination } = usePagination();
const columns: string[] = ['Name', ''];
const onDismissMessageBar = () => {
const notification: INotification = {
...appContext.state.notification,
isVisible: false
};
appContext.dispatch({ type: 'SET_NOTIFICATION', payload: notification });
};
const onPageChange = (event: SyntheticEvent, data: any) => {
const newCurrentPage: number = data.activePage;
dispatch({ type: 'SET_CURRENT_PAGE_NUMBER', payload: newCurrentPage });
};
const onDetails = (id: number) => {
// sidebarContext.dispatch({
// type: 'SET_SIDEBAR',
// payload: {
// sidebar: {
// visible: true,
// animation: 'overlay',
// width: 'very wide',
// direction: 'right'
// },
// content: <VenueDetails venueId={id} />
// }
// });
};
const onIsModalOpen = () => {
dispatch({ type: 'SET_IS_MODAL_OPEN', payload: !state.isModalOpen });
}
useEffect(() => {
if (data) {
dispatch({
type: 'SET_ITEMS',
payload: data.leagues
});
}
}, [data]);
useEffect(() => {
// console.log(error);
const notification: INotification = {
messageBarType: MessageBarType.error,
message: error?.message,
isMultiline: false,
isVisible: false
};
appContext.dispatch({ type: 'SET_NOTIFICATION', payload: notification });
}, [error]);
useEffect(() => {
refetch({
pageNumber: currentPageNumber,
pageSize: pageSize
});
}, [currentPageNumber]);
return (
<React.Fragment>
<Grid>
<Grid.Row columns={2}>
<Grid.Column >
<Header as='h1'>Seasons</Header>
</Grid.Column>
<Grid.Column textAlign='right'>
<Button
primary
icon
labelPosition='left'
onClick={onIsModalOpen}
>
<Icon name='add' />
Add
</Button>
</Grid.Column>
</Grid.Row>
{appContext.state.notification?.isVisible && (
<Grid.Row columns={1}>
<Grid.Column>
<MessageBar
messageBarType={appContext.state.notification?.messageBarType}
isMultiline={appContext.state.notification?.isMultiline}
onDismiss={onDismissMessageBar}
>
{appContext.state.notification?.message}
</MessageBar>
</Grid.Column>
</Grid.Row>
)}
<Grid.Row columns={1}>
<Grid.Column>
{loading && (
<Loader size='massive' active={loading}>
Loading...
</Loader>
)}
{!loading && state.items.length === 0 &&
<div>
<p>There are currently no seasons loaded.</p>
<p>Click <strong>Add</strong> to load a season.</p>
</div>
}
{!loading && state.items.length > 0 && (
<Table selectable>
<Table.Header>
<Table.Row>
{columns.map((column: string, index: number) => {
return (
<Table.HeaderCell key={index}>{column}</Table.HeaderCell>
);
})}
</Table.Row>
</Table.Header>
<Table.Body>
{state.items.map((item) => {
return (
<Table.Row key={item.id}>
<Table.Cell>{item.id}</Table.Cell>
<Table.Cell textAlign='right'>
<Button basic icon onClick={() => onDetails(item.id)}>
<Icon name='info circle' />
</Button>
</Table.Cell>
</Table.Row>
);
})}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan='2'>
<Grid columns={1}>
<Grid.Row>
<Grid.Column textAlign='right'>
<Pagination
totalPages={pagination ? pagination.TotalPages : ''}
activePage={state.currentPageNumber}
onPageChange={onPageChange}
size='mini'
/>
</Grid.Column>
</Grid.Row>
</Grid>
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
)}
</Grid.Column>
</Grid.Row>
</Grid>
<NewSeasonModal
isModalOpen={state.isModalOpen}
onCloseModal={onIsModalOpen}
/>
</React.Fragment>
);
};
export default Seasons;

View File

@@ -0,0 +1,58 @@
import { ISeasonsState } from './ISeasonsState';
import { ISeason } from '../../models/ISeason';
export type Action =
| { type: 'SET_ITEMS'; payload: ISeason[] }
| { type: 'SET_CURRENT_PAGE_NUMBER'; payload: number }
| { type: 'SET_PAGE_SIZE'; payload: number }
| { type: 'SET_IS_PANEL_OPEN'; payload: boolean }
| { type: 'SET_IS_MODAL_OPEN'; payload: boolean };
export const initialState: ISeasonsState = {
items: [],
currentPageNumber: 1,
pageSize: 10,
isPanelOpen: false,
isModalOpen: false,
};
export const reducer = (
state: ISeasonsState,
action: Action
): ISeasonsState => {
switch (action.type) {
case 'SET_ITEMS': {
return {
...state,
items: action.payload
};
}
case 'SET_CURRENT_PAGE_NUMBER': {
return {
...state,
currentPageNumber: action.payload
};
}
case 'SET_PAGE_SIZE': {
return {
...state,
pageSize: action.payload
};
}
case 'SET_IS_PANEL_OPEN': {
return {
...state,
isPanelOpen: action.payload
};
}
case 'SET_IS_MODAL_OPEN': {
return {
...state,
isModalOpen: action.payload
}
}
default: {
return state;
}
}
};

View File

@@ -0,0 +1,8 @@
import { ISport } from '../../models/ISport';
export interface ISportsState {
items: ISport[];
currentPageNumber: number;
pageSize: number;
isPanelOpen: boolean;
}

View File

@@ -0,0 +1,172 @@
import React, { SyntheticEvent, useEffect, useReducer } from 'react';
import { useQuery } from '@apollo/client';
import { MessageBar, MessageBarType } from '@fluentui/react/lib/MessageBar';
import { initialState, reducer } from './reducer';
import { useAppContext } from '../../hooks/appContext/UseAppContext';
import { useSidebarContext } from '../../hooks/sidebarContext/UseSidebarContext';
import { GET_SPORTS } from '../../constants/graphQL';
import { INotification } from '../../models/INotification';
import {
Button,
Grid,
Header,
Icon,
Loader,
Pagination,
Sidebar,
Table
} from 'semantic-ui-react';
// import VenueDetails from '../venueDetails/VenueDetails';
import { usePagination } from '../../hooks/usePaginiation/UsePagination';
const Sports: React.FC<unknown> = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const appContext = useAppContext();
const sidebarContext = useSidebarContext();
const currentPageNumber = state.currentPageNumber;
const pageSize = state.pageSize;
const { data, loading, error, refetch } = useQuery(GET_SPORTS, {
variables: {
pageNumber: currentPageNumber,
pageSize: pageSize
}
});
const { pagination } = usePagination();
const columns: string[] = ['Name', ''];
const onDismissMessageBar = () => {
const notification: INotification = {
...appContext.state.notification,
isVisible: false
};
appContext.dispatch({ type: 'SET_NOTIFICATION', payload: notification });
};
const onPageChange = (event: SyntheticEvent, data: any) => {
const newCurrentPage: number = data.activePage;
dispatch({ type: 'SET_CURRENT_PAGE_NUMBER', payload: newCurrentPage });
};
const onDetails = (id: number) => {
// sidebarContext.dispatch({
// type: 'SET_SIDEBAR',
// payload: {
// sidebar: {
// visible: true,
// animation: 'overlay',
// width: 'very wide',
// direction: 'right'
// },
// content: <VenueDetails venueId={id} />
// }
// });
};
useEffect(() => {
if (data) {
dispatch({
type: 'SET_ITEMS',
payload: data.sports
});
}
}, [data]);
useEffect(() => {
// console.log(error);
const notification: INotification = {
messageBarType: MessageBarType.error,
message: error?.message,
isMultiline: false,
isVisible: false
};
appContext.dispatch({ type: 'SET_NOTIFICATION', payload: notification });
}, [error]);
useEffect(() => {
refetch({
pageNumber: currentPageNumber,
pageSize: pageSize
});
}, [currentPageNumber]);
return (
<Grid columns={1}>
<Grid.Row>
<Header as='h1'>Sports</Header>
</Grid.Row>
{appContext.state.notification?.isVisible && (
<Grid.Row>
<Grid.Column>
<MessageBar
messageBarType={appContext.state.notification?.messageBarType}
isMultiline={appContext.state.notification?.isMultiline}
onDismiss={onDismissMessageBar}
>
{appContext.state.notification?.message}
</MessageBar>
</Grid.Column>
</Grid.Row>
)}
<Grid.Row>
<Grid.Column>
{loading && (
<Loader size='massive' active={loading}>
Loading...
</Loader>
)}
{!loading && (
<Table selectable>
<Table.Header>
<Table.Row>
{columns.map((column: string, index: number) => {
return (
<Table.HeaderCell key={index}>{column}</Table.HeaderCell>
);
})}
</Table.Row>
</Table.Header>
<Table.Body>
{state.items.map((item) => {
return (
<Table.Row key={item.id}>
<Table.Cell>{item.name}</Table.Cell>
<Table.Cell textAlign='right'>
<Button basic icon onClick={() => onDetails(item.id)}>
<Icon name='info circle' />
</Button>
</Table.Cell>
</Table.Row>
);
})}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan='2'>
<Grid columns={1}>
<Grid.Row>
<Grid.Column textAlign='right'>
<Pagination
totalPages={pagination ? pagination.TotalPages : ''}
activePage={state.currentPageNumber}
onPageChange={onPageChange}
size='mini'
/>
</Grid.Column>
</Grid.Row>
</Grid>
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
)}
</Grid.Column>
</Grid.Row>
</Grid>
);
};
export default Sports;

View File

@@ -0,0 +1,50 @@
import { ISportsState } from './ISportsState';
import { ISport } from '../../models/ISport';
export type Action =
| { type: 'SET_ITEMS'; payload: ISport[] }
| { type: 'SET_CURRENT_PAGE_NUMBER'; payload: number }
| { type: 'SET_PAGE_SIZE'; payload: number }
| { type: 'SET_IS_PANEL_OPEN'; payload: boolean };
export const initialState: ISportsState = {
items: [],
currentPageNumber: 1,
pageSize: 10,
isPanelOpen: false
};
export const reducer = (
state: ISportsState,
action: Action
): ISportsState => {
switch (action.type) {
case 'SET_ITEMS': {
return {
...state,
items: action.payload
};
}
case 'SET_CURRENT_PAGE_NUMBER': {
return {
...state,
currentPageNumber: action.payload
};
}
case 'SET_PAGE_SIZE': {
return {
...state,
pageSize: action.payload
};
}
case 'SET_IS_PANEL_OPEN': {
return {
...state,
isPanelOpen: action.payload
};
}
default: {
return state;
}
}
};

View File

@@ -1,13 +1,9 @@
import { ICommandBarItemProps } from "@fluentui/react/lib/CommandBar";
import { ICommandBarItemProps } from '@fluentui/react/lib/CommandBar';
import { ITeam } from '../../models/ITeam';
export interface ITeamsState {
items: ITeam[];
totalItemCount: number | null;
totalPageCount: number | null;
currentPageNumber: number;
pageSize: number;
selectedItems: ITeam[];
commandBarItems: ICommandBarItemProps[];
isPanelOpen: boolean;
}
items: ITeam[];
currentPageNumber: number;
pageSize: number;
isPanelOpen: boolean;
}

View File

@@ -1,135 +1,124 @@
import React, { SyntheticEvent, useEffect, useReducer } from 'react';
import { useQuery } from '@apollo/client';
import { MessageBar, MessageBarType } from '@fluentui/react/lib/MessageBar';
import { initialState, reducer } from './reducer'
import { initialState, reducer } from './reducer';
import { useAppContext } from '../../hooks/appContext/UseAppContext';
import { useSidebarContext } from '../../hooks/sidebarContext/UseSidebarContext';
import { GET_TEAMS } from '../../constants/graphQL';
import { INotification } from '../../models/INotification';
import { Button, Grid, Header, Icon, Loader, Pagination, Table } from 'semantic-ui-react'
import {
Button,
Grid,
Header,
Icon,
Loader,
Pagination,
Table
} from 'semantic-ui-react';
import { usePagination } from '../../hooks/usePaginiation/UsePagination';
const Teams: React.FC<unknown> = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const appContext = useAppContext();
const sidebarContext = useSidebarContext();
const currentPageNumber = state.currentPageNumber;
const pageSize = state.pageSize;
const { data, loading, error, refetch } = useQuery(GET_TEAMS, {
variables: {
pageNumber: currentPageNumber,
pageSize: pageSize
}
})
const columns: string[] = [
'Name',
''
]
const onPageChange = (event: SyntheticEvent, data: any) => {
const newCurrentPage: number = data.activePage;
dispatch({ type: 'SET_CURRENT_PAGE_NUMBER', payload: newCurrentPage });
const [state, dispatch] = useReducer(reducer, initialState);
const appContext = useAppContext();
const sidebarContext = useSidebarContext();
const currentPageNumber = state.currentPageNumber;
const pageSize = state.pageSize;
const { data, loading, error, refetch } = useQuery(GET_TEAMS, {
variables: {
pageNumber: currentPageNumber,
pageSize: pageSize
}
});
const { pagination } = usePagination();
useEffect(() => {
if (data) {
dispatch({
type: 'SET_ITEMS',
payload: {
items: data.teams.results,
totalItemCount: data.teams.totalItemCount,
totalPageCount: data.teams.totalPageCount
}
})
}
}, [data])
const columns: string[] = ['Name', ''];
useEffect(() => {
refetch({
pageNumber: currentPageNumber,
pageSize: pageSize
})
}, [currentPageNumber])
const onPageChange = (event: SyntheticEvent, data: any) => {
const newCurrentPage: number = data.activePage;
return (
<Grid columns={1}>
<Grid.Row>
<Header as='h1'>
Teams
</Header>
</Grid.Row>
{appContext.state.notification?.isVisible && (
<Grid.Row>
<Grid.Column>
</Grid.Column>
</Grid.Row>
)}
<Grid.Row>
<Grid.Column>
{loading && (
<Loader size='massive' active={loading}>Loading...</Loader>
)}
{!loading &&
<Table selectable>
<Table.Header>
<Table.Row>
{columns.map((column: string) => {
return (
<Table.HeaderCell>
{column}
</Table.HeaderCell>
)
})}
</Table.Row>
</Table.Header>
<Table.Body>
{state.items.map((item) => {
return (
<Table.Row
key={item.id}
>
<Table.Cell>
{item.name}
</Table.Cell>
<Table.Cell textAlign='right'>
<Button
basic
icon
// onClick={() => onDetails(item.id)}
>
<Icon name='info circle' />
</Button>
</Table.Cell>
</Table.Row>
)
})}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan='2'>
<Grid columns={1}>
<Grid.Row>
<Grid.Column textAlign='right'>
<Pagination
totalPages={data.teams.totalPageCount}
activePage={state.currentPageNumber}
onPageChange={onPageChange}
size='mini'
/>
</Grid.Column>
</Grid.Row>
</Grid>
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
}
</Grid.Column>
</Grid.Row>
</Grid>
)
}
dispatch({ type: 'SET_CURRENT_PAGE_NUMBER', payload: newCurrentPage });
};
export default Teams;
useEffect(() => {
if (data) {
dispatch({ type: 'SET_ITEMS', payload: data.teams });
}
}, [data]);
useEffect(() => {
refetch({
pageNumber: currentPageNumber,
pageSize: pageSize
});
}, [currentPageNumber]);
return (
<Grid columns={1}>
<Grid.Row>
<Header as='h1'>Teams</Header>
</Grid.Row>
{appContext.state.notification?.isVisible && (
<Grid.Row>
<Grid.Column></Grid.Column>
</Grid.Row>
)}
<Grid.Row>
<Grid.Column>
{loading && (
<Loader size='massive' active={loading}>
Loading...
</Loader>
)}
{!loading && (
<Table selectable>
<Table.Header>
<Table.Row>
{columns.map((column: string) => {
return <Table.HeaderCell>{column}</Table.HeaderCell>;
})}
</Table.Row>
</Table.Header>
<Table.Body>
{state.items.map((item) => {
return (
<Table.Row key={item.id}>
<Table.Cell>{item.name}</Table.Cell>
<Table.Cell textAlign='right'>
<Button
basic
icon
// onClick={() => onDetails(item.id)}
>
<Icon name='info circle' />
</Button>
</Table.Cell>
</Table.Row>
);
})}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan='2'>
<Grid columns={1}>
<Grid.Row>
<Grid.Column textAlign='right'>
<Pagination
totalPages={pagination ? pagination.TotalPages : ''}
activePage={state.currentPageNumber}
onPageChange={onPageChange}
size='mini'
/>
</Grid.Column>
</Grid.Row>
</Grid>
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
)}
</Grid.Column>
</Grid.Row>
</Grid>
);
};
export default Teams;

View File

@@ -1,73 +1,47 @@
import { ITeamsState } from './ITeamsState';
import { ITeam } from '../../models/ITeam';
import { ICommandBarItemProps } from '@fluentui/react/lib/CommandBar';
import { IObjectWithKey } from '@fluentui/react';
export type Action =
| {
type: 'SET_ITEMS';
payload: {
items: ITeam[];
totalItemCount: number;
totalPageCount: number;
};
}
| { type: 'SET_CURRENT_PAGE_NUMBER'; payload: number }
| { type: 'SET_PAGE_SIZE'; payload: number }
| { type: 'SET_SELECTED_ITEM'; payload: IObjectWithKey[] }
| { type: 'SET_COMMAND_BAR_ITEMS'; payload: ICommandBarItemProps[] }
| { type: 'SET_IS_PANEL_OPEN'; payload: boolean };
| { type: 'SET_ITEMS'; payload: ITeam[] }
| { type: 'SET_CURRENT_PAGE_NUMBER'; payload: number }
| { type: 'SET_PAGE_SIZE'; payload: number }
| { type: 'SET_IS_PANEL_OPEN'; payload: boolean };
export const initialState: ITeamsState = {
items: [],
totalItemCount: null,
totalPageCount: null,
currentPageNumber: 1,
pageSize: 10,
selectedItems: [],
commandBarItems: [],
isPanelOpen: false
}
items: [],
currentPageNumber: 1,
pageSize: 10,
isPanelOpen: false
};
export const reducer = (state: ITeamsState, action: Action): ITeamsState => {
switch (action.type) {
case 'SET_ITEMS': {
return {
...state,
items: action.payload.items,
totalItemCount: action.payload.totalItemCount,
totalPageCount: action.payload.totalPageCount
};
}
case 'SET_CURRENT_PAGE_NUMBER': {
return {
...state,
currentPageNumber: action.payload
};
}
case 'SET_PAGE_SIZE': {
return {
...state,
pageSize: action.payload
};
}
case 'SET_SELECTED_ITEM': {
return {
...state,
selectedItems: <ITeam[]>action.payload
};
}
case 'SET_COMMAND_BAR_ITEMS': {
return {
...state,
commandBarItems: action.payload
};
}
case 'SET_IS_PANEL_OPEN': {
return {
...state,
isPanelOpen: action.payload
};
}
switch (action.type) {
case 'SET_ITEMS': {
return {
...state,
items: action.payload
};
}
}
case 'SET_CURRENT_PAGE_NUMBER': {
return {
...state,
currentPageNumber: action.payload
};
}
case 'SET_PAGE_SIZE': {
return {
...state,
pageSize: action.payload
};
}
case 'SET_IS_PANEL_OPEN': {
return {
...state,
isPanelOpen: action.payload
};
}
default: {
return state;
}
}
};

View File

@@ -1,3 +1,3 @@
export interface IVenueDetailsProps {
venueId: number;
}
venueId: number;
}

View File

@@ -2,32 +2,32 @@ import React, { useEffect } from 'react';
import { IVenueDetailsProps } from './IVenueDetailsProps';
import { Grid, Header } from 'semantic-ui-react';
import { useQuery } from '@apollo/client';
import { GET_VENUE_BY_ID } from '../../constants/graphQL';
import { GET_VENUE } from '../../constants/graphQL';
const VenueDetails: React.FC<IVenueDetailsProps> = ({ venueId }: IVenueDetailsProps) => {
const { data, loading, error } = useQuery(GET_VENUE_BY_ID, {
variables: {
id: venueId
}
});
const VenueDetails: React.FC<IVenueDetailsProps> = ({
venueId
}: IVenueDetailsProps) => {
const { data, loading, error } = useQuery(GET_VENUE, {
variables: {
id: venueId
}
});
return (
<Grid>
<Grid.Row>
<Header as='h2'>
Venue
</Header>
</Grid.Row>
<Grid.Row>
{data &&
<ul>
<li>{data.venueById.results.name}</li>
<li>{data.venueById.results.id}</li>
</ul>
}
</Grid.Row>
</Grid>
)
}
return (
<Grid>
<Grid.Row>
<Header as='h2'>Venue</Header>
</Grid.Row>
<Grid.Row>
{data && (
<ul>
<li>{data.venueById.results.name}</li>
<li>{data.venueById.results.id}</li>
</ul>
)}
</Grid.Row>
</Grid>
);
};
export default VenueDetails;
export default VenueDetails;

View File

@@ -1,13 +1,8 @@
import { ICommandBarItemProps } from '@fluentui/react/lib/CommandBar';
import { IVenue } from '../../models/IVenue';
export interface IVenuesState {
items: IVenue[];
totalItemCount: number | null;
totalPageCount: number | null;
currentPageNumber: number;
pageSize: number;
selectedItems: IVenue[];
commandBarItems: ICommandBarItemProps[];
isPanelOpen: boolean;
}

View File

@@ -6,8 +6,18 @@ import { useAppContext } from '../../hooks/appContext/UseAppContext';
import { useSidebarContext } from '../../hooks/sidebarContext/UseSidebarContext';
import { GET_VENUES } from '../../constants/graphQL';
import { INotification } from '../../models/INotification';
import { Button, Grid, Header, Icon, Loader, Pagination, Sidebar, Table } from 'semantic-ui-react';
import {
Button,
Grid,
Header,
Icon,
Loader,
Pagination,
Sidebar,
Table
} from 'semantic-ui-react';
import VenueDetails from '../venueDetails/VenueDetails';
import { usePagination } from '../../hooks/usePaginiation/UsePagination';
const Venues: React.FC<unknown> = () => {
const [state, dispatch] = useReducer(reducer, initialState);
@@ -21,11 +31,9 @@ const Venues: React.FC<unknown> = () => {
pageSize: pageSize
}
});
const { pagination } = usePagination();
const columns: string[] = [
'Name',
''
]
const columns: string[] = ['Name', ''];
const onDismissMessageBar = () => {
const notification: INotification = {
@@ -43,28 +51,31 @@ const Venues: React.FC<unknown> = () => {
};
const onDetails = (id: number) => {
sidebarContext.dispatch({ type: 'SET_SIDEBAR', payload: { sidebar: { visible: true, animation: 'overlay', width: 'very wide', direction: 'right' }, content: <VenueDetails venueId={id} />}})
}
useEffect(() => {
console.log(sidebarContext)
}, [sidebarContext])
sidebarContext.dispatch({
type: 'SET_SIDEBAR',
payload: {
sidebar: {
visible: true,
animation: 'overlay',
width: 'very wide',
direction: 'right'
},
content: <VenueDetails venueId={id} />
}
});
};
useEffect(() => {
if (data) {
dispatch({
type: 'SET_ITEMS',
payload: {
items: data.venues.results,
totalItemCount: data.venues.totalItemCount,
totalPageCount: data.venues.totalPageCount
}
payload: data.venues
});
}
}, [data]);
useEffect(() => {
console.log(error);
// console.log(error);
const notification: INotification = {
messageBarType: MessageBarType.error,
message: error?.message,
@@ -80,66 +91,56 @@ const Venues: React.FC<unknown> = () => {
pageNumber: currentPageNumber,
pageSize: pageSize
});
}, [currentPageNumber])
}, [currentPageNumber]);
return (
<Grid columns={1}>
<Grid.Row>
<Header as='h1'>
Venues
</Header>
<Header as='h1'>Venues</Header>
</Grid.Row>
{appContext.state.notification?.isVisible && (
<Grid.Row>
<Grid.Column>
<MessageBar
messageBarType={appContext.state.notification?.messageBarType}
isMultiline={appContext.state.notification?.isMultiline}
onDismiss={onDismissMessageBar}
>
{appContext.state.notification?.message}
</MessageBar>
<MessageBar
messageBarType={appContext.state.notification?.messageBarType}
isMultiline={appContext.state.notification?.isMultiline}
onDismiss={onDismissMessageBar}
>
{appContext.state.notification?.message}
</MessageBar>
</Grid.Column>
</Grid.Row>
)}
<Grid.Row>
<Grid.Column>
{loading && (
<Loader size='massive' active={loading}>Loading...</Loader>
<Loader size='massive' active={loading}>
Loading...
</Loader>
)}
{!loading &&
{!loading && (
<Table selectable>
<Table.Header>
<Table.Row>
{columns.map((column: string) => {
{columns.map((column: string, index: number) => {
return (
<Table.HeaderCell>
{column}
</Table.HeaderCell>
)
<Table.HeaderCell key={index}>{column}</Table.HeaderCell>
);
})}
</Table.Row>
</Table.Header>
<Table.Body>
{state.items.map((item) => {
return (
<Table.Row
key={item.id}
>
<Table.Cell>
{item.name}
</Table.Cell>
<Table.Row key={item.id}>
<Table.Cell>{item.name}</Table.Cell>
<Table.Cell textAlign='right'>
<Button
basic
icon
onClick={() => onDetails(item.id)}
>
<Button basic icon onClick={() => onDetails(item.id)}>
<Icon name='info circle' />
</Button>
</Table.Cell>
</Table.Row>
)
);
})}
</Table.Body>
<Table.Footer>
@@ -147,11 +148,9 @@ const Venues: React.FC<unknown> = () => {
<Table.HeaderCell colSpan='2'>
<Grid columns={1}>
<Grid.Row>
<Grid.Column
textAlign='right'
>
<Grid.Column textAlign='right'>
<Pagination
totalPages={data?.venues?.totalPageCount}
totalPages={pagination ? pagination.TotalPages : ''}
activePage={state.currentPageNumber}
onPageChange={onPageChange}
size='mini'
@@ -163,7 +162,7 @@ const Venues: React.FC<unknown> = () => {
</Table.Row>
</Table.Footer>
</Table>
}
)}
</Grid.Column>
</Grid.Row>
</Grid>

View File

@@ -1,31 +1,16 @@
import { IVenuesState } from './IVenuesState';
import { IVenue } from '../../models/IVenue';
import { ICommandBarItemProps } from '@fluentui/react/lib/CommandBar';
import { IObjectWithKey } from '@fluentui/react';
export type Action =
| {
type: 'SET_ITEMS';
payload: {
items: IVenue[];
totalItemCount: number;
totalPageCount: number;
};
}
| { type: 'SET_ITEMS'; payload: IVenue[] }
| { type: 'SET_CURRENT_PAGE_NUMBER'; payload: number }
| { type: 'SET_PAGE_SIZE'; payload: number }
| { type: 'SET_SELECTED_ITEM'; payload: IObjectWithKey[] }
| { type: 'SET_COMMAND_BAR_ITEMS'; payload: ICommandBarItemProps[] }
| { type: 'SET_IS_PANEL_OPEN'; payload: boolean };
export const initialState: IVenuesState = {
items: [],
totalItemCount: null,
totalPageCount: null,
currentPageNumber: 1,
pageSize: 10,
selectedItems: [],
commandBarItems: [],
isPanelOpen: false
};
@@ -34,9 +19,7 @@ export const reducer = (state: IVenuesState, action: Action): IVenuesState => {
case 'SET_ITEMS': {
return {
...state,
items: action.payload.items,
totalItemCount: action.payload.totalItemCount,
totalPageCount: action.payload.totalPageCount
items: action.payload
};
}
case 'SET_CURRENT_PAGE_NUMBER': {
@@ -51,23 +34,14 @@ export const reducer = (state: IVenuesState, action: Action): IVenuesState => {
pageSize: action.payload
};
}
case 'SET_SELECTED_ITEM': {
return {
...state,
selectedItems: <IVenue[]>action.payload
};
}
case 'SET_COMMAND_BAR_ITEMS': {
return {
...state,
commandBarItems: action.payload
};
}
case 'SET_IS_PANEL_OPEN': {
return {
...state,
isPanelOpen: action.payload
};
}
default: {
return state;
}
}
};

View File

@@ -1,73 +1,86 @@
import { gql } from '@apollo/client';
export const GET_DIVISIONS = gql`
query GetDivisions($pageNumber: Int!, $pageSize: Int!) {
divisions(pageNumber: $pageNumber, pageSize: $pageSize) {
name
link
id
active
}
}
`;
export const GET_LEAGUES = gql`
query GetLeagues($pageNumber: Int!, $pageSize: Int!) {
leagues(pageNumber: $pageNumber, pageSize: $pageSize) {
name
link
id
active
}
}
`;
export const GET_SEASONS = gql`
query GetSeasons($pageNumber: Int!, $pageSize: Int!) {
seasons(pageNumber: $pageNumber, pageSize: $pageSize) {
id
active
}
}
`;
export const GET_SPORTS = gql`
query GetSports($pageNumber: Int!, $pageSize: Int!) {
sports(pageNumber: $pageNumber, pageSize: $pageSize) {
name
link
id
}
}
`;
export const GET_TEAMS = gql`
query GetTeams($pageNumber: Int!, $pageSize: Int!) {
teams(pageNumber: $pageNumber, pageSize: $pageSize) {
pageSize
message
errors
currentPage
totalPageCount
totalItemCount
succeeded
results {
name
link
id
active
}
name
link
id
active
}
}
`;
export const GET_TEAM = gql`
query GetTeam($id: Int!) {
teamById(id: $id) {
message
errors
succeeded
results {
name
link
id
active
}
team(id: $id) {
name
link
id
active
}
}
`
`;
export const GET_VENUES = gql`
query GetVenues($pageNumber: Int!, $pageSize: Int!) {
venues(pageNumber: $pageNumber, pageSize: $pageSize) {
pageSize
message
errors
currentPage
totalPageCount
totalItemCount
succeeded
results {
name
link
id
active
}
name
link
id
active
}
}
`;
export const GET_VENUE_BY_ID = gql`
export const GET_VENUE = gql`
query GetVenue($id: Int!) {
venueById(id: $id) {
message
errors
succeeded
results {
name
link
id
active
}
venue(id: $id) {
name
link
id
active
}
}
`;

View File

@@ -1,7 +1,9 @@
import { IPagination } from 'src/models/IPagination';
import { ISidebar } from 'src/models/ISidebar';
import { INotification } from '../../models/INotification';
export interface IAppContextState {
notification: INotification | undefined;
isSidebarVisible: boolean;
pagination: IPagination | undefined;
}

View File

@@ -1,10 +1,12 @@
import { MessageBarType } from '@fluentui/react';
import { IPagination } from 'src/models/IPagination';
import { INotification } from '../../models/INotification';
import { IAppContextState } from './IAppContextState';
export type Action =
export type Action =
| { type: 'SET_NOTIFICATION'; payload: INotification }
| { type: 'SET_IS_SIDEBAR_VISIBLE', payload: boolean };
| { type: 'SET_IS_SIDEBAR_VISIBLE'; payload: boolean }
| { type: 'SET_PAGINATION'; payload: IPagination | undefined };
export const initialState: IAppContextState = {
notification: {
@@ -13,7 +15,8 @@ export const initialState: IAppContextState = {
isMultiline: false,
isVisible: true
},
isSidebarVisible: false
isSidebarVisible: false,
pagination: undefined
};
export const reducer = (
@@ -31,7 +34,13 @@ export const reducer = (
return {
...state,
isSidebarVisible: action.payload
}
};
}
case 'SET_PAGINATION': {
return {
...state,
pagination: action.payload
};
}
default: {
return state;

View File

@@ -3,6 +3,6 @@ import { ISidebarContextState } from './ISidebarContextState';
import { Action } from './reducer';
export interface ISidebarContextProps {
state: ISidebarContextState;
dispatch: React.Dispatch<Action>;
}
state: ISidebarContextState;
dispatch: React.Dispatch<Action>;
}

View File

@@ -1,5 +1,5 @@
import React from "react";
import React from 'react';
export interface ISidebarContextProviderProps {
children: React.ReactNode;
}
children: React.ReactNode;
}

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { SidebarProps } from 'semantic-ui-react';
export interface ISidebarContextState {
content: React.ReactNode;
sidebar: SidebarProps;
title: string;
}
content: React.ReactNode;
sidebar: SidebarProps;
title: string;
}

View File

@@ -1,4 +1,4 @@
.sidebar {
background-color: #FFFFFF;
padding: 30px;
}
background-color: #ffffff;
padding: 30px;
}

View File

@@ -1,4 +1,5 @@
import { Context, createContext } from 'react';
import { ISidebarContextProps } from './ISidebarContextProps';
export const SidebarContext: Context<ISidebarContextProps> = createContext<ISidebarContextProps>({} as ISidebarContextProps);
export const SidebarContext: Context<ISidebarContextProps> =
createContext<ISidebarContextProps>({} as ISidebarContextProps);

View File

@@ -1,53 +1,48 @@
import React, { useEffect, useMemo, useReducer } from 'react';
import styles from './Sidebar.module.scss';
import { SidebarContext } from "./SidebarContext";
import { SidebarContext } from './SidebarContext';
import { ISidebarContextProps } from './ISidebarContextProps';
import { ISidebarContextProviderProps } from './ISidebarContextProviderProps';
import { initialState, reducer } from './reducer';
import { Button, Grid, Icon, Sidebar } from 'semantic-ui-react';
const SidebarContextProvider: React.FC<ISidebarContextProviderProps> = (props: ISidebarContextProviderProps) => {
const [state, dispatch] = useReducer(reducer, initialState);
const contextValue: ISidebarContextProps = useMemo(() => {
return {
state,
dispatch
}
}, [state, dispatch]);
const SidebarContextProvider: React.FC<ISidebarContextProviderProps> = (
props: ISidebarContextProviderProps
) => {
const [state, dispatch] = useReducer(reducer, initialState);
const contextValue: ISidebarContextProps = useMemo(() => {
return {
state,
dispatch
};
}, [state, dispatch]);
const onCloseSidebar = () => {
dispatch({ type: 'SET_SIDEBAR_VISIBLE', payload: !state.sidebar?.visible });
}
const onCloseSidebar = () => {
dispatch({ type: 'SET_SIDEBAR_VISIBLE', payload: !state.sidebar?.visible });
};
return (
<React.Fragment>
<Sidebar
className={styles.sidebar}
{...state.sidebar}
>
<Grid columns={1}>
<Grid.Row>
<Grid.Column
textAlign='right'
>
<Button>
<Icon name='close' size='large' onClick={onCloseSidebar} />
</Button>
</Grid.Column>
</Grid.Row>
<Grid.Row>
{state.content}
</Grid.Row>
<Grid.Row>
<Button onClick={onCloseSidebar}>Close</Button>
</Grid.Row>
</Grid>
</Sidebar>
<SidebarContext.Provider value={contextValue}>
{props.children}
</SidebarContext.Provider>
</React.Fragment>
)
}
return (
<React.Fragment>
<Sidebar className={styles.sidebar} {...state.sidebar}>
<Grid columns={1}>
<Grid.Row>
<Grid.Column textAlign='right'>
<Button>
<Icon name='close' size='large' onClick={onCloseSidebar} />
</Button>
</Grid.Column>
</Grid.Row>
<Grid.Row>{state.content}</Grid.Row>
<Grid.Row>
<Button onClick={onCloseSidebar}>Close</Button>
</Grid.Row>
</Grid>
</Sidebar>
<SidebarContext.Provider value={contextValue}>
{props.children}
</SidebarContext.Provider>
</React.Fragment>
);
};
export default SidebarContextProvider;
export default SidebarContextProvider;

View File

@@ -1,52 +1,58 @@
import React from "react";
import { SidebarProps } from "semantic-ui-react";
import { ISidebarContextState } from "./ISidebarContextState";
import React from 'react';
import { SidebarProps } from 'semantic-ui-react';
import { ISidebarContextState } from './ISidebarContextState';
export type Action =
| { type: 'SET_SIDEBAR', payload: { sidebar: SidebarProps, content: React.ReactNode }}
| { type: 'SET_SIDEBAR_PROPS', payload: SidebarProps}
| { type: 'SET_SIDEBAR_CONTENT', payload: React.ReactNode }
| { type: 'SET_SIDEBAR_VISIBLE', payload: boolean }
export type Action =
| {
type: 'SET_SIDEBAR';
payload: { sidebar: SidebarProps; content: React.ReactNode };
}
| { type: 'SET_SIDEBAR_PROPS'; payload: SidebarProps }
| { type: 'SET_SIDEBAR_CONTENT'; payload: React.ReactNode }
| { type: 'SET_SIDEBAR_VISIBLE'; payload: boolean };
export const initialState: ISidebarContextState = {
sidebar: {
visible: false
},
content: undefined
}
sidebar: {
visible: false
},
content: undefined
};
export const reducer = (state: ISidebarContextState, action: Action): ISidebarContextState => {
switch (action.type) {
case 'SET_SIDEBAR': {
return {
...state,
sidebar: action.payload.sidebar,
content: action.payload.content
}
}
case 'SET_SIDEBAR_PROPS': {
return {
...state,
sidebar: action.payload
}
}
case 'SET_SIDEBAR_CONTENT': {
return {
...state,
content: action.payload
}
}
case 'SET_SIDEBAR_VISIBLE': {
return {
...state,
sidebar: {
...state.sidebar,
visible: action.payload
}
}
}
default: {
return state;
}
export const reducer = (
state: ISidebarContextState,
action: Action
): ISidebarContextState => {
switch (action.type) {
case 'SET_SIDEBAR': {
return {
...state,
sidebar: action.payload.sidebar,
content: action.payload.content
};
}
}
case 'SET_SIDEBAR_PROPS': {
return {
...state,
sidebar: action.payload
};
}
case 'SET_SIDEBAR_CONTENT': {
return {
...state,
content: action.payload
};
}
case 'SET_SIDEBAR_VISIBLE': {
return {
...state,
sidebar: {
...state.sidebar,
visible: action.payload
}
};
}
default: {
return state;
}
}
};

View File

@@ -2,10 +2,10 @@ import { useContext } from 'react';
import { SidebarContext } from '../../context/sidebar/SidebarContext';
export const useSidebarContext = () => {
const { state, dispatch } = useContext(SidebarContext);
const { state, dispatch } = useContext(SidebarContext);
return {
state,
dispatch
}
}
return {
state,
dispatch
};
};

View File

@@ -1,6 +0,0 @@
export interface IUsePaginationProps {
totalCount: number;
pageSize: number;
siblingCount: number;
currentPage: number;
}

View File

@@ -1,71 +1,45 @@
import { useMemo } from 'react';
import { IUsePaginationProps } from './IUsePaginationProps';
import React, { useEffect, useState } from 'react';
import { useAppContext } from '../appContext/UseAppContext';
import { IPagination } from '../../models/IPagination';
export const DOTS = '...';
export const usePagination = () => {
const [pagination, setPagination] = useState<IPagination | null>(null);
const range = (start: number, end: number) => {
const length = end - start + 1;
return Array.from({ length }, (_, idx) => idx + start);
};
export const usePagination = ({
totalCount,
pageSize,
siblingCount = 1,
currentPage
}: IUsePaginationProps) => {
const paginationRange = useMemo(() => {
const totalPageCount = Math.ceil(totalCount / pageSize);
// Pages count is determined as siblingCount + firstPage + lastPage + currentPage + 2*DOTS
const totalPageNumbers = siblingCount + 5;
/*
If the number of pages is less than the page numbers we want to show in our
paginationComponent, we return the range [1..totalPageCount]
*/
if (totalPageNumbers >= totalPageCount) {
return range(1, totalPageCount);
}
const leftSiblingIndex = Math.max(currentPage - siblingCount, 1);
const rightSiblingIndex = Math.min(
currentPage + siblingCount,
totalPageCount
);
/*
We do not want to show dots if there is only one position left
after/before the left/right page count as that would lead to a change if our Pagination
component size which we do not want
*/
const shouldShowLeftDots = leftSiblingIndex > 2;
const shouldShowRightDots = rightSiblingIndex < totalPageCount - 2;
const firstPageIndex = 1;
const lastPageIndex = totalPageCount;
if (!shouldShowLeftDots && shouldShowRightDots) {
const leftItemCount = 3 + 2 * siblingCount;
const leftRange = range(1, leftItemCount);
return [...leftRange, DOTS, totalPageCount];
}
if (shouldShowLeftDots && !shouldShowRightDots) {
const rightItemCount = 3 + 2 * siblingCount;
const rightRange = range(
totalPageCount - rightItemCount + 1,
totalPageCount
);
return [firstPageIndex, DOTS, ...rightRange];
}
if (shouldShowLeftDots && shouldShowRightDots) {
const middleRange = range(leftSiblingIndex, rightSiblingIndex);
return [firstPageIndex, DOTS, ...middleRange, DOTS, lastPageIndex];
}
}, [totalCount, pageSize, siblingCount, currentPage]);
return paginationRange;
const getStoredValue = (): IPagination | null => {
const storedPagination: string | null =
sessionStorage.getItem('pagination');
console.log(storedPagination);
let pagination: IPagination;
if (storedPagination) {
return (pagination = JSON.parse(storedPagination));
} else {
return null;
}
};
const removeStoredValue = () => {
sessionStorage.removeItem('pagination');
console.log('blah');
};
const onSessionStorageChanged = () => {
setPagination(getStoredValue());
};
useEffect(() => {
setPagination(getStoredValue());
window.addEventListener('storage', onSessionStorageChanged);
return () => {
console.log('blah');
window.removeEventListener('storage', onSessionStorageChanged);
};
}, []);
return {
pagination,
removeStoredValue
};
};

View File

@@ -0,0 +1,24 @@
import React, { useEffect, useState } from 'react';
import { IPagination } from '../../models/IPagination';
export const useSessionStorage = (key: string) => {
const [storedValue, setStoredValue] = useState<IPagination>(
JSON.parse(sessionStorage.getItem(key) || '')
);
useEffect(() => {
const onStorage = () => {
setStoredValue(JSON.parse(sessionStorage.getItem(key) || ''));
};
window.addEventListener('storage', onStorage);
return () => {
window.removeEventListener('storage', onStorage);
};
}, []);
return {
storedValue
};
};

View File

@@ -1,14 +1,44 @@
import React from 'react';
import { createRoot, Root } from 'react-dom/client';
import { BrowserRouter as Router } from 'react-router-dom';
import { ApolloClient, InMemoryCache, ApolloProvider, NormalizedCacheObject } from '@apollo/client';
import {
ApolloClient,
InMemoryCache,
ApolloProvider,
NormalizedCacheObject,
HttpLink,
from,
ApolloLink
} from '@apollo/client';
import AppContextProvider from './context/app/AppContextProvider';
import App from './components/app/App';
import 'semantic-ui-css/semantic.min.css';
import SidebarContextProvider from './context/sidebar/SidebarContextProvider';
const httpLink = new HttpLink({ uri: 'http://localhost:8080/graphql' });
const afterwareLink = new ApolloLink((operation, forward) => {
return forward(operation).map((data) => {
const context = operation.getContext();
const {
response: { headers }
} = context;
if (headers) {
const pagination = headers.get('x-pagination');
if (pagination) {
sessionStorage.setItem('pagination', pagination);
window.dispatchEvent(new Event('storage'));
}
}
return data;
});
});
const client: ApolloClient<NormalizedCacheObject> = new ApolloClient({
uri: 'http://localhost:8081/graphql',
link: from([afterwareLink, httpLink]),
cache: new InMemoryCache()
});

View File

@@ -0,0 +1,6 @@
export interface IDivision {
active: boolean;
id: number;
link: string;
name: string;
}

View File

@@ -0,0 +1,7 @@
export interface ILeague {
active: boolean;
id: number;
link: string;
name: string;
}

View File

@@ -0,0 +1,6 @@
export interface IPagination {
CurrentPage: number;
PageSize: number;
TotalCount: number;
TotalPages: number;
}

View File

@@ -0,0 +1,4 @@
export interface ISeason {
id: number;
active: boolean;
}

View File

@@ -0,0 +1,4 @@
export interface ISeasonMessage {
totalDates: number;
datesCompleted: number;
}

View File

@@ -1,6 +1,6 @@
import React from "react";
import React from 'react';
export interface ISidebar {
component: React.ReactNode | undefined;
isVisible: boolean;
}
component: React.ReactNode | undefined;
isVisible: boolean;
}

6
app/src/models/ISport.ts Normal file
View File

@@ -0,0 +1,6 @@
export interface ISport {
active: boolean;
id: number;
link: string;
name: string;
}

View File

@@ -1,23 +1,23 @@
export interface ITeam {
id: number;
name: string;
abbreviation: string;
active: string;
allStarStatus: string;
clubName: string;
divisionId: number;
fileCode: string;
firstYearOfPlay: number;
franchiseName: string;
leagueId: number;
link: string;
locationName: string;
season: string;
shortName: string;
springLeagueId: number;
sprintVenueId: number;
sportId: number;
teamCode: string;
teamName: string;
venueId: number;
}
id: number;
name: string;
abbreviation: string;
active: string;
allStarStatus: string;
clubName: string;
divisionId: number;
fileCode: string;
firstYearOfPlay: number;
franchiseName: string;
leagueId: number;
link: string;
locationName: string;
season: string;
shortName: string;
springLeagueId: number;
sprintVenueId: number;
sportId: number;
teamCode: string;
teamName: string;
venueId: number;
}

View File

@@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

3
communication-hub/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.vs/
[Bb]in/
[Oo]bj/

View File

@@ -0,0 +1,11 @@
using System;
using CommunicationHub.Models;
namespace CommunicationHub.Clients
{
public interface ISeasonClient
{
Task ReceiveSeasonMessage(SeasonMessage message);
}
}

View File

@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerComposeProjectPath>docker-compose.dcproj</DockerComposeProjectPath>
<UserSecretsId>e2ffe822-2852-46ab-a3a4-ea30601c3968</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
</ItemGroup>
<ItemGroup>
<None Remove="Hubs\" />
<None Remove="Models\" />
<None Remove="Clients\" />
<None Remove="Controllers\" />
</ItemGroup>
<ItemGroup>
<Folder Include="Hubs\" />
<Folder Include="Models\" />
<Folder Include="Clients\" />
<Folder Include="Controllers\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,31 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 25.0.1705.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunicationHub", "CommunicationHub.csproj", "{26F5E13C-C06A-425F-8C38-BA098E1EBE7D}"
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{8522C27B-F7DE-4451-8660-AAD89B6E5353}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{26F5E13C-C06A-425F-8C38-BA098E1EBE7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{26F5E13C-C06A-425F-8C38-BA098E1EBE7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{26F5E13C-C06A-425F-8C38-BA098E1EBE7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{26F5E13C-C06A-425F-8C38-BA098E1EBE7D}.Release|Any CPU.Build.0 = Release|Any CPU
{8522C27B-F7DE-4451-8660-AAD89B6E5353}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8522C27B-F7DE-4451-8660-AAD89B6E5353}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8522C27B-F7DE-4451-8660-AAD89B6E5353}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8522C27B-F7DE-4451-8660-AAD89B6E5353}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {59D3F6E7-DC4F-4ADC-ABF7-46DD18371B39}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,22 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["CommunicationHub.csproj", "."]
RUN dotnet restore "./CommunicationHub.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "CommunicationHub.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "CommunicationHub.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "CommunicationHub.dll"]

View File

@@ -0,0 +1,16 @@
using System;
using CommunicationHub.Clients;
using CommunicationHub.Models;
using Microsoft.AspNetCore.SignalR;
namespace CommunicationHub.Hubs
{
public class SeasonHub : Hub<ISeasonClient>
{
public async Task SendSeasonMessage(SeasonMessage message)
{
await Clients.All.ReceiveSeasonMessage(message);
}
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace CommunicationHub.Models
{
public class SeasonMessage
{
public int TotalDates { get; set; }
public int DatesCompleted { get; set; }
}
}

View File

@@ -0,0 +1,39 @@
using CommunicationHub.Hubs;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSignalR();
builder.Services.AddCors((options) =>
{
options.AddPolicy("CorsPolicy", policy =>
{
policy.AllowAnyHeader()
.AllowAnyMethod()
.WithOrigins("http://localhost:3000")
.AllowCredentials();
});
});
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("CorsPolicy");
app.MapControllers();
app.MapHub<SeasonHub>("/hubs/season");
app.Run();

View File

@@ -0,0 +1,31 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:58148",
"sslPort": 44318
}
},
"profiles": {
"CommunicationHub": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5580",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk" DefaultTargets="Build">
<PropertyGroup Label="Globals">
<ProjectVersion>2.1</ProjectVersion>
<DockerTargetOS>Linux</DockerTargetOS>
<ProjectGuid>{8522C27B-F7DE-4451-8660-AAD89B6E5353}</ProjectGuid>
<DockerLaunchBrowser>True</DockerLaunchBrowser>
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}/swagger</DockerServiceUrl>
<DockerServiceName>communicationhub</DockerServiceName>
</PropertyGroup>
<ItemGroup>
<None Include="docker-compose.override.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose.yml" />
<None Include=".dockerignore" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
version: '3.4'
services:
communicationhub:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=https://+:443;http://+:80
ports:
- "80"
- "443"
volumes:
- ~/.aspnet/https:/root/.aspnet/https:ro
- ~/.microsoft/usersecrets:/root/.microsoft/usersecrets:ro

View File

@@ -0,0 +1,8 @@
version: '3.4'
services:
communicationhub:
image: ${DOCKER_REGISTRY-}communicationhub
build:
context: .
dockerfile: ./Dockerfile

View File

@@ -1,5 +0,0 @@
POSTGRES_SCHEMAS=flyway
POSTGRES_DB=mlb-game-day
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
ConnectionStrings__DBConnectionString="Host=postgres:5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"

5
databases/divisions/.env Normal file
View File

@@ -0,0 +1,5 @@
POSTGRES_SCHEMAS=flyway
POSTGRES_DB=divisions
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
ConnectionStrings__DBConnectionString="Host=divisions-database:5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"

View File

@@ -0,0 +1,7 @@
flyway.url=jdbc:postgresql://divisions-database:5432/divisions
flyway.locations=filesystem:/flyway/sql
flyway.configFiles=filesystem:/flyway/conf
flyway.user=postgres
flyway.password=postgres
flyway.baselineVersion=0
flyway.baselineOnMigrate=true

View File

@@ -0,0 +1,71 @@
CREATE TABLE IF NOT EXISTS division (
"id" INT,
"name" TEXT,
"season" INT,
"nameShort" TEXT,
"link" TEXT,
"abbreviation" TEXT,
"leagueId" INT,
"sportId" INT,
"hasWildcard" TEXT,
"sortOrder" INT,
"numPlayoffTeams" INT,
"active" TEXT
);
INSERT INTO division VALUES
(205,'National League Central',2022,'NL Central','/api/v1/divisions/205','NLC',104,1,'False',33,1,'True'),
(200,'American League West',2022,'AL West','/api/v1/divisions/200','ALW',103,1,'False',24,1,'True'),
(233,'Pacific Coast League East',2022,'PCL East','/api/v1/divisions/233','PCLE',112,11,'False',122,1,'True'),
(212,'Eastern League Northeast',2022,'EAS Northeast','/api/v1/divisions/212','EASNE',113,12,'False',213,1,'True'),
(201,'American League East',2022,'AL East','/api/v1/divisions/201','ALE',103,1,'False',22,1,'True'),
(204,'National League East',2022,'NL East','/api/v1/divisions/204','NLE',104,1,'False',32,1,'True'),
(202,'American League Central',2022,'AL Central','/api/v1/divisions/202','ALC',103,1,'False',23,1,'True'),
(241,'Texas League North',2022,'TEX North','/api/v1/divisions/241','TEXN',109,12,'False',233,1,'True'),
(239,'Southern League North',2022,'SOU North','/api/v1/divisions/239','SOUN',111,12,'False',223,1,'True'),
(203,'National League West',2022,'NL West','/api/v1/divisions/203','NLW',104,1,'False',34,1,'True'),
(219,'International League East',2022,'INT East','/api/v1/divisions/219','INTE',117,11,'False',112,1,'True'),
(222,'Mexican League Norte',2022,'MEX Norte','/api/v1/divisions/222','MEXN',125,23,'False',2127,5,'True'),
(223,'Mexican League Sur',2022,'MEX Sur','/api/v1/divisions/223','MEXS',125,23,'False',2128,5,'True'),
(221,'International League West',2022,'INT West','/api/v1/divisions/221','INTW',117,11,'False',114,1,'True'),
(231,'Pacific Coast League West',2022,'PCL West','/api/v1/divisions/231','PCLW',112,11,'False',123,1,'True'),
(213,'Eastern League Southwest',2022,'EAS Southwest','/api/v1/divisions/213','EASSW',113,12,'False',215,1,'True'),
(240,'Southern League South',2022,'SOU South','/api/v1/divisions/240','SOUS',111,12,'False',225,1,'True'),
(242,'Texas League South',2022,'TEX South','/api/v1/divisions/242','TEXS',109,12,'False',235,1,'True'),
(208,'California League North',2022,'CAL North','/api/v1/divisions/208','CALN',110,14,'False',433,1,'True'),
(224,'Midwest League East',2022,'MID East','/api/v1/divisions/224','MIDE',118,13,'False',323,1,'True'),
(237,'South Atlantic League North',2022,'SAL North','/api/v1/divisions/237','SALN',116,13,'False',313,1,'True'),
(214,'Florida State League East',2022,'FSL East','/api/v1/divisions/214','FSLE',123,14,'False',423,1,'True'),
(209,'California League South',2022,'CAL South','/api/v1/divisions/209','CALS',110,14,'False',435,1,'True'),
(215,'Florida State League West',2022,'FSL West','/api/v1/divisions/215','FSLW',123,14,'False',425,1,'True'),
(225,'Midwest League West',2022,'MID West','/api/v1/divisions/225','MIDW',118,13,'False',325,1,'True'),
(238,'South Atlantic League South',2022,'SAL South','/api/v1/divisions/238','SALS',116,13,'False',315,1,'True'),
(210,'Carolina League North',2022,'CAR North','/api/v1/divisions/210','CARN',122,14,'False',413,1,'True'),
(247,'Dominican Summer League North',2022,'DSL North','/api/v1/divisions/247','DSLN',130,16,'False',732,1,'True'),
(211,'Carolina League South',2022,'CAR South','/api/v1/divisions/211','CARS',122,14,'False',417,1,'True'),
(316,'WBC Qualifier 1',2022,'WBC Qualifier 1','/api/v1/divisions/316','WBC Q1',159,51,'False',3619,NULL,'True'),
(317,'WBC Qualifier 2',2022,'WBC Qualifier 2','/api/v1/divisions/317','WBC Q2',159,51,'False',3620,NULL,'True'),
(310,'Pool A',2021,'Pool A','/api/v1/divisions/310','Pool A',160,51,'False',3612,NULL,'True'),
(311,'Pool B',2021,'Pool B','/api/v1/divisions/311','Pool B',160,51,'False',3613,NULL,'True'),
(312,'Pool C',2021,'Pool C','/api/v1/divisions/312','Pool C',160,51,'False',3614,NULL,'True'),
(313,'Pool D',2021,'Pool D','/api/v1/divisions/313','Pool D',160,51,'False',3615,NULL,'True'),
(314,'Asia Quarterfinals',2021,'Asia Quarterfinals','/api/v1/divisions/314','AQ',160,51,'False',3617,2,'True'),
(315,'North America Quarterfinals',2021,'N.A. Quarterfinals','/api/v1/divisions/315','NAQ',160,51,'False',3618,2,'True'),
(217,'Florida Complex League North',2022,'FCL North','/api/v1/divisions/217','FCLN',124,16,'False',725,1,'True'),
(248,'Dominican Summer League South',2022,'DSL South','/api/v1/divisions/248','DSLS',130,16,'False',733,1,'True'),
(216,'Florida Complex League East',2022,'FCL East','/api/v1/divisions/216','FCLE',124,16,'False',723,1,'True'),
(249,'Dominican Summer League Northwest',2022,'DSL Northwest','/api/v1/divisions/249','DSLNW',130,16,'False',735,1,'True'),
(218,'Florida Complex League South',2022,'FCL South','/api/v1/divisions/218','FCLS',124,16,'False',729,1,'True'),
(250,'Dominican Summer League Baseball City',2022,'DSL Baseball City','/api/v1/divisions/250','DSLBC',130,16,'False',736,1,'True'),
(246,'Dominican Summer League San Pedro',2022,'DSL San Pedro','/api/v1/divisions/246','DSLSP',130,16,'False',737,1,'True'),
(401,'Dominican Summer League Northeast',2022,'DSL Northeast','/api/v1/divisions/401','DSLNE',130,16,'False',734,1,'True'),
(251,'Arizona Fall League East',2021,'AFL East','/api/v1/divisions/251','AFLE',119,17,'False',1312,1,'True'),
(252,'Arizona Fall League West',2021,'AFL West','/api/v1/divisions/252','AFLW',119,17,'False',1313,1,'True'),
(560,'Arizona Complex League East',2022,'ACL East','/api/v1/divisions/560','ACLE',121,16,'False',713,1,'True'),
(561,'Arizona Complex League West',2022,'ACL West','/api/v1/divisions/561','ACLW',121,16,'False',717,1,'True'),
(410,'LVBP Central Division',2020,'LVBP Oriental','/api/v1/divisions/410','ORI',135,17,'False',1336,2,'True'),
(411,'LVBP Occidental Division',2020,'LVBP Occidental','/api/v1/divisions/411','OCC',135,17,'False',1339,2,'True'),
(5418,'Australian Baseball League Northeast',2022,'Australian Lg. NE','/api/v1/divisions/5418','ABL NE',595,17,'False',1362,2,'True'),
(5419,'Australian Baseball League Southwest',2022,'Australian Lg. SW','/api/v1/divisions/5419','ABL SW',595,17,'False',1363,2,'True'),
(570,'Arizona Complex League Central',2022,'ACL Central','/api/v1/divisions/570','ACLC',121,16,'False',715,1,'True'),
(5436,'South Division',2022,'South','/api/v1/divisions/5436','ALPBS',436,23,'False',2123,1,'True'),
(5437,'North Division',2022,'North','/api/v1/divisions/5437','ALPBN',436,23,'False',2122,1,'True');

5
databases/games/.env Normal file
View File

@@ -0,0 +1,5 @@
POSTGRES_SCHEMAS=flyway
POSTGRES_DB=games
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
ConnectionStrings__DBConnectionString="Host=games-database:5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"

View File

@@ -1,4 +1,4 @@
flyway.url=jdbc:postgresql://postgres:5432/mlb-game-day
flyway.url=jdbc:postgresql://games-database:5432/games
flyway.locations=filesystem:/flyway/sql
flyway.configFiles=filesystem:/flyway/conf
flyway.user=postgres

View File

@@ -0,0 +1,51 @@
CREATE TABLE IF NOT EXISTS game (
"id" SERIAL PRIMARY KEY,
"gamePk" INT,
"link" TEXT,
"gameType" TEXT,
"season" INT,
"gameDate" TEXT,
"officialDate" TIMESTAMP,
"status_abstractGameState" TEXT,
"status_codedGameState" TEXT,
"status_detailedState" TEXT,
"status_statusCode" TEXT,
"status_startTimeTBD" BOOLEAN,
"status_abstractGameCode" TEXT,
"teams_away_leagueRecord_wins" INT,
"teams_away_leagueRecord_losses" INT,
"teams_away_leagueRecord_pct" NUMERIC(4, 3),
"teams_away_score" INT,
"teams_away_teamId" INT,
"teams_away_isWinner" BOOLEAN,
"teams_away_splitSquad" BOOLEAN,
"teams_away_seriesNumber" INT,
"teams_home_leagueRecord_wins" INT,
"teams_home_leagueRecord_losses" INT,
"teams_home_leagueRecord_pct" NUMERIC(4, 3),
"teams_home_score" INT,
"teams_home_teamId" INT,
"teams_home_isWinner" BOOLEAN,
"teams_home_splitSquad" BOOLEAN,
"teams_home_seriesNumber" INT,
"venueId" INT,
"content_link" TEXT,
"isTie" BOOLEAN,
"gameNumber" INT,
"publicFacing" BOOLEAN,
"doubleHeader" TEXT,
"gamedayType" TEXT,
"tiebreaker" TEXT,
"calendarEventID" TEXT,
"seasonDisplay" INT,
"dayNight" TEXT,
"scheduledInnings" INT,
"reverseHomeAwayStatus" BOOLEAN,
"inningBreakLength" INT,
"gamesInSeries" INT,
"seriesGameNumber" INT,
"seriesDescription" TEXT,
"recordSource" TEXT,
"ifNecessary" TEXT,
"ifNecessaryDescription" TEXT
);

5
databases/leagues/.env Normal file
View File

@@ -0,0 +1,5 @@
POSTGRES_SCHEMAS=flyway
POSTGRES_DB=leagues
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
ConnectionStrings__DBConnectionString="Host=leagues-database:5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"

View File

@@ -0,0 +1,7 @@
flyway.url=jdbc:postgresql://leagues-database:5432/leagues
flyway.locations=filesystem:/flyway/sql
flyway.configFiles=filesystem:/flyway/conf
flyway.user=postgres
flyway.password=postgres
flyway.baselineVersion=0
flyway.baselineOnMigrate=true

View File

@@ -0,0 +1,29 @@
CREATE TABLE IF NOT EXISTS league (
"id" INT,
"name" TEXT,
"link" TEXT,
"abbreviation" TEXT,
"nameShort" TEXT,
"seasonState" TEXT,
"hasWildCard" TEXT,
"hasSplitSeason" TEXT,
"numGames" INT,
"hasPlayoffPoints" TEXT,
"numTeams" INT,
"numWildcardTeams" INT,
"orgCode" TEXT,
"conferencesInUse" TEXT,
"divisionsInUse" TEXT,
"sportId" INT,
"sortOrder" INT,
"active" TEXT
);
INSERT INTO league VALUES
(103,'American League','/api/v1/league/103','AL','American','offseason','True','False',162,'False',15,3,'AL','False','True',1,21,'True'),
(104,'National League','/api/v1/league/104','NL','National','offseason','True','False',162,'False',15,3,'NL','False','True',1,31,'True'),
(114,'Cactus League','/api/v1/league/114','CL','Cactus','offseason','False','False',NULL,'False',NULL,NULL,'CL','False','False',NULL,51,'True'),
(115,'Grapefruit League','/api/v1/league/115','GL','Grapefruit','offseason','False','False',NULL,'False',NULL,NULL,'GL','False','False',NULL,52,'True'),
(100,'American Association','/api/v1/league/100','AA',NULL,'offseason','False','False',NULL,NULL,9,NULL,'AA','False','False',1,2811,'False'),
(105,'Players League','/api/v1/league/105','PL',NULL,'offseason','False','False',NULL,NULL,8,NULL,'PL','False','False',1,2831,'False'),
(101,'Union Association','/api/v1/league/101','UA',NULL,'offseason','False','False',NULL,NULL,12,NULL,'UA','False','False',1,2841,'False'),
(106,'Federal League','/api/v1/league/106','FL',NULL,'offseason','False','False',154,NULL,8,NULL,'FL','False','False',1,2851,'False');

5
databases/seasons/.env Normal file
View File

@@ -0,0 +1,5 @@
POSTGRES_SCHEMAS=flyway
POSTGRES_DB=seasons
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
ConnectionStrings__DBConnectionString="Host=seasons-database:5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"

View File

@@ -0,0 +1,7 @@
flyway.url=jdbc:postgresql://seasons-database:5432/seasons
flyway.locations=filesystem:/flyway/sql
flyway.configFiles=filesystem:/flyway/conf
flyway.user=postgres
flyway.password=postgres
flyway.baselineVersion=0
flyway.baselineOnMigrate=true

View File

@@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS season (
"id" INT,
"active" TEXT
)

5
databases/sports/.env Normal file
View File

@@ -0,0 +1,5 @@
POSTGRES_SCHEMAS=flyway
POSTGRES_DB=sports
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
ConnectionStrings__DBConnectionString="Host=sports-database:5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"

View File

@@ -0,0 +1,7 @@
flyway.url=jdbc:postgresql://sports-database:5432/sports
flyway.locations=filesystem:/flyway/sql
flyway.configFiles=filesystem:/flyway/conf
flyway.user=postgres
flyway.password=postgres
flyway.baselineVersion=0
flyway.baselineOnMigrate=true

View File

@@ -0,0 +1,25 @@
CREATE TABLE IF NOT EXISTS sport (
"id" INT,
"code" TEXT,
"link" TEXT,
"name" TEXT,
"abbreviation" TEXT,
"sortOrder" INT,
"activeStatus" TEXT
);
INSERT INTO sport VALUES
(1,'mlb','/api/v1/sports/1','Major League Baseball','MLB',11,'True'),
(11,'aaa','/api/v1/sports/11','Triple-A','AAA',101,'True'),
(12,'aax','/api/v1/sports/12','Double-A','AA',201,'True'),
(13,'afa','/api/v1/sports/13','High-A','A+',301,'True'),
(14,'afx','/api/v1/sports/14','Single-A','A',401,'True'),
(16,'rok','/api/v1/sports/16','Rookie','ROK',701,'True'),
(17,'win','/api/v1/sports/17','Winter Leagues','WIN',1301,'True'),
(8,'bbl','/api/v1/sports/8','Organized Baseball','Pros',1401,'True'),
(21,'min','/api/v1/sports/21','Minor League Baseball','Minors',1402,'True'),
(23,'ind','/api/v1/sports/23','Independent Leagues','IND',2101,'True'),
(51,'int','/api/v1/sports/51','International Baseball','INT',3501,'True'),
(509,'nae','/api/v1/sports/509','International Baseball (18U)','18U',3503,'True'),
(510,'nas','/api/v1/sports/510','International Baseball (16 and under)','16U',3505,'True'),
(22,'bbc','/api/v1/sports/22','College Baseball','College',5101,'True'),
(586,'hsb','/api/v1/sports/586','High School Baseball','H.S.',6201,'True');

5
databases/teams/.env Normal file
View File

@@ -0,0 +1,5 @@
POSTGRES_SCHEMAS=flyway
POSTGRES_DB=teams
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
ConnectionStrings__DBConnectionString="Host=teams-database:5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"

View File

@@ -0,0 +1,7 @@
flyway.url=jdbc:postgresql://teams-database:5432/teams
flyway.locations=filesystem:/flyway/sql
flyway.configFiles=filesystem:/flyway/conf
flyway.user=postgres
flyway.password=postgres
flyway.baselineVersion=0
flyway.baselineOnMigrate=true

View File

@@ -0,0 +1,56 @@
CREATE TABLE IF NOT EXISTS team (
"springLeagueId" INT,
"allStarStatus" TEXT,
"id" INT,
"name" TEXT,
"link" TEXT,
"season" INT,
"venueId" INT,
"springVenueId" INT,
"teamCode" TEXT,
"fileCode" TEXT,
"abbreviation" TEXT,
"teamName" TEXT,
"locationName" TEXT,
"firstYearOfPlay" INT,
"leagueId" INT,
"divisionId" INT,
"sportId" INT,
"shortName" TEXT,
"franchiseName" TEXT,
"clubName" TEXT,
"active" TEXT,
"venue" INT,
"springVenue" INT
);
INSERT INTO team VALUES
(114,'N',133,'Oakland Athletics','/api/v1/teams/133',2022,10,2507,'oak','oak','OAK','Athletics','Oakland',1901,103,200,1,'Oakland','Oakland','Athletics','True',NULL,NULL),
(115,'N',134,'Pittsburgh Pirates','/api/v1/teams/134',2022,31,2526,'pit','pit','PIT','Pirates','Pittsburgh',1882,104,205,1,'Pittsburgh','Pittsburgh','Pirates','True',NULL,NULL),
(114,'N',135,'San Diego Padres','/api/v1/teams/135',2022,2680,2530,'sdn','sd','SD','Padres','San Diego',1968,104,2,1,'San Diego','San Diego','Padres','True',NULL,NULL),
(114,'N',136,'Seattle Mariners','/api/v1/teams/136',2022,680,2530,'sea','sea','SEA','Mariners','Seattle',1977,103,200,1,'Seattle','Seattle','Mariners','True',NULL,NULL),
(114,'N',137,'San Francisco Giants','/api/v1/teams/137',2022,2395,2532,'sfn','sf','SF','Giants','San Francisco',1883,104,203,1,'San Francisco','San Francisco','Giants','True',NULL,NULL),
(115,'N',138,'St. Louis Cardinals','/api/v1/teams/138',2022,NULL,NULL,'sln','stl','STL','Cardinals','St. Louis',1892,104,205,1,'St. Louis','St. Louis','Cardinals','True',2889,2520),
(115,'N',139,'Tampa Bay Rays','/api/v1/teams/139',2022,12,2534,'tba','tb','TB','Rays','St. Petersburg',1996,103,201,1,'Tampa Bay','Tampa Bay','Rays','True',NULL,NULL),
(114,'N',140,'Texas Rangers','/api/v1/teams/140',2022,5325,2603,'tex','tex','TEX','Rangers','Arlington',1961,103,200,1,'Texas','Texas','Rangers','True',NULL,NULL),
(115,'N',141,'Toronto Blue Jays','/api/v1/teams/141',2022,14,2536,'tor','tor','TOR','Blue Jays','Toronto',1977,103,201,1,'Toronto','Toronto','Blue Jays','True',NULL,NULL),
(115,'N',142,'Minnesota Twins','/api/v1/teams/142',2022,3312,2862,'min','min','MIN','Twins','Minneapolis',1901,103,202,1,'Minnesota','Minnesota','Twins','True',NULL,NULL),
(115,'N',143,'Philadelphia Phillies','/api/v1/teams/143',2022,2681,2700,'phi','phi','PHI','Phillies','Philadelphia',1883,104,204,1,'Philadelphia','Philadelphia','Phillies','True',NULL,NULL),
(115,'N',144,'Atlanta Braves','/api/v1/teams/144',2022,4705,5380,'atl','atl','ATL','Braves','Atlanta',1871,104,204,1,'Atlanta','Atlanta','Braves','True',NULL,NULL),
(114,'N',145,'Chicago White Sox','/api/v1/teams/145',2022,4,3809,'cha','cws','CWS','White Sox','Chicago',1901,103,202,1,'Chi White Sox','Chicago','White Sox','True',NULL,NULL),
(115,'N',146,'Miami Marlins','/api/v1/teams/146',2022,4169,2520,'mia','mia','MIA','Marlins','Miami',1991,104,204,1,'Miami','Miami','Marlins','True',NULL,NULL),
(115,'N',147,'New York Yankees','/api/v1/teams/147',2022,3313,2523,'nya','nyy','NYY','Yankees','Bronx',1903,103,201,1,'NY Yankees','New York','Yankees','True',NULL,NULL),
(114,'N',158,'Milwaukee Brewers','/api/v1/teams/158',2022,32,2518,'mil','mil','MIL','Brewers','Milwaukee',1968,104,205,1,'Milwaukee','Milwaukee','Brewers','True',NULL,NULL),
(114,'N',108,'Los Angeles Angels','/api/v1/teams/108',2022,1,2500,'ana','ana','LAA','Angels','Anaheim',1961,103,200,1,'LA Angels','Los Angeles','Angels','True',NULL,NULL),
(114,'N',109,'Arizona Diamondbacks','/api/v1/teams/109',2022,15,4249,'ari','ari','AZ','D-backs','Phoenix',1996,104,203,1,'Arizona','Arizona','Diamondbacks','True',NULL,NULL),
(115,'N',110,'Baltimore Orioles','/api/v1/teams/110',2022,2,2508,'bal','bal','BAL','Orioles','Baltimore',1901,103,201,1,'Baltimore','Baltimore','Orioles','True',NULL,NULL),
(115,'N',111,'Boston Red Sox','/api/v1/teams/111',2022,3,4309,'bos','bos','BOS','Red Sox','Boston',1901,103,201,1,'Boston','Boston','Red Sox','True',NULL,NULL),
(114,'N',112,'Chicago Cubs','/api/v1/teams/112',2022,17,4629,'chn','chc','CHC','Cubs','Chicago',1874,104,205,1,'Chi Cubs','Chicago','Cubs','True',NULL,NULL),
(114,'N',113,'Cincinnati Reds','/api/v1/teams/113',2022,2602,3834,'cin','cin','CIN','Reds','Cincinnati',1882,104,205,1,'Cincinnati','Cincinnati','Reds','True',NULL,NULL),
(114,'N',114,'Cleveland Guardians','/api/v1/teams/114',2022,5,3834,'cle','cle','CLE','Guardians','Cleveland',1901,103,202,1,'Cleveland','Cleveland','Guardians','True',NULL,NULL),
(114,'N',115,'Colorado Rockies','/api/v1/teams/115',2022,19,4249,'col','col','COL','Rockies','Denver',1992,104,203,1,'Colorado','Colorado','Rockies','True',NULL,NULL),
(115,'N',116,'Detroit Tigers','/api/v1/teams/116',2022,2394,2511,'det','det','DET','Tigers','Detroit',1901,103,202,1,'Detroit','Detroit','Tigers','True',NULL,NULL),
(115,'N',117,'Houston Astros','/api/v1/teams/117',2022,2392,5000,'hou','hou','HOU','Astros','Houston',1962,103,200,1,'Houston','Houston','Astros','True',NULL,NULL),
(114,'N',118,'Kansas City Royals','/api/v1/teams/118',2022,7,2603,'kca','kc','KC','Royals','Kansas City',1968,103,202,1,'Kansas City','Kansas City','Royals','True',NULL,NULL),
(114,'N',119,'Los Angeles Dodgers','/api/v1/teams/119',2022,22,3809,'lan','la','LAD','Dodgers','Los Angeles',1884,104,203,1,'LA Dodgers','Los Angeles','Dodgers','True',NULL,NULL),
(115,'N',120,'Washington Nationals','/api/v1/teams/120',2022,3309,5000,'was','was','WSH','Nationals','Washington',1968,104,204,1,'Washington','Washington','Nationals','True',NULL,NULL),
(115,'N',121,'New York Mets','/api/v1/teams/121',2022,3289,2856,'nyn','nym','NYM','Mets','Flushing',1962,104,204,1,'NY Mets','New York','Mets','True',NULL,NULL);

5
databases/venues/.env Normal file
View File

@@ -0,0 +1,5 @@
POSTGRES_SCHEMAS=flyway
POSTGRES_DB=venues
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
ConnectionStrings__DBConnectionString="Host=venues-database:5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"

View File

@@ -0,0 +1,7 @@
flyway.url=jdbc:postgresql://venues-database:5432/venues
flyway.locations=filesystem:/flyway/sql
flyway.configFiles=filesystem:/flyway/conf
flyway.user=postgres
flyway.password=postgres
flyway.baselineVersion=0
flyway.baselineOnMigrate=true

View File

@@ -0,0 +1,62 @@
CREATE TABLE IF NOT EXISTS venue (
"id" INT,
"name" TEXT,
"link" TEXT,
"active" TEXT
);
INSERT INTO venue VALUES
(1,'Angel Stadium','/api/v1/venues/1','True'),
(2,'Oriole Park at Camden Yards','/api/v1/venues/2','True'),
(3,'Fenway Park','/api/v1/venues/3','True'),
(5380,'CoolToday Park','/api/v1/venues/5380','True'),
(4,'Guaranteed Rate Field','/api/v1/venues/4','True'),
(5,'Progressive Field','/api/v1/venues/5','True'),
(7,'Kauffman Stadium','/api/v1/venues/7','True'),
(5000,'The Ballpark of the Palm Beaches','/api/v1/venues/5000','True'),
(10,'Oakland Coliseum','/api/v1/venues/10','True'),
(12,'Tropicana Field','/api/v1/venues/12','True'),
(2700,'BayCare Ballpark','/api/v1/venues/2700','True'),
(14,'Rogers Centre','/api/v1/venues/14','True'),
(15,'Chase Field','/api/v1/venues/15','True'),
(17,'Wrigley Field','/api/v1/venues/17','True'),
(19,'Coors Field','/api/v1/venues/19','True'),
(4629,'Sloan Park','/api/v1/venues/4629','True'),
(22,'Dodger Stadium','/api/v1/venues/22','True'),
(4249,'Salt River Fields at Talking Stick','/api/v1/venues/4249','True'),
(31,'PNC Park','/api/v1/venues/31','True'),
(32,'American Family Field','/api/v1/venues/32','True'),
(2856,'Clover Park','/api/v1/venues/2856','True'),
(680,'T-Mobile Park','/api/v1/venues/680','True'),
(2602,'Great American Ball Park','/api/v1/venues/2602','True'),
(2603,'Surprise Stadium','/api/v1/venues/2603','True'),
(2862,'Hammond Stadium','/api/v1/venues/2862','True'),
(2735,'Muncy Bank Ballpark','/api/v1/venues/2735','True'),
(2500,'Tempe Diablo Stadium','/api/v1/venues/2500','True'),
(5445,'Field of Dreams','/api/v1/venues/5445','True'),
(4169,'loanDepot park','/api/v1/venues/4169','True'),
(2889,'Busch Stadium','/api/v1/venues/2889','True'),
(2507,'Hohokam Stadium','/api/v1/venues/2507','True'),
(2508,'Ed Smith Stadium','/api/v1/venues/2508','True'),
(5325,'Globe Life Field','/api/v1/venues/5325','True'),
(2511,'Publix Field at Joker Marchant Stadium','/api/v1/venues/2511','True'),
(4309,'JetBlue Park','/api/v1/venues/4309','True'),
(2518,'American Family Fields of Phoenix','/api/v1/venues/2518','True'),
(2520,'Roger Dean Chevrolet Stadium','/api/v1/venues/2520','True'),
(2392,'Minute Maid Park','/api/v1/venues/2392','True'),
(3289,'Citi Field','/api/v1/venues/3289','True'),
(2394,'Comerica Park','/api/v1/venues/2394','True'),
(2523,'George M. Steinbrenner Field','/api/v1/venues/2523','True'),
(2395,'Oracle Park','/api/v1/venues/2395','True'),
(2526,'LECOM Park','/api/v1/venues/2526','True'),
(3809,'Camelback Ranch','/api/v1/venues/3809','True'),
(4705,'Truist Park','/api/v1/venues/4705','True'),
(2530,'Peoria Stadium','/api/v1/venues/2530','True'),
(2532,'Scottsdale Stadium','/api/v1/venues/2532','True'),
(2534,'Charlotte Sports Park','/api/v1/venues/2534','True'),
(2536,'TD Ballpark','/api/v1/venues/2536','True'),
(3309,'Nationals Park','/api/v1/venues/3309','True'),
(3312,'Target Field','/api/v1/venues/3312','True'),
(3313,'Yankee Stadium','/api/v1/venues/3313','True'),
(2680,'Petco Park','/api/v1/venues/2680','True'),
(2681,'Citizens Bank Park','/api/v1/venues/2681','True'),
(3834,'Goodyear Ballpark','/api/v1/venues/3834','True');

25
divisions/.dockerignore Normal file
View File

@@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

3
divisions/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.vs/
[Bb]in/
[Oo]bj/

View File

@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Divisions.Queries;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace Divisions.Controllers
{
[ApiController]
[Route("api/divisions")]
public class DivisionsController : ControllerBase
{
private readonly ISender _sender;
public DivisionsController(ISender sender) => _sender = sender;
[HttpGet]
public async Task<ActionResult> GetDivisions([FromQuery] int pageNumber, [FromQuery] int pageSize)
{
var divisions = await _sender.Send(new GetDivisionsQuery
{
PageNumber = pageNumber,
PageSize = pageSize
});
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(new
{
divisions.CurrentPage,
divisions.PageSize,
divisions.TotalCount,
divisions.TotalPages
}));
return Ok(divisions);
}
[HttpGet("{id}")]
public async Task<ActionResult> GetDivisionById(int id)
{
var division = await _sender.Send(new GetDivisionByIdQuery
{
DivisionId = id
});
return Ok(division);
}
}
}

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using Divisions.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
namespace Divisions.DbContexts
{
public partial class DivisionsContext : DbContext
{
public DivisionsContext()
{
}
public DivisionsContext(DbContextOptions<DivisionsContext> options)
: base(options)
{
}
public virtual DbSet<Division> Divisions { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
optionsBuilder.UseNpgsql("Host=localhost;Port=5410;Database=divisions;Username=postgres;Password=postgres");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Division>(entity =>
{
entity.HasNoKey();
entity.ToTable("division");
entity.Property(e => e.Abbreviation).HasColumnName("abbreviation");
entity.Property(e => e.Active).HasColumnName("active");
entity.Property(e => e.HasWildcard).HasColumnName("hasWildcard");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.LeagueId).HasColumnName("leagueId");
entity.Property(e => e.Link).HasColumnName("link");
entity.Property(e => e.Name).HasColumnName("name");
entity.Property(e => e.NameShort).HasColumnName("nameShort");
entity.Property(e => e.NumPlayoffTeams).HasColumnName("numPlayoffTeams");
entity.Property(e => e.Season).HasColumnName("season");
entity.Property(e => e.SortOrder).HasColumnName("sortOrder");
entity.Property(e => e.SportId).HasColumnName("sportId");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
}

Some files were not shown because too many files have changed in this diff Show More