This commit is contained in:
Noah Spannbauer
2023-02-15 12:46:50 -06:00
parent c2d8bd1838
commit 0d5eb3e801
99 changed files with 20124 additions and 213 deletions

12
.config/dotnet-tools.json Normal file
View File

@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "6.0.12",
"commands": [
"dotnet-ef"
]
}
}
}

268
app/package-lock.json generated
View File

@@ -16,7 +16,9 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-grid-system": "^8.1.6", "react-grid-system": "^8.1.6",
"react-router-dom": "^6.6.1" "react-router-dom": "^6.6.1",
"semantic-ui-css": "^2.5.0",
"semantic-ui-react": "^2.1.4"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.12", "@babel/core": "^7.20.12",
@@ -1809,7 +1811,6 @@
"version": "7.20.13", "version": "7.20.13",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz",
"integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==",
"dev": true,
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.13.11" "regenerator-runtime": "^0.13.11"
}, },
@@ -2259,6 +2260,31 @@
"react-dom": ">=16.8.0 <19.0.0" "react-dom": ">=16.8.0 <19.0.0"
} }
}, },
"node_modules/@fluentui/react-component-event-listener": {
"version": "0.63.1",
"resolved": "https://registry.npmjs.org/@fluentui/react-component-event-listener/-/react-component-event-listener-0.63.1.tgz",
"integrity": "sha512-gSMdOh6tI3IJKZFqxfQwbTpskpME0CvxdxGM2tdglmf6ZPVDi0L4+KKIm+2dN8nzb8Ya1A8ZT+Ddq0KmZtwVQg==",
"dependencies": {
"@babel/runtime": "^7.10.4"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18",
"react-dom": "^16.8.0 || ^17 || ^18"
}
},
"node_modules/@fluentui/react-component-ref": {
"version": "0.63.1",
"resolved": "https://registry.npmjs.org/@fluentui/react-component-ref/-/react-component-ref-0.63.1.tgz",
"integrity": "sha512-8MkXX4+R3i80msdbD4rFpEB4WWq2UDvGwG386g3ckIWbekdvN9z2kWAd9OXhRGqB7QeOsoAGWocp6gAMCivRlw==",
"dependencies": {
"@babel/runtime": "^7.10.4",
"react-is": "^16.6.3"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18",
"react-dom": "^16.8.0 || ^17 || ^18"
}
},
"node_modules/@fluentui/react-focus": { "node_modules/@fluentui/react-focus": {
"version": "8.8.12", "version": "8.8.12",
"resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-8.8.12.tgz", "resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-8.8.12.tgz",
@@ -2631,6 +2657,15 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0" "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
} }
}, },
"node_modules/@popperjs/core": {
"version": "2.11.6",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@remix-run/router": { "node_modules/@remix-run/router": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.0.tgz",
@@ -2639,6 +2674,19 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/@semantic-ui-react/event-stack": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@semantic-ui-react/event-stack/-/event-stack-3.1.3.tgz",
"integrity": "sha512-FdTmJyWvJaYinHrKRsMLDrz4tTMGdFfds299Qory53hBugiDvGC0tEJf+cHsi5igDwWb/CLOgOiChInHwq8URQ==",
"dependencies": {
"exenv": "^1.2.2",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@sideway/address": { "node_modules/@sideway/address": {
"version": "4.1.4", "version": "4.1.4",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
@@ -5036,6 +5084,14 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/coffeeify": { "node_modules/coffeeify": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/coffeeify/-/coffeeify-3.0.1.tgz", "resolved": "https://registry.npmjs.org/coffeeify/-/coffeeify-3.0.1.tgz",
@@ -6870,6 +6926,11 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/exenv": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
"integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw=="
},
"node_modules/express": { "node_modules/express": {
"version": "4.18.2", "version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
@@ -8811,6 +8872,11 @@
"@sideway/pinpoint": "^2.0.0" "@sideway/pinpoint": "^2.0.0"
} }
}, },
"node_modules/jquery": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz",
"integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg=="
},
"node_modules/js-base64": { "node_modules/js-base64": {
"version": "2.6.4", "version": "2.6.4",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz",
@@ -8975,6 +9041,11 @@
"verror": "1.10.0" "verror": "1.10.0"
} }
}, },
"node_modules/keyboard-key": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/keyboard-key/-/keyboard-key-1.1.0.tgz",
"integrity": "sha512-qkBzPTi3rlAKvX7k0/ub44sqOfXeLc/jcnGGmj5c7BJpU8eDrEVPyhCvNYAaoubbsLm9uGWwQJO1ytQK1a9/dQ=="
},
"node_modules/kind-of": { "node_modules/kind-of": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -9423,8 +9494,12 @@
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"dev": true },
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
}, },
"node_modules/lodash.clonedeep": { "node_modules/lodash.clonedeep": {
"version": "4.5.0", "version": "4.5.0",
@@ -11774,6 +11849,11 @@
"react": "^18.2.0" "react": "^18.2.0"
} }
}, },
"node_modules/react-fast-compare": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
},
"node_modules/react-grid-system": { "node_modules/react-grid-system": {
"version": "8.1.8", "version": "8.1.8",
"resolved": "https://registry.npmjs.org/react-grid-system/-/react-grid-system-8.1.8.tgz", "resolved": "https://registry.npmjs.org/react-grid-system/-/react-grid-system-8.1.8.tgz",
@@ -11790,6 +11870,20 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"node_modules/react-popper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz",
"integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==",
"dependencies": {
"react-fast-compare": "^3.0.1",
"warning": "^4.0.2"
},
"peerDependencies": {
"@popperjs/core": "^2.0.0",
"react": "^16.8.0 || ^17 || ^18",
"react-dom": "^16.8.0 || ^17 || ^18"
}
},
"node_modules/react-router": { "node_modules/react-router": {
"version": "6.7.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.7.0.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.7.0.tgz",
@@ -11994,8 +12088,7 @@
"node_modules/regenerator-runtime": { "node_modules/regenerator-runtime": {
"version": "0.13.11", "version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
"dev": true
}, },
"node_modules/regenerator-transform": { "node_modules/regenerator-transform": {
"version": "0.15.1", "version": "0.15.1",
@@ -12506,6 +12599,38 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/semantic-ui-css": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/semantic-ui-css/-/semantic-ui-css-2.5.0.tgz",
"integrity": "sha512-jIWn3WXXE2uSaWCcB+gVJVRG3masIKtTMNEP2X8Aw909H2rHpXGneYOxzO3hT8TpyvB5/dEEo9mBFCitGwoj1A==",
"dependencies": {
"jquery": "x.*"
}
},
"node_modules/semantic-ui-react": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-2.1.4.tgz",
"integrity": "sha512-7CxjBoFUfH7fUvtn+SPkkIocthUD9kV3niF1mUMa9TbeyPAf2brtRCZBlT2OpHaXmkscFzGjEfhbJo9gKfotzQ==",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@fluentui/react-component-event-listener": "~0.63.0",
"@fluentui/react-component-ref": "~0.63.0",
"@popperjs/core": "^2.6.0",
"@semantic-ui-react/event-stack": "^3.1.3",
"clsx": "^1.1.1",
"keyboard-key": "^1.1.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"prop-types": "^15.7.2",
"react-is": "^16.8.6 || ^17.0.0 || ^18.0.0",
"react-popper": "^2.3.0",
"shallowequal": "^1.1.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/semver": { "node_modules/semver": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -12699,6 +12824,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
},
"node_modules/shasum": { "node_modules/shasum": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz",
@@ -14243,6 +14373,14 @@
"node": ">=12.0.0" "node": ">=12.0.0"
} }
}, },
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/watchify": { "node_modules/watchify": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/watchify/-/watchify-4.0.0.tgz", "resolved": "https://registry.npmjs.org/watchify/-/watchify-4.0.0.tgz",
@@ -16164,7 +16302,6 @@
"version": "7.20.13", "version": "7.20.13",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz",
"integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==",
"dev": true,
"requires": { "requires": {
"regenerator-runtime": "^0.13.11" "regenerator-runtime": "^0.13.11"
} }
@@ -16529,6 +16666,23 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"@fluentui/react-component-event-listener": {
"version": "0.63.1",
"resolved": "https://registry.npmjs.org/@fluentui/react-component-event-listener/-/react-component-event-listener-0.63.1.tgz",
"integrity": "sha512-gSMdOh6tI3IJKZFqxfQwbTpskpME0CvxdxGM2tdglmf6ZPVDi0L4+KKIm+2dN8nzb8Ya1A8ZT+Ddq0KmZtwVQg==",
"requires": {
"@babel/runtime": "^7.10.4"
}
},
"@fluentui/react-component-ref": {
"version": "0.63.1",
"resolved": "https://registry.npmjs.org/@fluentui/react-component-ref/-/react-component-ref-0.63.1.tgz",
"integrity": "sha512-8MkXX4+R3i80msdbD4rFpEB4WWq2UDvGwG386g3ckIWbekdvN9z2kWAd9OXhRGqB7QeOsoAGWocp6gAMCivRlw==",
"requires": {
"@babel/runtime": "^7.10.4",
"react-is": "^16.6.3"
}
},
"@fluentui/react-focus": { "@fluentui/react-focus": {
"version": "8.8.12", "version": "8.8.12",
"resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-8.8.12.tgz", "resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-8.8.12.tgz",
@@ -16826,11 +16980,25 @@
"rimraf": "^3.0.2" "rimraf": "^3.0.2"
} }
}, },
"@popperjs/core": {
"version": "2.11.6",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw=="
},
"@remix-run/router": { "@remix-run/router": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.0.tgz",
"integrity": "sha512-nwQoYb3m4DDpHTeOwpJEuDt8lWVcujhYYSFGLluC+9es2PyLjm+jjq3IeRBQbwBtPLJE/lkuHuGHr8uQLgmJRA==" "integrity": "sha512-nwQoYb3m4DDpHTeOwpJEuDt8lWVcujhYYSFGLluC+9es2PyLjm+jjq3IeRBQbwBtPLJE/lkuHuGHr8uQLgmJRA=="
}, },
"@semantic-ui-react/event-stack": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@semantic-ui-react/event-stack/-/event-stack-3.1.3.tgz",
"integrity": "sha512-FdTmJyWvJaYinHrKRsMLDrz4tTMGdFfds299Qory53hBugiDvGC0tEJf+cHsi5igDwWb/CLOgOiChInHwq8URQ==",
"requires": {
"exenv": "^1.2.2",
"prop-types": "^15.6.2"
}
},
"@sideway/address": { "@sideway/address": {
"version": "4.1.4", "version": "4.1.4",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
@@ -18740,6 +18908,11 @@
"shallow-clone": "^3.0.0" "shallow-clone": "^3.0.0"
} }
}, },
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
},
"coffeeify": { "coffeeify": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/coffeeify/-/coffeeify-3.0.1.tgz", "resolved": "https://registry.npmjs.org/coffeeify/-/coffeeify-3.0.1.tgz",
@@ -20157,6 +20330,11 @@
"pify": "^2.2.0" "pify": "^2.2.0"
} }
}, },
"exenv": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
"integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw=="
},
"express": { "express": {
"version": "4.18.2", "version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
@@ -21624,6 +21802,11 @@
"@sideway/pinpoint": "^2.0.0" "@sideway/pinpoint": "^2.0.0"
} }
}, },
"jquery": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz",
"integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg=="
},
"js-base64": { "js-base64": {
"version": "2.6.4", "version": "2.6.4",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz",
@@ -21752,6 +21935,11 @@
"verror": "1.10.0" "verror": "1.10.0"
} }
}, },
"keyboard-key": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/keyboard-key/-/keyboard-key-1.1.0.tgz",
"integrity": "sha512-qkBzPTi3rlAKvX7k0/ub44sqOfXeLc/jcnGGmj5c7BJpU8eDrEVPyhCvNYAaoubbsLm9uGWwQJO1ytQK1a9/dQ=="
},
"kind-of": { "kind-of": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -22058,8 +22246,12 @@
"lodash": { "lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"dev": true },
"lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
}, },
"lodash.clonedeep": { "lodash.clonedeep": {
"version": "4.5.0", "version": "4.5.0",
@@ -23869,6 +24061,11 @@
"scheduler": "^0.23.0" "scheduler": "^0.23.0"
} }
}, },
"react-fast-compare": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
},
"react-grid-system": { "react-grid-system": {
"version": "8.1.8", "version": "8.1.8",
"resolved": "https://registry.npmjs.org/react-grid-system/-/react-grid-system-8.1.8.tgz", "resolved": "https://registry.npmjs.org/react-grid-system/-/react-grid-system-8.1.8.tgz",
@@ -23882,6 +24079,15 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"react-popper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz",
"integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==",
"requires": {
"react-fast-compare": "^3.0.1",
"warning": "^4.0.2"
}
},
"react-router": { "react-router": {
"version": "6.7.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.7.0.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.7.0.tgz",
@@ -24049,8 +24255,7 @@
"regenerator-runtime": { "regenerator-runtime": {
"version": "0.13.11", "version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
"dev": true
}, },
"regenerator-transform": { "regenerator-transform": {
"version": "0.15.1", "version": "0.15.1",
@@ -24417,6 +24622,34 @@
"node-forge": "^1" "node-forge": "^1"
} }
}, },
"semantic-ui-css": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/semantic-ui-css/-/semantic-ui-css-2.5.0.tgz",
"integrity": "sha512-jIWn3WXXE2uSaWCcB+gVJVRG3masIKtTMNEP2X8Aw909H2rHpXGneYOxzO3hT8TpyvB5/dEEo9mBFCitGwoj1A==",
"requires": {
"jquery": "x.*"
}
},
"semantic-ui-react": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-2.1.4.tgz",
"integrity": "sha512-7CxjBoFUfH7fUvtn+SPkkIocthUD9kV3niF1mUMa9TbeyPAf2brtRCZBlT2OpHaXmkscFzGjEfhbJo9gKfotzQ==",
"requires": {
"@babel/runtime": "^7.10.5",
"@fluentui/react-component-event-listener": "~0.63.0",
"@fluentui/react-component-ref": "~0.63.0",
"@popperjs/core": "^2.6.0",
"@semantic-ui-react/event-stack": "^3.1.3",
"clsx": "^1.1.1",
"keyboard-key": "^1.1.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"prop-types": "^15.7.2",
"react-is": "^16.8.6 || ^17.0.0 || ^18.0.0",
"react-popper": "^2.3.0",
"shallowequal": "^1.1.0"
}
},
"semver": { "semver": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -24589,6 +24822,11 @@
"kind-of": "^6.0.2" "kind-of": "^6.0.2"
} }
}, },
"shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
},
"shasum": { "shasum": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz",
@@ -25772,6 +26010,14 @@
"rxjs": "^7.8.0" "rxjs": "^7.8.0"
} }
}, },
"warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"requires": {
"loose-envify": "^1.0.0"
}
},
"watchify": { "watchify": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/watchify/-/watchify-4.0.0.tgz", "resolved": "https://registry.npmjs.org/watchify/-/watchify-4.0.0.tgz",

View File

@@ -69,6 +69,8 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-grid-system": "^8.1.6", "react-grid-system": "^8.1.6",
"react-router-dom": "^6.6.1" "react-router-dom": "^6.6.1",
"semantic-ui-css": "^2.5.0",
"semantic-ui-react": "^2.1.4"
} }
} }

View File

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

View File

@@ -1,38 +1,44 @@
import React from 'react'; import React from 'react';
import { Routes, Route, useLocation } from 'react-router-dom'; import styles from './App.module.scss'
import { Container, Row, Col } from 'react-grid-system'; import { Routes, Route } from 'react-router-dom';
import { initializeIcons } from '@fluentui/font-icons-mdl2'; import { initializeIcons } from '@fluentui/font-icons-mdl2';
import Menu from '../menu/Menu'; import { Grid, Sidebar } from 'semantic-ui-react';
import Header from '../header/Header';
import Footer from '../footer/Footer';
import Navigation from '../navigation/Navigation';
import Teams from '../teams/Teams';
import Venues from '../venues/Venues'; import Venues from '../venues/Venues';
import { useSidebarContext } from '../../hooks/sidebarContext/UseSidebarContext';
initializeIcons(); initializeIcons();
const App: React.FC<unknown> = () => { const App: React.FC<unknown> = () => {
const location = useLocation(); const sidebarContext = useSidebarContext();
return ( return (
<Container fluid={true} style={{ padding: '0' }}> <Sidebar.Pushable>
<Row> <Sidebar.Pusher dimmed={sidebarContext.state.sidebar?.visible}>
<Col sm={6}>Goodbye!</Col> <Header />
<Col sm={6}>Hello!</Col> <Grid columns={16}>
</Row> <Grid.Row>
<Row> <Grid.Column width={3}>
<Col sm={3}> <Navigation />
<Menu </Grid.Column>
selectedKey={ <Grid.Column width={12}>
location.pathname === '/' <div className={styles.content}>
? 'home' <Routes>
: location.pathname.replace('/', '') <Route path='teams' element={<Teams />} />
} </Routes>
/>
</Col>
<Col>
<Routes> <Routes>
<Route path='venues' element={<Venues />} /> <Route path='venues' element={<Venues />} />
</Routes> </Routes>
</Col> </div>
</Row> </Grid.Column>
</Container> </Grid.Row>
</Grid>
<Footer />
</Sidebar.Pusher>
</Sidebar.Pushable>
); );
}; };

View File

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

View File

@@ -0,0 +1,22 @@
import React from 'react';
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>
)
}
export default Footer;

View File

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

View File

@@ -0,0 +1,22 @@
import React from 'react';
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>
)
}
export default Header;

View File

@@ -1,3 +0,0 @@
export interface IMenuProps {
selectedKey: string;
}

View File

@@ -1,29 +0,0 @@
import React from 'react';
import { IMenuProps } from './IMenuProps';
import {
Nav,
INavLinkGroup
} from '@fluentui/react/lib/Nav';
const Menu: React.FC<IMenuProps> = (props: IMenuProps) => {
const navLinkGroups: INavLinkGroup[] = [
{
links: [
{
name: 'Home',
url: 'http://localhost:3000',
key: 'home'
},
{
name: 'Venues',
url: 'http://localhost:3000/venues',
key: 'venues'
}
]
}
];
return <Nav groups={navLinkGroups} selectedKey={props.selectedKey} />;
};
export default Menu;

View File

@@ -0,0 +1,3 @@
export interface INavigationState {
activeLink: string;
}

View File

@@ -0,0 +1,75 @@
import React, { Component, useEffect, useReducer } from 'react';
// import { IMenuProps } from './IMenuProps';
// import {
// Nav,
// INavLinkGroup
// } from '@fluentui/react/lib/Nav';
import { Menu } from 'semantic-ui-react';
import { initialState, reducer } from './reducer';
import { useLocation, useNavigate } from 'react-router-dom';
// const Menu: React.FC<IMenuProps> = (props: IMenuProps) => {
// const navLinkGroups: INavLinkGroup[] = [
// {
// links: [
// {
// name: 'Home',
// url: 'http://localhost:3000',
// key: 'home'
// },
// {
// name: 'Venues',
// url: 'http://localhost:3000/venues',
// key: 'venues'
// }
// ]
// }
// ];
// return <Nav groups={navLinkGroups} selectedKey={props.selectedKey} />;
// };
// export default Menu;
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 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('/', '')
dispatch({ type: 'SET_ACTIVE_MENU_ITEM', payload: activeLink });
}, [])
return (
<Menu
pointing
secondary
vertical
>
{menuItems.map((menuItem: string) => {
return (
<Menu.Item
key={menuItem}
name={menuItem}
active={state.activeLink === menuItem}
onClick={onMenuItemClicked}
/>
)
})}
</Menu>
)
}
export default Navigation;

View File

@@ -0,0 +1,19 @@
import { INavigationState } from "./INavigationState"
export type Action =
| { type: 'SET_ACTIVE_MENU_ITEM', payload: string }
export const initialState: INavigationState = {
activeLink: ''
}
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,13 @@
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;
}

View File

@@ -0,0 +1,135 @@
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_TEAMS } from '../../constants/graphQL';
import { INotification } from '../../models/INotification';
import { Button, Grid, Header, Icon, Loader, Pagination, Table } from 'semantic-ui-react'
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 });
}
useEffect(() => {
if (data) {
dispatch({
type: 'SET_ITEMS',
payload: {
items: data.teams.results,
totalItemCount: data.teams.totalItemCount,
totalPageCount: data.teams.totalPageCount
}
})
}
}, [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={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>
)
}
export default Teams;

View File

@@ -0,0 +1,73 @@
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 };
export const initialState: ITeamsState = {
items: [],
totalItemCount: null,
totalPageCount: null,
currentPageNumber: 1,
pageSize: 10,
selectedItems: [],
commandBarItems: [],
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
};
}
}
}

View File

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

View File

@@ -0,0 +1,33 @@
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';
const VenueDetails: React.FC<IVenueDetailsProps> = ({ venueId }: IVenueDetailsProps) => {
const { data, loading, error } = useQuery(GET_VENUE_BY_ID, {
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>
)
}
export default VenueDetails;

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import AppContextProvider from '../../context/AppContextProvider'; import AppContextProvider from '../../context/app/AppContextProvider';
import Venues from './Venues'; import Venues from './Venues';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import { GET_VENUES } from '../../constants/graphQL'; import { GET_VENUES } from '../../constants/graphQL';

View File

@@ -1,28 +1,18 @@
import React, { useEffect, useReducer } from 'react'; import React, { SyntheticEvent, useEffect, useReducer } from 'react';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import {
CommandBar,
ICommandBarItemProps
} from '@fluentui/react/lib/CommandBar';
import {
DetailsList,
IColumn,
Selection,
SelectionMode
} from '@fluentui/react/lib/DetailsList';
import { MessageBar, MessageBarType } from '@fluentui/react/lib/MessageBar'; import { MessageBar, MessageBarType } from '@fluentui/react/lib/MessageBar';
import { Panel } from '@fluentui/react/lib/Panel';
import Paginator from '../paginator/Paginator';
import { Spinner, SpinnerSize } from '@fluentui/react/lib/Spinner';
import { initialState, reducer } from './reducer'; import { initialState, reducer } from './reducer';
import { useAppContext } from '../../hooks/appContext/UseAppContext'; import { useAppContext } from '../../hooks/appContext/UseAppContext';
import { useSidebarContext } from '../../hooks/sidebarContext/UseSidebarContext';
import { GET_VENUES } from '../../constants/graphQL'; import { GET_VENUES } from '../../constants/graphQL';
import { INotification } from '../../models/INotification'; import { INotification } from '../../models/INotification';
import { IVenue } from '../../models/IVenue'; import { Button, Grid, Header, Icon, Loader, Pagination, Sidebar, Table } from 'semantic-ui-react';
import VenueDetails from '../venueDetails/VenueDetails';
const Venues: React.FC<unknown> = () => { const Venues: React.FC<unknown> = () => {
const [state, dispatch] = useReducer(reducer, initialState); const [state, dispatch] = useReducer(reducer, initialState);
const appContext = useAppContext(); const appContext = useAppContext();
const sidebarContext = useSidebarContext();
const currentPageNumber = state.currentPageNumber; const currentPageNumber = state.currentPageNumber;
const pageSize = state.pageSize; const pageSize = state.pageSize;
const { data, loading, error, refetch } = useQuery(GET_VENUES, { const { data, loading, error, refetch } = useQuery(GET_VENUES, {
@@ -31,24 +21,11 @@ const Venues: React.FC<unknown> = () => {
pageSize: pageSize pageSize: pageSize
} }
}); });
const columns: IColumn[] = [
{
key: 'name',
name: 'Name',
fieldName: 'name',
minWidth: 100,
isResizable: true
}
];
const selection: Selection = new Selection({ const columns: string[] = [
onSelectionChanged: () => 'Name',
dispatch({ type: 'SET_SELECTED_ITEM', payload: selection.getSelection() }) ''
}); ]
const onIsPanelOpen = () => {
dispatch({ type: 'SET_IS_PANEL_OPEN', payload: !state.isPanelOpen });
};
const onDismissMessageBar = () => { const onDismissMessageBar = () => {
const notification: INotification = { const notification: INotification = {
@@ -59,32 +36,21 @@ const Venues: React.FC<unknown> = () => {
appContext.dispatch({ type: 'SET_NOTIFICATION', payload: notification }); appContext.dispatch({ type: 'SET_NOTIFICATION', payload: notification });
}; };
const onPageChange = (newCurrentPage: number) => { const onPageChange = (event: SyntheticEvent, data: any) => {
console.log(newCurrentPage); const newCurrentPage: number = data.activePage;
dispatch({ type: 'SET_CURRENT_PAGE_NUMBER', payload: newCurrentPage }); dispatch({ type: 'SET_CURRENT_PAGE_NUMBER', payload: newCurrentPage });
refetch({
pageNumber: newCurrentPage,
pageSize: pageSize
});
}; };
useEffect(() => { const onDetails = (id: number) => {
dispatch({ sidebarContext.dispatch({ type: 'SET_SIDEBAR', payload: { sidebar: { visible: true, animation: 'overlay', width: 'very wide', direction: 'right' }, content: <VenueDetails venueId={id} />}})
type: 'SET_COMMAND_BAR_ITEMS',
payload: [
{
key: 'openItem',
text: 'Open',
iconProps: { iconName: 'OpenPane' },
onClick: onIsPanelOpen,
disabled: true
} }
]
});
}, []);
useEffect(() => { useEffect(() => {
console.log(JSON.stringify(data)); console.log(sidebarContext)
}, [sidebarContext])
useEffect(() => {
if (data) { if (data) {
dispatch({ dispatch({
type: 'SET_ITEMS', type: 'SET_ITEMS',
@@ -98,6 +64,7 @@ const Venues: React.FC<unknown> = () => {
}, [data]); }, [data]);
useEffect(() => { useEffect(() => {
console.log(error);
const notification: INotification = { const notification: INotification = {
messageBarType: MessageBarType.error, messageBarType: MessageBarType.error,
message: error?.message, message: error?.message,
@@ -109,26 +76,22 @@ const Venues: React.FC<unknown> = () => {
}, [error]); }, [error]);
useEffect(() => { useEffect(() => {
if (state.selectedItems.length > 0 && state.commandBarItems.length > 0) { refetch({
const newCommandBarItems = state.commandBarItems.map( pageNumber: currentPageNumber,
(commandBarItem: ICommandBarItemProps) => { pageSize: pageSize
return { });
...commandBarItem, }, [currentPageNumber])
disabled: false
};
}
);
dispatch({ type: 'SET_COMMAND_BAR_ITEMS', payload: newCommandBarItems });
}
}, [state.selectedItems]);
return ( return (
<div> <Grid columns={1}>
{loading && <Spinner size={SpinnerSize.large} />} <Grid.Row>
{!loading && ( <Header as='h1'>
<React.Fragment> Venues
</Header>
</Grid.Row>
{appContext.state.notification?.isVisible && ( {appContext.state.notification?.isVisible && (
<Grid.Row>
<Grid.Column>
<MessageBar <MessageBar
messageBarType={appContext.state.notification?.messageBarType} messageBarType={appContext.state.notification?.messageBarType}
isMultiline={appContext.state.notification?.isMultiline} isMultiline={appContext.state.notification?.isMultiline}
@@ -136,41 +99,74 @@ const Venues: React.FC<unknown> = () => {
> >
{appContext.state.notification?.message} {appContext.state.notification?.message}
</MessageBar> </MessageBar>
</Grid.Column>
</Grid.Row>
)} )}
<CommandBar items={state.commandBarItems} /> <Grid.Row>
<DetailsList <Grid.Column>
columns={columns} {loading && (
items={state.items} <Loader size='massive' active={loading}>Loading...</Loader>
selection={selection} )}
selectionMode={SelectionMode.single} {!loading &&
selectionPreservedOnEmptyClick={true} <Table selectable>
/> <Table.Header>
<Paginator <Table.Row>
totalPageCount={data.venues.totalPageCount} {columns.map((column: string) => {
currentPage={state.currentPageNumber}
pageSize={state.pageSize}
onPageChange={onPageChange}
/>
<Panel
headerText='Venue'
isOpen={state.isPanelOpen}
onDismiss={onIsPanelOpen}
>
{state.selectedItems.length > 0 &&
state.selectedItems.map((item: IVenue) => {
return ( return (
<React.Fragment> <Table.HeaderCell>
<p>{item.name}</p> {column}
<p>{item.id}</p> </Table.HeaderCell>
<p>{item.link}</p> )
<p>{item.active}</p>
</React.Fragment>
);
})} })}
</Panel> </Table.Row>
</React.Fragment> </Table.Header>
)} <Table.Body>
</div> {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?.venues?.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>
); );
}; };

View File

@@ -1,20 +1,72 @@
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
export const GET_VENUES = gql` export const GET_TEAMS = gql`
query GetVenues($pageNumber: Int, $pageSize: Int) { query GetTeams($pageNumber: Int!, $pageSize: Int!) {
venues(pageNumber: $pageNumber, pageSize: $pageSize) { teams(pageNumber: $pageNumber, pageSize: $pageSize) {
currentPage
errors
message
pageSize pageSize
succeeded message
totalItemCount errors
currentPage
totalPageCount totalPageCount
totalItemCount
succeeded
results { results {
active
id
link
name name
link
id
active
}
}
}
`;
export const GET_TEAM = gql`
query GetTeam($id: Int!) {
teamById(id: $id) {
message
errors
succeeded
results {
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
}
}
}
`;
export const GET_VENUE_BY_ID = gql`
query GetVenue($id: Int!) {
venueById(id: $id) {
message
errors
succeeded
results {
name
link
id
active
} }
} }
} }

View File

@@ -1,5 +0,0 @@
import { INotification } from '../models/INotification';
export interface IAppContextState {
notification: INotification | undefined;
}

View File

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

View File

@@ -1,8 +1,10 @@
import { MessageBarType } from '@fluentui/react'; import { MessageBarType } from '@fluentui/react';
import { INotification } from '../models/INotification'; import { INotification } from '../../models/INotification';
import { IAppContextState } from './IAppContextState'; import { IAppContextState } from './IAppContextState';
export type Action = { type: 'SET_NOTIFICATION'; payload: INotification }; export type Action =
| { type: 'SET_NOTIFICATION'; payload: INotification }
| { type: 'SET_IS_SIDEBAR_VISIBLE', payload: boolean };
export const initialState: IAppContextState = { export const initialState: IAppContextState = {
notification: { notification: {
@@ -10,7 +12,8 @@ export const initialState: IAppContextState = {
message: 'Blah blah blah', message: 'Blah blah blah',
isMultiline: false, isMultiline: false,
isVisible: true isVisible: true
} },
isSidebarVisible: false
}; };
export const reducer = ( export const reducer = (
@@ -24,5 +27,14 @@ export const reducer = (
notification: action.payload notification: action.payload
}; };
} }
case 'SET_IS_SIDEBAR_VISIBLE': {
return {
...state,
isSidebarVisible: action.payload
}
}
default: {
return state;
}
} }
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,53 @@
import React, { useEffect, useMemo, useReducer } from 'react';
import styles from './Sidebar.module.scss';
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 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>
)
}
export default SidebarContextProvider;

View File

@@ -0,0 +1,52 @@
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 const initialState: ISidebarContextState = {
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;
}
}
}

View File

@@ -1,5 +1,5 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { AppContext } from '../../context/AppContext'; import { AppContext } from '../../context/app/AppContext';
export const useAppContext = () => { export const useAppContext = () => {
const { state, dispatch } = useContext(AppContext); const { state, dispatch } = useContext(AppContext);

View File

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

View File

@@ -2,11 +2,13 @@ import React from 'react';
import { createRoot, Root } from 'react-dom/client'; import { createRoot, Root } from 'react-dom/client';
import { BrowserRouter as Router } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom';
import { ApolloClient, InMemoryCache, ApolloProvider, NormalizedCacheObject } from '@apollo/client'; import { ApolloClient, InMemoryCache, ApolloProvider, NormalizedCacheObject } from '@apollo/client';
import AppContextProvider from './context/AppContextProvider'; import AppContextProvider from './context/app/AppContextProvider';
import App from './components/app/App'; import App from './components/app/App';
import 'semantic-ui-css/semantic.min.css';
import SidebarContextProvider from './context/sidebar/SidebarContextProvider';
const client: ApolloClient<NormalizedCacheObject> = new ApolloClient({ const client: ApolloClient<NormalizedCacheObject> = new ApolloClient({
uri: 'http://localhost:8080/v1/graphql', uri: 'http://localhost:8081/graphql',
cache: new InMemoryCache() cache: new InMemoryCache()
}); });
@@ -14,12 +16,14 @@ const container = document.getElementById('app-root') as HTMLElement;
const root: Root = createRoot(container); const root: Root = createRoot(container);
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<AppContextProvider>
<ApolloProvider client={client}> <ApolloProvider client={client}>
<AppContextProvider>
<SidebarContextProvider>
<Router> <Router>
<App /> <App />
</Router> </Router>
</ApolloProvider> </SidebarContextProvider>
</AppContextProvider> </AppContextProvider>
</ApolloProvider>
</React.StrictMode> </React.StrictMode>
); );

View File

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

23
app/src/models/ITeam.ts Normal file
View File

@@ -0,0 +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;
}

View File

@@ -20,10 +20,14 @@ module.exports = {
}, },
use: 'babel-loader', use: 'babel-loader',
}, },
{
test: /\.(css)$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{ {
test: /\.(s(a|c)ss)$/, test: /\.(s(a|c)ss)$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
} },
] ]
}, },
devServer: { devServer: {

View File

@@ -28,6 +28,25 @@ services:
depends_on: depends_on:
- postgres - postgres
graphql:
container_name: graphql
image:
teams:
container_name: teams
build:
context: ./teams
dockerfile: Dockerfile
ports:
- "5520:80"
env_file:
- ./database/.env
restart: on-failure
networks:
- mlbgameday
depends_on:
- postgres
venues: venues:
container_name: venues container_name: venues
build: build:
@@ -50,6 +69,7 @@ services:
- "8080:8080" - "8080:8080"
depends_on: depends_on:
- postgres - postgres
- teams
- venues - venues
restart: on-failure restart: on-failure
environment: environment:

25
graphql/.eslintrc.js Normal file
View File

@@ -0,0 +1,25 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};

35
graphql/.gitignore vendored Normal file
View File

@@ -0,0 +1,35 @@
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

4
graphql/.prettierrc Normal file
View File

@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

8
graphql/Dockerfile Normal file
View File

@@ -0,0 +1,8 @@
FROM node:16.19.0-alpine3.17
WORKDIR /user/src/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 8080
CMD ["node", "dist/main"]

73
graphql/README.md Normal file
View File

@@ -0,0 +1,73 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
```bash
$ npm install
```
## Running the app
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Test
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](LICENSE).

8
graphql/nest-cli.json Normal file
View File

@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

16739
graphql/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

77
graphql/package.json Normal file
View File

@@ -0,0 +1,77 @@
{
"name": "graphql",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/apollo": "^10.1.7",
"@nestjs/axios": "^1.0.1",
"@nestjs/common": "^9.0.0",
"@nestjs/core": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"axios": "^1.3.1",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/graphql": "^10.1.7",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@types/express": "^4.17.13",
"@types/jest": "29.2.4",
"@types/node": "18.11.18",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"apollo-server-express": "^3.11.1",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"graphql": "^16.6.0",
"graphql-tools": "^8.3.17",
"jest": "29.3.1",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "29.0.3",
"ts-loader": "^9.2.3",
"ts-morph": "^17.0.1",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.1.1",
"typescript": "^4.7.4"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});

View File

@@ -0,0 +1,12 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}

17
graphql/src/app.module.ts Normal file
View File

@@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { TeamModule } from './teams/team.module';
import { VenueModule } from './venues/venue.module';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
typePaths: ['./**/*.graphql'],
driver: ApolloDriver
}),
TeamModule,
VenueModule,
],
})
export class AppModule {}

View File

@@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}

View File

@@ -0,0 +1,10 @@
"use strict";
exports.__esModule = true;
var graphql_1 = require("@nestjs/graphql");
var path_1 = require("path");
var definitionsFactory = new graphql_1.GraphQLDefinitionsFactory();
definitionsFactory.generate({
typePaths: ['./src/**/*.graphql'],
path: (0, path_1.join)(process.cwd(), 'src/graphql.ts'),
outputAs: 'class'
});

View File

@@ -0,0 +1,10 @@
import { GraphQLDefinitionsFactory } from '@nestjs/graphql';
import { join } from 'path';
const definitionsFactory = new GraphQLDefinitionsFactory();
definitionsFactory.generate({
typePaths: ['./src/**/*.graphql'],
path: join(process.cwd(), 'src/graphql.ts'),
outputAs: 'class'
})

88
graphql/src/graphql.ts Normal file
View File

@@ -0,0 +1,88 @@
/*
* -------------------------------------------------------
* THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
* -------------------------------------------------------
*/
/* tslint:disable */
/* eslint-disable */
export class Team {
id?: Nullable<string>;
name?: Nullable<string>;
abbreviation?: Nullable<string>;
active?: Nullable<string>;
allStarStatus?: Nullable<string>;
clubName?: Nullable<string>;
divisionId?: Nullable<number>;
fileCode?: Nullable<string>;
firstYearOfPlay?: Nullable<number>;
franchiseName?: Nullable<string>;
leagueId?: Nullable<number>;
link?: Nullable<string>;
locationName?: Nullable<string>;
season?: Nullable<string>;
shortName?: Nullable<string>;
springLeagueId?: Nullable<number>;
springVenueId?: Nullable<number>;
sportId?: Nullable<number>;
teamCode?: Nullable<string>;
teamName?: Nullable<string>;
venueId?: Nullable<number>;
}
export class TeamsPagedResponse {
currentPage?: Nullable<number>;
errors?: Nullable<string>;
message?: Nullable<string>;
pageSize?: Nullable<number>;
results?: Nullable<Nullable<Team>[]>;
succeeded?: Nullable<boolean>;
totalItemCount?: Nullable<number>;
totalPageCount?: Nullable<number>;
}
export class TeamResponse {
errors?: Nullable<string>;
message?: Nullable<string>;
results: Team;
succeeded: boolean;
}
export abstract class IQuery {
abstract teams(pageNumber: number, pageSize: number): TeamsPagedResponse | Promise<TeamsPagedResponse>;
abstract teamById(id: number): TeamResponse | Promise<TeamResponse>;
abstract venues(pageNumber: number, pageSize: number): VenuesPagedResponse | Promise<VenuesPagedResponse>;
abstract venueById(id: number): VenueResponse | Promise<VenueResponse>;
}
export class Venue {
active: string;
id: number;
link: string;
name: string;
}
export class VenuesPagedResponse {
currentPage: number;
errors?: Nullable<string>;
message?: Nullable<string>;
pageSize: number;
results: Nullable<Venue>[];
succeeded: boolean;
totalItemCount: number;
totalPageCount: number;
}
export class VenueResponse {
errors?: Nullable<string>;
message?: Nullable<string>;
results: Venue;
succeeded: boolean;
}
type Nullable<T> = T | null;

8
graphql/src/main.ts Normal file
View File

@@ -0,0 +1,8 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(8081);
}
bootstrap();

View File

@@ -0,0 +1,54 @@
type Team {
id: String
name: String
abbreviation: String
active: String
allStarStatus: String
clubName: String
divisionId: Int
fileCode: String
firstYearOfPlay: Int
franchiseName: String
leagueId: Int
link: String
locationName: String
season: String
shortName: String
springLeagueId: Int
springVenueId: Int
sportId: Int
teamCode: String
teamName: String
venueId: Int
}
type TeamsPagedResponse {
currentPage: Int
errors: String
message: String
pageSize: Int
results: [Team]
succeeded: Boolean
totalItemCount: Int
totalPageCount: Int
}
type TeamResponse {
errors: String
message: String
results: Team!
succeeded: Boolean!
}
type Query {
teams(
pageNumber: Int!
pageSize: Int!
): TeamsPagedResponse!
}
type Query {
teamById(
id: Int!
): TeamResponse!
}

View File

@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
import { TeamResolvers } from './team.resolvers';
import { TeamService } from './team.service';
@Module({
imports: [HttpModule],
providers: [TeamService, TeamResolvers]
})
export class TeamModule {}

View File

@@ -0,0 +1,18 @@
import { Args, Resolver, Query } from '@nestjs/graphql';
import { TeamService } from './team.service';
import { Team } from '../graphql';
@Resolver('Team')
export class TeamResolvers {
constructor (private readonly teamService: TeamService) {}
@Query()
async teams(@Args('pageNumber') pageNumber: number, @Args('pageSize') pageSize: number): Promise<Team[]>{
return this.teamService.findAll(pageNumber, pageSize);
}
@Query()
async teamById(@Args('id') id: number): Promise<Team> {
return this.teamService.findById(id);
}
}

View File

@@ -0,0 +1,23 @@
import { HttpService } from '@nestjs/axios';
import { Injectable } from '@nestjs/common';
import { Team } from 'src/graphql';
import { AxiosResponse } from 'axios';
@Injectable()
export class TeamService {
constructor (private readonly httpService: HttpService) {}
private readonly teams: Team[] = [];
async findAll(pageNumber: number, pageSize: number): Promise<Team[]> {
const response: AxiosResponse = await this.httpService.axiosRef.get(`http://localhost:5520/api/teams?pageNumber=${pageNumber}&pageSize=${pageSize}`);
return response.data;
}
async findById(id: number): Promise<Team> {
const response: AxiosResponse = await this.httpService.axiosRef.get(`http://localhost:5520/api/teams/${id}`);
return response.data
}
}

View File

@@ -0,0 +1,37 @@
type Venue {
active: String!
id: Int!
link: String!
name: String!
}
type VenuesPagedResponse {
currentPage: Int!
errors: String
message: String
pageSize: Int!
results: [Venue]!
succeeded: Boolean!
totalItemCount: Int!
totalPageCount: Int!
}
type VenueResponse {
errors: String
message: String
results: Venue!
succeeded: Boolean!
}
type Query {
venues(
pageNumber: Int!
pageSize: Int!
): VenuesPagedResponse!
}
type Query {
venueById(
id: Int!
): VenueResponse!
}

View File

@@ -0,0 +1,11 @@
import { Module } from "@nestjs/common";
import { HttpModule } from '@nestjs/axios';
import { VenueResolvers } from './venue.resolvers';
import { VenueService } from './venue.service';
@Module({
imports: [HttpModule],
providers: [VenueService, VenueResolvers]
})
export class VenueModule {}

View File

@@ -0,0 +1,18 @@
import { Resolver, Query, Args } from "@nestjs/graphql";
import { VenueService } from "./venue.service";
import { Venue } from '../graphql';
@Resolver('Venue')
export class VenueResolvers {
constructor (private readonly venueService: VenueService) {}
@Query()
async venues(@Args('pageNumber') pageNumber: number, @Args('pageSize') pageSize: number): Promise<Venue[]> {
return this.venueService.findAll(pageNumber, pageSize);
}
@Query()
async venueById(@Args('id') id: number): Promise<Venue> {
return this.venueService.findById(id);
}
}

View File

@@ -0,0 +1,23 @@
import { HttpService } from "@nestjs/axios";
import { Injectable } from "@nestjs/common";
import { Venue } from '../graphql';
import { AxiosResponse } from 'axios';
@Injectable()
export class VenueService {
constructor (private readonly httpService: HttpService) {}
private readonly venues: Venue[] = [];
async findAll(pageNumber: number, pageSize: number): Promise<Venue[]> {
const response: AxiosResponse = await this.httpService.axiosRef.get(`http://localhost:5510/api/venues?pageNumber=${pageNumber}&pageSize=${pageSize}`);
return response.data;
}
async findById(id: number): Promise<Venue> {
const response: AxiosResponse = await this.httpService.axiosRef.get(`http://localhost:5510/api/venues/${id}`);
return response.data;
}
}

View File

@@ -0,0 +1,24 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});

View File

@@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

21
graphql/tsconfig.json Normal file
View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}

View File

@@ -1,3 +1,16 @@
type Query {
teamById(
id: Int!
): TeamByIdResponse
}
type Query {
teams(
pageNumber: Int
pageSize: Int
): TeamsPagedResponse
}
type Query { type Query {
venueById( venueById(
id: Int! id: Int!
@@ -52,3 +65,45 @@ type VenueByIdResponse {
succeeded: Boolean succeeded: Boolean
} }
type TeamsPagedResponse {
currentPage: Int
errors: String
message: String
pageSize: Int
results: [Team]
succeeded: Boolean
totalItemCount: Int
totalPageCount: Int
}
type Team {
id: String
name: String
abbreviation: String
active: String
allStarStatus: String
clubName: String
divisionId: Int
fileCode: String
firstYearOfPlay: Int
franchiseName: String
leagueId: Int
link: String
locationName: String
season: String
shortName: String
springLeagueId: Int
springVenueId: Int
sportId: Int
teamCode: String
teamName: String
venueId: Int
}
type TeamByIdResponse {
errors: String
message: String
results: Team
succeeded: Boolean
}

View File

@@ -1,4 +1,34 @@
actions: actions:
- name: teamById
definition:
kind: ""
handler: http://teams
request_transform:
method: GET
query_params: {}
request_headers:
add_headers: {}
remove_headers:
- content-type
template_engine: Kriti
url: '{{$base_url}}/api/teams/{{$body.input.id}}'
version: 2
- name: teams
definition:
kind: ""
handler: http://teams
request_transform:
method: GET
query_params:
pageNumber: '{{$body.input.pageNumber}}'
pageSize: '{{$body.input.pageSize}}'
request_headers:
add_headers: {}
remove_headers:
- content-type
template_engine: Kriti
url: '{{$base_url}}/api/teams'
version: 2
- name: venueById - name: venueById
definition: definition:
kind: "" kind: ""
@@ -39,4 +69,7 @@ custom_types:
- name: VenuesPagedResponse - name: VenuesPagedResponse
- name: VenueResponse - name: VenueResponse
- name: VenueByIdResponse - name: VenueByIdResponse
- name: TeamsPagedResponse
- name: Team
- name: TeamByIdResponse
scalars: [] scalars: []

462
package-lock.json generated Normal file
View File

@@ -0,0 +1,462 @@
{
"name": "mlb-gameday-dotnet-react",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"dependencies": {
"semantic-ui-css": "^2.5.0",
"semantic-ui-react": "^2.1.4"
}
},
"node_modules/@babel/runtime": {
"version": "7.20.13",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz",
"integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==",
"dependencies": {
"regenerator-runtime": "^0.13.11"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@fluentui/react-component-event-listener": {
"version": "0.63.1",
"resolved": "https://registry.npmjs.org/@fluentui/react-component-event-listener/-/react-component-event-listener-0.63.1.tgz",
"integrity": "sha512-gSMdOh6tI3IJKZFqxfQwbTpskpME0CvxdxGM2tdglmf6ZPVDi0L4+KKIm+2dN8nzb8Ya1A8ZT+Ddq0KmZtwVQg==",
"dependencies": {
"@babel/runtime": "^7.10.4"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18",
"react-dom": "^16.8.0 || ^17 || ^18"
}
},
"node_modules/@fluentui/react-component-ref": {
"version": "0.63.1",
"resolved": "https://registry.npmjs.org/@fluentui/react-component-ref/-/react-component-ref-0.63.1.tgz",
"integrity": "sha512-8MkXX4+R3i80msdbD4rFpEB4WWq2UDvGwG386g3ckIWbekdvN9z2kWAd9OXhRGqB7QeOsoAGWocp6gAMCivRlw==",
"dependencies": {
"@babel/runtime": "^7.10.4",
"react-is": "^16.6.3"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18",
"react-dom": "^16.8.0 || ^17 || ^18"
}
},
"node_modules/@fluentui/react-component-ref/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/@popperjs/core": {
"version": "2.11.6",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@semantic-ui-react/event-stack": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@semantic-ui-react/event-stack/-/event-stack-3.1.3.tgz",
"integrity": "sha512-FdTmJyWvJaYinHrKRsMLDrz4tTMGdFfds299Qory53hBugiDvGC0tEJf+cHsi5igDwWb/CLOgOiChInHwq8URQ==",
"dependencies": {
"exenv": "^1.2.2",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/exenv": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
"integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw=="
},
"node_modules/jquery": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz",
"integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg=="
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/keyboard-key": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/keyboard-key/-/keyboard-key-1.1.0.tgz",
"integrity": "sha512-qkBzPTi3rlAKvX7k0/ub44sqOfXeLc/jcnGGmj5c7BJpU8eDrEVPyhCvNYAaoubbsLm9uGWwQJO1ytQK1a9/dQ=="
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/prop-types/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.0"
},
"peerDependencies": {
"react": "^18.2.0"
}
},
"node_modules/react-fast-compare": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
},
"node_modules/react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
},
"node_modules/react-popper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz",
"integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==",
"dependencies": {
"react-fast-compare": "^3.0.1",
"warning": "^4.0.2"
},
"peerDependencies": {
"@popperjs/core": "^2.0.0",
"react": "^16.8.0 || ^17 || ^18",
"react-dom": "^16.8.0 || ^17 || ^18"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/semantic-ui-css": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/semantic-ui-css/-/semantic-ui-css-2.5.0.tgz",
"integrity": "sha512-jIWn3WXXE2uSaWCcB+gVJVRG3masIKtTMNEP2X8Aw909H2rHpXGneYOxzO3hT8TpyvB5/dEEo9mBFCitGwoj1A==",
"dependencies": {
"jquery": "x.*"
}
},
"node_modules/semantic-ui-react": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-2.1.4.tgz",
"integrity": "sha512-7CxjBoFUfH7fUvtn+SPkkIocthUD9kV3niF1mUMa9TbeyPAf2brtRCZBlT2OpHaXmkscFzGjEfhbJo9gKfotzQ==",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@fluentui/react-component-event-listener": "~0.63.0",
"@fluentui/react-component-ref": "~0.63.0",
"@popperjs/core": "^2.6.0",
"@semantic-ui-react/event-stack": "^3.1.3",
"clsx": "^1.1.1",
"keyboard-key": "^1.1.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"prop-types": "^15.7.2",
"react-is": "^16.8.6 || ^17.0.0 || ^18.0.0",
"react-popper": "^2.3.0",
"shallowequal": "^1.1.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
},
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"dependencies": {
"loose-envify": "^1.0.0"
}
}
},
"dependencies": {
"@babel/runtime": {
"version": "7.20.13",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz",
"integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==",
"requires": {
"regenerator-runtime": "^0.13.11"
}
},
"@fluentui/react-component-event-listener": {
"version": "0.63.1",
"resolved": "https://registry.npmjs.org/@fluentui/react-component-event-listener/-/react-component-event-listener-0.63.1.tgz",
"integrity": "sha512-gSMdOh6tI3IJKZFqxfQwbTpskpME0CvxdxGM2tdglmf6ZPVDi0L4+KKIm+2dN8nzb8Ya1A8ZT+Ddq0KmZtwVQg==",
"requires": {
"@babel/runtime": "^7.10.4"
}
},
"@fluentui/react-component-ref": {
"version": "0.63.1",
"resolved": "https://registry.npmjs.org/@fluentui/react-component-ref/-/react-component-ref-0.63.1.tgz",
"integrity": "sha512-8MkXX4+R3i80msdbD4rFpEB4WWq2UDvGwG386g3ckIWbekdvN9z2kWAd9OXhRGqB7QeOsoAGWocp6gAMCivRlw==",
"requires": {
"@babel/runtime": "^7.10.4",
"react-is": "^16.6.3"
},
"dependencies": {
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
}
},
"@popperjs/core": {
"version": "2.11.6",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw=="
},
"@semantic-ui-react/event-stack": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@semantic-ui-react/event-stack/-/event-stack-3.1.3.tgz",
"integrity": "sha512-FdTmJyWvJaYinHrKRsMLDrz4tTMGdFfds299Qory53hBugiDvGC0tEJf+cHsi5igDwWb/CLOgOiChInHwq8URQ==",
"requires": {
"exenv": "^1.2.2",
"prop-types": "^15.6.2"
}
},
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
},
"exenv": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
"integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw=="
},
"jquery": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz",
"integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg=="
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"keyboard-key": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/keyboard-key/-/keyboard-key-1.1.0.tgz",
"integrity": "sha512-qkBzPTi3rlAKvX7k0/ub44sqOfXeLc/jcnGGmj5c7BJpU8eDrEVPyhCvNYAaoubbsLm9uGWwQJO1ytQK1a9/dQ=="
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
},
"prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
},
"dependencies": {
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
}
},
"react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"peer": true,
"requires": {
"loose-envify": "^1.1.0"
}
},
"react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
"peer": true,
"requires": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.0"
}
},
"react-fast-compare": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
},
"react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
},
"react-popper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz",
"integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==",
"requires": {
"react-fast-compare": "^3.0.1",
"warning": "^4.0.2"
}
},
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
"peer": true,
"requires": {
"loose-envify": "^1.1.0"
}
},
"semantic-ui-css": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/semantic-ui-css/-/semantic-ui-css-2.5.0.tgz",
"integrity": "sha512-jIWn3WXXE2uSaWCcB+gVJVRG3masIKtTMNEP2X8Aw909H2rHpXGneYOxzO3hT8TpyvB5/dEEo9mBFCitGwoj1A==",
"requires": {
"jquery": "x.*"
}
},
"semantic-ui-react": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-2.1.4.tgz",
"integrity": "sha512-7CxjBoFUfH7fUvtn+SPkkIocthUD9kV3niF1mUMa9TbeyPAf2brtRCZBlT2OpHaXmkscFzGjEfhbJo9gKfotzQ==",
"requires": {
"@babel/runtime": "^7.10.5",
"@fluentui/react-component-event-listener": "~0.63.0",
"@fluentui/react-component-ref": "~0.63.0",
"@popperjs/core": "^2.6.0",
"@semantic-ui-react/event-stack": "^3.1.3",
"clsx": "^1.1.1",
"keyboard-key": "^1.1.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"prop-types": "^15.7.2",
"react-is": "^16.8.6 || ^17.0.0 || ^18.0.0",
"react-popper": "^2.3.0",
"shallowequal": "^1.1.0"
}
},
"shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
},
"warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"requires": {
"loose-envify": "^1.0.0"
}
}
}
}

6
package.json Normal file
View File

@@ -0,0 +1,6 @@
{
"dependencies": {
"semantic-ui-css": "^2.5.0",
"semantic-ui-react": "^2.1.4"
}
}

View File

@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "6.0.12",
"commands": [
"dotnet-ef"
]
}
}
}

25
teams/.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

405
teams/.gitignore vendored Normal file
View File

@@ -0,0 +1,405 @@
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# content below from: https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/

View File

@@ -0,0 +1,50 @@
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Teams.Filter;
using Teams.Models;
using Teams.Services;
using Teams.Wrappers;
namespace Teams.Controllers;
[ApiController]
[Route("api/teams")]
public class TeamsController : ControllerBase
{
private readonly ITeamsRepository _teamsRepository;
private readonly IMapper _mapper;
const int maxTeamsPerPage = 50;
public TeamsController(ITeamsRepository teamsRepository, IMapper mapper)
{
_teamsRepository = teamsRepository ?? throw new ArgumentNullException(nameof(teamsRepository));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
[HttpGet]
public async Task<ActionResult<IEnumerable<TeamDto>>> GetTeams([FromQuery] PaginationFilter filter)
{
if (filter.PageSize > maxTeamsPerPage)
{
filter.PageSize = maxTeamsPerPage;
}
var (teamsEntities, pageinationMetadata) = await _teamsRepository.GetTeamsAsync(filter.PageNumber, filter.PageSize);
return Ok(new PagedResponse<IEnumerable<TeamDto>>(_mapper.Map<IEnumerable<TeamDto>>(teamsEntities), pageinationMetadata));
}
[HttpGet("{id}")]
public async Task<ActionResult<TeamDto>> GetTeam(int id)
{
var team = await _teamsRepository.GetTeamAsync(id);
if (team == null)
{
return NotFound();
}
return Ok(new Response<TeamDto>(_mapper.Map<TeamDto>(team)));
}
}

View File

@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Teams.Entities;
namespace Teams.DbContexts
{
public partial class MlbGameDayContext : DbContext
{
public MlbGameDayContext()
{
}
public MlbGameDayContext(DbContextOptions<MlbGameDayContext> options)
: base(options)
{
}
public virtual DbSet<Team> Teams { 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;Database=mlb-game-day;Username=postgres;Password=postgres");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Team>(entity =>
{
entity.HasNoKey();
entity.ToTable("team");
entity.Property(e => e.Abbreviation).HasColumnName("abbreviation");
entity.Property(e => e.Active).HasColumnName("active");
entity.Property(e => e.AllStarStatus).HasColumnName("allStarStatus");
entity.Property(e => e.ClubName).HasColumnName("clubName");
entity.Property(e => e.DivisionId).HasColumnName("divisionId");
entity.Property(e => e.FileCode).HasColumnName("fileCode");
entity.Property(e => e.FirstYearOfPlay).HasColumnName("firstYearOfPlay");
entity.Property(e => e.FranchiseName).HasColumnName("franchiseName");
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.LocationName).HasColumnName("locationName");
entity.Property(e => e.Name).HasColumnName("name");
entity.Property(e => e.Season).HasColumnName("season");
entity.Property(e => e.ShortName).HasColumnName("shortName");
entity.Property(e => e.SportId).HasColumnName("sportId");
entity.Property(e => e.SpringLeagueId).HasColumnName("springLeagueId");
entity.Property(e => e.SpringVenue).HasColumnName("springVenue");
entity.Property(e => e.SpringVenueId).HasColumnName("springVenueId");
entity.Property(e => e.TeamCode).HasColumnName("teamCode");
entity.Property(e => e.TeamName).HasColumnName("teamName");
entity.Property(e => e.Venue).HasColumnName("venue");
entity.Property(e => e.VenueId).HasColumnName("venueId");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
}

22
teams/Dockerfile Normal file
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 ["Teams.csproj", "."]
RUN dotnet restore "./Teams.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "Teams.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Teams.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Teams.dll"]

32
teams/Entities/Team.cs Normal file
View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
namespace Teams.Entities
{
public partial class Team
{
public int? SpringLeagueId { get; set; }
public string? AllStarStatus { get; set; }
public int? Id { get; set; }
public string? Name { get; set; }
public string? Link { get; set; }
public int? Season { get; set; }
public int? VenueId { get; set; }
public int? SpringVenueId { get; set; }
public string? TeamCode { get; set; }
public string? FileCode { get; set; }
public string? Abbreviation { get; set; }
public string? TeamName { get; set; }
public string? LocationName { get; set; }
public int? FirstYearOfPlay { get; set; }
public int? LeagueId { get; set; }
public int? DivisionId { get; set; }
public int? SportId { get; set; }
public string? ShortName { get; set; }
public string? FranchiseName { get; set; }
public string? ClubName { get; set; }
public string? Active { get; set; }
public int? Venue { get; set; }
public int? SpringVenue { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using System;
namespace Teams.Filter
{
public class PaginationFilter
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public PaginationFilter()
{
this.PageNumber = 1;
this.PageSize = 10;
}
public PaginationFilter(int pageNumber, int pageSize)
{
this.PageNumber = pageNumber < 1 ? 1 : pageNumber;
this.PageSize = pageSize > 10 ? 10 : pageSize;
}
}
}

29
teams/Models/TeamDto.cs Normal file
View File

@@ -0,0 +1,29 @@
using System;
namespace Teams.Models
{
public class TeamDto
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Abbreviation { get; set; }
public string? Active { get; set; }
public string? AllStarStatus { get; set; }
public string? ClubName { get; set; }
public int? DivisionId { get; set; }
public string? FileCode { get; set; }
public int? FirstYearOfPlay { get; set; }
public string? FranchiseName { get; set; }
public int? LeagueId { get; set; }
public string? Link { get; set; }
public string? LocationName { get; set; }
public string? Season { get; set; }
public string? ShortName { get; set; }
public int? SpringLeagueId { get; set; }
public int? SpringVenueId { get; set; }
public int? SportId { get; set; }
public string? TeamCode { get; set; }
public string? TeamName { get; set; }
public int? VenueId { get; set; }
}
}

View File

@@ -0,0 +1,14 @@
using System;
using AutoMapper;
namespace Teams.Profiles
{
public class TeamProfile : Profile
{
public TeamProfile()
{
CreateMap<Entities.Team, Models.TeamDto>();
}
}
}

56
teams/Program.cs Normal file
View File

@@ -0,0 +1,56 @@
using Microsoft.EntityFrameworkCore;
using Serilog;
using Teams;
using Teams.DbContexts;
using Teams.Services;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File("logs/teams.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog();
// Add services to the container.
builder.Services.AddControllers(options =>
{
options.ReturnHttpNotAcceptable = true;
}).AddXmlDataContractSerializerFormatters();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<MlbGameDayContext>(dbContextOptions =>
dbContextOptions.UseNpgsql(builder.Configuration["ConnectionStrings:DBConnectionString"])
);
builder.Services.AddScoped<ITeamsRepository, TeamsRepository>();
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();

View File

@@ -0,0 +1,30 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:39374",
"sslPort": 44327
}
},
"profiles": {
"Teams": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7520;http://localhost:5520",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using Teams.Entities;
namespace Teams.Services
{
public interface ITeamsRepository
{
Task<Team?> GetTeamAsync(int teamId);
Task<(IEnumerable<Team>, PaginationMetadata)> GetTeamsAsync(int pageNumber, int pageSize);
}
}

View File

@@ -0,0 +1,21 @@
using System;
namespace Teams.Services
{
public class PaginationMetadata
{
public int TotalItemCount { get; set; }
public int TotalPageCount { get; set; }
public int PageSize { get; set; }
public int CurrentPage { get; set; }
public PaginationMetadata(int totalItemCount, int pageSize, int currentPage)
{
TotalItemCount = totalItemCount;
PageSize = pageSize;
CurrentPage = currentPage;
TotalPageCount = (int)Math.Ceiling(totalItemCount / (double)pageSize);
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using Microsoft.EntityFrameworkCore;
using Teams.DbContexts;
using Teams.Entities;
namespace Teams.Services
{
public class TeamsRepository : ITeamsRepository
{
private readonly MlbGameDayContext _context;
public TeamsRepository(MlbGameDayContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public async Task<Team?> GetTeamAsync(int teamId)
{
return await _context.Teams.Where(t => t.Id == teamId).FirstOrDefaultAsync();
}
public async Task<(IEnumerable<Team>, PaginationMetadata)> GetTeamsAsync(int pageNumber, int pageSize)
{
var collection = _context.Teams as IQueryable<Team>;
var totalItemCount = await collection.CountAsync();
var paginationMetadata = new PaginationMetadata(totalItemCount, pageSize, pageNumber);
var collectionToReturn = await collection.OrderBy(t => t.Name)
.Skip(pageSize * (pageNumber - 1))
.Take(pageSize)
.ToListAsync();
return (collectionToReturn, paginationMetadata);
}
}
}

50
teams/Teams.csproj Normal file
View File

@@ -0,0 +1,50 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerComposeProjectPath>docker-compose.dcproj</DockerComposeProjectPath>
<UserSecretsId>d745850b-868c-4b57-ad7a-904de079cb65</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.12">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.8" />
<PackageReference Include="Serilog.AspNetCore" Version="6.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
</ItemGroup>
<ItemGroup>
<None Remove="Entities\" />
<None Remove="Models\" />
<None Remove="DbContexts\" />
<None Remove="Filter\" />
<None Remove="Profiles\" />
<None Remove="Services\" />
<None Remove="Wrappers\" />
<None Remove="Microsoft.EntityFrameworkCore" />
<None Remove="Microsoft.EntityFrameworkCore.Tools" />
<None Remove="Npgsql.EntityFrameworkCore.PostgreSQL" />
<None Remove="Serilog.AspNetCore" />
<None Remove="Serilog.Sinks.Console" />
<None Remove="Serilog.Sinks.File" />
<None Remove="AutoMapper.Extensions.Microsoft.DependencyInjection" />
</ItemGroup>
<ItemGroup>
<Folder Include="Entities\" />
<Folder Include="Models\" />
<Folder Include="DbContexts\" />
<Folder Include="Entities\" />
<Folder Include="Filter\" />
<Folder Include="Profiles\" />
<Folder Include="Services\" />
<Folder Include="Wrappers\" />
</ItemGroup>
</Project>

31
teams/Teams.sln Normal file
View File

@@ -0,0 +1,31 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 25.0.1704.2
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Teams", "Teams.csproj", "{CA6D3C8A-50A5-460F-A2AB-BC57088322B3}"
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{A089F086-FA22-494F-8322-0B275120213E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CA6D3C8A-50A5-460F-A2AB-BC57088322B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CA6D3C8A-50A5-460F-A2AB-BC57088322B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CA6D3C8A-50A5-460F-A2AB-BC57088322B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CA6D3C8A-50A5-460F-A2AB-BC57088322B3}.Release|Any CPU.Build.0 = Release|Any CPU
{A089F086-FA22-494F-8322-0B275120213E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A089F086-FA22-494F-8322-0B275120213E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A089F086-FA22-494F-8322-0B275120213E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A089F086-FA22-494F-8322-0B275120213E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3FE7224D-E56C-4230-88AF-AA2D1A7D4C17}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,27 @@
using System;
using Teams.Services;
namespace Teams.Wrappers
{
public class PagedResponse<T>: Response<T>
{
public int TotalItemCount { get; set; }
public int TotalPageCount { get; set; }
public int PageSize { get; set; }
public int CurrentPage { get; set; }
public PagedResponse(T results, PaginationMetadata paginationMetadata)
{
this.TotalItemCount = paginationMetadata.TotalItemCount;
this.TotalPageCount = paginationMetadata.TotalPageCount;
this.CurrentPage = paginationMetadata.CurrentPage;
this.PageSize = paginationMetadata.PageSize;
this.CurrentPage = paginationMetadata.CurrentPage;
this.Results = results;
this.Message = null;
this.Succeeded = true;
this.Errors = null;
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
namespace Teams.Wrappers
{
public class Response<T>
{
public T Results { get; set; }
public bool Succeeded { get; set; }
public string[] Errors { get; set; }
public string Message { get; set; }
public Response()
{
}
public Response(T results)
{
Succeeded = true;
Message = string.Empty;
Errors = null;
Results = results;
}
}
}

View File

@@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DBConnectionString": "Host=localhost;Database=mlb-game-day;Username=postgres;Password=postgres"
}
}

10
teams/appsettings.json Normal file
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>{A089F086-FA22-494F-8322-0B275120213E}</ProjectGuid>
<DockerLaunchBrowser>True</DockerLaunchBrowser>
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}/swagger</DockerServiceUrl>
<DockerServiceName>teams</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:
teams:
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

8
teams/docker-compose.yml Normal file
View File

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