Add frontend stuff
parent
210b590544
commit
67567046b8
|
@ -13,7 +13,8 @@
|
|||
"pinia": "^2.2.4",
|
||||
"v-tooltip": "^2.1.3",
|
||||
"vue": "^3.5.12",
|
||||
"vue-router": "^4.4.5"
|
||||
"vue-router": "^4.4.5",
|
||||
"vue-select": "^4.0.0-beta.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
|
@ -31,6 +32,7 @@
|
|||
"eslint-plugin-vue": "^9.29.0",
|
||||
"jsdom": "^25.0.1",
|
||||
"npm-run-all2": "^6.2.3",
|
||||
"openapi-typescript-codegen": "^0.29.0",
|
||||
"postcss": "^8.4.47",
|
||||
"prettier": "^3.3.3",
|
||||
"tailwindcss": "^3.4.14",
|
||||
|
@ -53,6 +55,24 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@apidevtools/json-schema-ref-parser": {
|
||||
"version": "11.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz",
|
||||
"integrity": "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jsdevtools/ono": "^7.1.3",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/philsturgeon"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
|
||||
|
@ -868,6 +888,13 @@
|
|||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsdevtools/ono": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
|
||||
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
@ -2376,6 +2403,19 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
|
||||
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase-css": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||
|
@ -4059,6 +4099,28 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/handlebars": {
|
||||
"version": "4.7.8",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
|
||||
"integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5",
|
||||
"neo-async": "^2.6.2",
|
||||
"source-map": "^0.6.1",
|
||||
"wordwrap": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"handlebars": "bin/handlebars"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.7"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"uglify-js": "^3.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
|
@ -5127,6 +5189,13 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/neo-async": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||
|
@ -5328,6 +5397,48 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/openapi-typescript-codegen": {
|
||||
"version": "0.29.0",
|
||||
"resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.29.0.tgz",
|
||||
"integrity": "sha512-/wC42PkD0LGjDTEULa/XiWQbv4E9NwLjwLjsaJ/62yOsoYhwvmBR31kPttn1DzQ2OlGe5stACcF/EIkZk43M6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@apidevtools/json-schema-ref-parser": "^11.5.4",
|
||||
"camelcase": "^6.3.0",
|
||||
"commander": "^12.0.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"handlebars": "^4.7.8"
|
||||
},
|
||||
"bin": {
|
||||
"openapi": "bin/index.js"
|
||||
}
|
||||
},
|
||||
"node_modules/openapi-typescript-codegen/node_modules/commander": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/openapi-typescript-codegen/node_modules/fs-extra": {
|
||||
"version": "11.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
|
||||
"integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
|
@ -6308,7 +6419,6 @@
|
|||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
@ -6881,6 +6991,20 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/uglify-js": {
|
||||
"version": "3.19.3",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
|
||||
"integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"uglifyjs": "bin/uglifyjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
|
@ -7316,6 +7440,15 @@
|
|||
"vue": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-select": {
|
||||
"version": "4.0.0-beta.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-select/-/vue-select-4.0.0-beta.6.tgz",
|
||||
"integrity": "sha512-K+zrNBSpwMPhAxYLTCl56gaMrWZGgayoWCLqe5rWwkB8aUbAUh7u6sXjIR7v4ckp2WKC7zEEUY27g6h1MRsIHw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"vue": "3.x"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-tsc": {
|
||||
"version": "2.1.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.1.6.tgz",
|
||||
|
@ -7447,6 +7580,13 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build --force",
|
||||
"lint": "eslint . --fix",
|
||||
"format": "prettier --write src/"
|
||||
"format": "prettier --write src/",
|
||||
"openapi-generate": "openapi --input 'http://localhost:5000/apidoc/openapi.json' --output src/client --name AvailabilitfClient"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.7",
|
||||
|
@ -19,7 +20,8 @@
|
|||
"pinia": "^2.2.4",
|
||||
"v-tooltip": "^2.1.3",
|
||||
"vue": "^3.5.12",
|
||||
"vue-router": "^4.4.5"
|
||||
"vue-router": "^4.4.5",
|
||||
"vue-select": "^4.0.0-beta.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
|
@ -37,6 +39,7 @@
|
|||
"eslint-plugin-vue": "^9.29.0",
|
||||
"jsdom": "^25.0.1",
|
||||
"npm-run-all2": "^6.2.3",
|
||||
"openapi-typescript-codegen": "^0.29.0",
|
||||
"postcss": "^8.4.47",
|
||||
"prettier": "^3.3.3",
|
||||
"tailwindcss": "^3.4.14",
|
||||
|
|
|
@ -8,6 +8,7 @@ const baseUrl = window.location.origin;
|
|||
<header>
|
||||
<div class="wrapper">
|
||||
<nav>
|
||||
<h1>availabili.tf</h1>
|
||||
<RouterLink to="/">Home</RouterLink>
|
||||
<RouterLink to="/schedule">Schedule</RouterLink>
|
||||
<RouterLink to="/schedule/roster">Roster Builder</RouterLink>
|
||||
|
@ -25,7 +26,9 @@ const baseUrl = window.location.origin;
|
|||
</div>
|
||||
</header>
|
||||
|
||||
<RouterView />
|
||||
<div class="content">
|
||||
<RouterView />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
@ -45,7 +48,8 @@ nav {
|
|||
width: 100%;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
margin: 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active {
|
||||
|
@ -64,6 +68,11 @@ nav a:hover {
|
|||
background-color: var(--accent-transparent);
|
||||
}
|
||||
|
||||
nav > h1 {
|
||||
line-height: unset;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
header {
|
||||
display: flex;
|
||||
|
@ -88,7 +97,14 @@ nav a:hover {
|
|||
font-size: 1rem;
|
||||
|
||||
padding: 1rem 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
#app > div.content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#app > div.content > main {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -37,7 +37,8 @@
|
|||
|
||||
--flamingo: #f0c6c6;
|
||||
--flamingo-transparent: #f0c6c655;
|
||||
--green: #a6e3a1;
|
||||
--green: #40a02b;
|
||||
--yellow: #df8e1d;
|
||||
--lavender: #7287fd;
|
||||
--accent: var(--lavender);
|
||||
--accent-transparent-80: color-mix(in srgb, var(--accent), transparent 80%);
|
||||
|
@ -45,6 +46,10 @@
|
|||
--accent-transparent: var(--accent-transparent-80);
|
||||
--shadow: color-mix(in srgb, var(--text), transparent 50%);
|
||||
|
||||
--vs-selected-color: var(--text)!important;
|
||||
--vs-dropdown-option--active-bg: var(--accent)!important;
|
||||
--vs-dropdown-option--active-color: var(--base)!important;
|
||||
--vs-border-color: var(--overlay-0)!important;
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
width: 100%;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
@ -30,7 +29,7 @@ button {
|
|||
background-color: var(--crust);
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
border-radius: 4px;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
|
@ -63,7 +62,7 @@ button:hover {
|
|||
button.accent {
|
||||
background-color: var(--accent);
|
||||
color: var(--base);
|
||||
text-transform: uppercase;
|
||||
/*text-transform: uppercase;*/
|
||||
}
|
||||
|
||||
button.accent.dark {
|
||||
|
@ -88,6 +87,10 @@ button.transparent:hover {
|
|||
background-color: var(--surface-0);
|
||||
}
|
||||
|
||||
button.small {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
button[disabled] {
|
||||
color: var(--overlay-0);
|
||||
cursor: initial;
|
||||
|
@ -119,3 +122,31 @@ select {
|
|||
.subtext {
|
||||
color: var(--overlay-0);
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 6px 9px;
|
||||
border: none;
|
||||
/*outline: 1px solid var(--overlay-0);*/
|
||||
border: 1px solid var(--overlay-0);
|
||||
border-radius: 4px;
|
||||
background-color: transparent;
|
||||
/*font-size: 11pt;*/
|
||||
font-size: 15px;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
'Fira Sans',
|
||||
'Droid Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,315 @@
|
|||
[
|
||||
"Africa/Abidjan",
|
||||
"Africa/Algiers",
|
||||
"Africa/Bissau",
|
||||
"Africa/Cairo",
|
||||
"Africa/Casablanca",
|
||||
"Africa/Ceuta",
|
||||
"Africa/El_Aaiun",
|
||||
"Africa/Johannesburg",
|
||||
"Africa/Juba",
|
||||
"Africa/Khartoum",
|
||||
"Africa/Lagos",
|
||||
"Africa/Maputo",
|
||||
"Africa/Monrovia",
|
||||
"Africa/Nairobi",
|
||||
"Africa/Ndjamena",
|
||||
"Africa/Sao_Tome",
|
||||
"Africa/Tripoli",
|
||||
"Africa/Tunis",
|
||||
"Africa/Windhoek",
|
||||
"America/Adak",
|
||||
"America/Anchorage",
|
||||
"America/Araguaina",
|
||||
"America/Argentina/Buenos_Aires",
|
||||
"America/Argentina/Catamarca",
|
||||
"America/Argentina/Cordoba",
|
||||
"America/Argentina/Jujuy",
|
||||
"America/Argentina/La_Rioja",
|
||||
"America/Argentina/Mendoza",
|
||||
"America/Argentina/Rio_Gallegos",
|
||||
"America/Argentina/Salta",
|
||||
"America/Argentina/San_Juan",
|
||||
"America/Argentina/San_Luis",
|
||||
"America/Argentina/Tucuman",
|
||||
"America/Argentina/Ushuaia",
|
||||
"America/Asuncion",
|
||||
"America/Bahia",
|
||||
"America/Bahia_Banderas",
|
||||
"America/Barbados",
|
||||
"America/Belem",
|
||||
"America/Belize",
|
||||
"America/Boa_Vista",
|
||||
"America/Bogota",
|
||||
"America/Boise",
|
||||
"America/Cambridge_Bay",
|
||||
"America/Campo_Grande",
|
||||
"America/Cancun",
|
||||
"America/Caracas",
|
||||
"America/Cayenne",
|
||||
"America/Chicago",
|
||||
"America/Chihuahua",
|
||||
"America/Ciudad_Juarez",
|
||||
"America/Costa_Rica",
|
||||
"America/Cuiaba",
|
||||
"America/Danmarkshavn",
|
||||
"America/Dawson",
|
||||
"America/Dawson_Creek",
|
||||
"America/Denver",
|
||||
"America/Detroit",
|
||||
"America/Edmonton",
|
||||
"America/Eirunepe",
|
||||
"America/El_Salvador",
|
||||
"America/Fort_Nelson",
|
||||
"America/Fortaleza",
|
||||
"America/Glace_Bay",
|
||||
"America/Goose_Bay",
|
||||
"America/Grand_Turk",
|
||||
"America/Guatemala",
|
||||
"America/Guayaquil",
|
||||
"America/Guyana",
|
||||
"America/Halifax",
|
||||
"America/Havana",
|
||||
"America/Hermosillo",
|
||||
"America/Indiana/Indianapolis",
|
||||
"America/Indiana/Knox",
|
||||
"America/Indiana/Marengo",
|
||||
"America/Indiana/Petersburg",
|
||||
"America/Indiana/Tell_City",
|
||||
"America/Indiana/Vevay",
|
||||
"America/Indiana/Vincennes",
|
||||
"America/Indiana/Winamac",
|
||||
"America/Inuvik",
|
||||
"America/Iqaluit",
|
||||
"America/Jamaica",
|
||||
"America/Juneau",
|
||||
"America/Kentucky/Louisville",
|
||||
"America/Kentucky/Monticello",
|
||||
"America/La_Paz",
|
||||
"America/Lima",
|
||||
"America/Los_Angeles",
|
||||
"America/Maceio",
|
||||
"America/Managua",
|
||||
"America/Manaus",
|
||||
"America/Martinique",
|
||||
"America/Matamoros",
|
||||
"America/Mazatlan",
|
||||
"America/Menominee",
|
||||
"America/Merida",
|
||||
"America/Metlakatla",
|
||||
"America/Mexico_City",
|
||||
"America/Miquelon",
|
||||
"America/Moncton",
|
||||
"America/Monterrey",
|
||||
"America/Montevideo",
|
||||
"America/New_York",
|
||||
"America/Nome",
|
||||
"America/Noronha",
|
||||
"America/North_Dakota/Beulah",
|
||||
"America/North_Dakota/Center",
|
||||
"America/North_Dakota/New_Salem",
|
||||
"America/Nuuk",
|
||||
"America/Ojinaga",
|
||||
"America/Panama",
|
||||
"America/Paramaribo",
|
||||
"America/Phoenix",
|
||||
"America/Port-au-Prince",
|
||||
"America/Porto_Velho",
|
||||
"America/Puerto_Rico",
|
||||
"America/Punta_Arenas",
|
||||
"America/Rankin_Inlet",
|
||||
"America/Recife",
|
||||
"America/Regina",
|
||||
"America/Resolute",
|
||||
"America/Rio_Branco",
|
||||
"America/Santarem",
|
||||
"America/Santiago",
|
||||
"America/Santo_Domingo",
|
||||
"America/Sao_Paulo",
|
||||
"America/Scoresbysund",
|
||||
"America/Sitka",
|
||||
"America/St_Johns",
|
||||
"America/Swift_Current",
|
||||
"America/Tegucigalpa",
|
||||
"America/Thule",
|
||||
"America/Tijuana",
|
||||
"America/Toronto",
|
||||
"America/Vancouver",
|
||||
"America/Whitehorse",
|
||||
"America/Winnipeg",
|
||||
"America/Yakutat",
|
||||
"Antarctica/Casey",
|
||||
"Antarctica/Davis",
|
||||
"Antarctica/Macquarie",
|
||||
"Antarctica/Mawson",
|
||||
"Antarctica/Palmer",
|
||||
"Antarctica/Rothera",
|
||||
"Antarctica/Troll",
|
||||
"Antarctica/Vostok",
|
||||
"Asia/Almaty",
|
||||
"Asia/Amman",
|
||||
"Asia/Anadyr",
|
||||
"Asia/Aqtau",
|
||||
"Asia/Aqtobe",
|
||||
"Asia/Ashgabat",
|
||||
"Asia/Atyrau",
|
||||
"Asia/Baghdad",
|
||||
"Asia/Baku",
|
||||
"Asia/Bangkok",
|
||||
"Asia/Barnaul",
|
||||
"Asia/Beirut",
|
||||
"Asia/Bishkek",
|
||||
"Asia/Chita",
|
||||
"Asia/Colombo",
|
||||
"Asia/Damascus",
|
||||
"Asia/Dhaka",
|
||||
"Asia/Dili",
|
||||
"Asia/Dubai",
|
||||
"Asia/Dushanbe",
|
||||
"Asia/Famagusta",
|
||||
"Asia/Gaza",
|
||||
"Asia/Hebron",
|
||||
"Asia/Ho_Chi_Minh",
|
||||
"Asia/Hong_Kong",
|
||||
"Asia/Hovd",
|
||||
"Asia/Irkutsk",
|
||||
"Asia/Jakarta",
|
||||
"Asia/Jayapura",
|
||||
"Asia/Jerusalem",
|
||||
"Asia/Kabul",
|
||||
"Asia/Kamchatka",
|
||||
"Asia/Karachi",
|
||||
"Asia/Kathmandu",
|
||||
"Asia/Khandyga",
|
||||
"Asia/Kolkata",
|
||||
"Asia/Krasnoyarsk",
|
||||
"Asia/Kuching",
|
||||
"Asia/Macau",
|
||||
"Asia/Magadan",
|
||||
"Asia/Makassar",
|
||||
"Asia/Manila",
|
||||
"Asia/Nicosia",
|
||||
"Asia/Novokuznetsk",
|
||||
"Asia/Novosibirsk",
|
||||
"Asia/Omsk",
|
||||
"Asia/Oral",
|
||||
"Asia/Pontianak",
|
||||
"Asia/Pyongyang",
|
||||
"Asia/Qatar",
|
||||
"Asia/Qostanay",
|
||||
"Asia/Qyzylorda",
|
||||
"Asia/Riyadh",
|
||||
"Asia/Sakhalin",
|
||||
"Asia/Samarkand",
|
||||
"Asia/Seoul",
|
||||
"Asia/Shanghai",
|
||||
"Asia/Singapore",
|
||||
"Asia/Srednekolymsk",
|
||||
"Asia/Taipei",
|
||||
"Asia/Tashkent",
|
||||
"Asia/Tbilisi",
|
||||
"Asia/Tehran",
|
||||
"Asia/Thimphu",
|
||||
"Asia/Tokyo",
|
||||
"Asia/Tomsk",
|
||||
"Asia/Ulaanbaatar",
|
||||
"Asia/Urumqi",
|
||||
"Asia/Ust-Nera",
|
||||
"Asia/Vladivostok",
|
||||
"Asia/Yakutsk",
|
||||
"Asia/Yangon",
|
||||
"Asia/Yekaterinburg",
|
||||
"Asia/Yerevan",
|
||||
"Atlantic/Azores",
|
||||
"Atlantic/Bermuda",
|
||||
"Atlantic/Canary",
|
||||
"Atlantic/Cape_Verde",
|
||||
"Atlantic/Faroe",
|
||||
"Atlantic/Madeira",
|
||||
"Atlantic/South_Georgia",
|
||||
"Atlantic/Stanley",
|
||||
"Australia/Adelaide",
|
||||
"Australia/Brisbane",
|
||||
"Australia/Broken_Hill",
|
||||
"Australia/Darwin",
|
||||
"Australia/Eucla",
|
||||
"Australia/Hobart",
|
||||
"Australia/Lindeman",
|
||||
"Australia/Lord_Howe",
|
||||
"Australia/Melbourne",
|
||||
"Australia/Perth",
|
||||
"Australia/Sydney",
|
||||
"Etc/UTC",
|
||||
"Europe/Andorra",
|
||||
"Europe/Astrakhan",
|
||||
"Europe/Athens",
|
||||
"Europe/Belgrade",
|
||||
"Europe/Berlin",
|
||||
"Europe/Brussels",
|
||||
"Europe/Bucharest",
|
||||
"Europe/Budapest",
|
||||
"Europe/Chisinau",
|
||||
"Europe/Dublin",
|
||||
"Europe/Gibraltar",
|
||||
"Europe/Helsinki",
|
||||
"Europe/Istanbul",
|
||||
"Europe/Kaliningrad",
|
||||
"Europe/Kirov",
|
||||
"Europe/Kyiv",
|
||||
"Europe/Lisbon",
|
||||
"Europe/London",
|
||||
"Europe/Madrid",
|
||||
"Europe/Malta",
|
||||
"Europe/Minsk",
|
||||
"Europe/Moscow",
|
||||
"Europe/Paris",
|
||||
"Europe/Prague",
|
||||
"Europe/Riga",
|
||||
"Europe/Rome",
|
||||
"Europe/Samara",
|
||||
"Europe/Saratov",
|
||||
"Europe/Simferopol",
|
||||
"Europe/Sofia",
|
||||
"Europe/Tallinn",
|
||||
"Europe/Tirane",
|
||||
"Europe/Ulyanovsk",
|
||||
"Europe/Vienna",
|
||||
"Europe/Vilnius",
|
||||
"Europe/Volgograd",
|
||||
"Europe/Warsaw",
|
||||
"Europe/Zurich",
|
||||
"Factory",
|
||||
"Indian/Chagos",
|
||||
"Indian/Maldives",
|
||||
"Indian/Mauritius",
|
||||
"Pacific/Apia",
|
||||
"Pacific/Auckland",
|
||||
"Pacific/Bougainville",
|
||||
"Pacific/Chatham",
|
||||
"Pacific/Easter",
|
||||
"Pacific/Efate",
|
||||
"Pacific/Fakaofo",
|
||||
"Pacific/Fiji",
|
||||
"Pacific/Galapagos",
|
||||
"Pacific/Gambier",
|
||||
"Pacific/Guadalcanal",
|
||||
"Pacific/Guam",
|
||||
"Pacific/Honolulu",
|
||||
"Pacific/Kanton",
|
||||
"Pacific/Kiritimati",
|
||||
"Pacific/Kosrae",
|
||||
"Pacific/Kwajalein",
|
||||
"Pacific/Marquesas",
|
||||
"Pacific/Nauru",
|
||||
"Pacific/Niue",
|
||||
"Pacific/Norfolk",
|
||||
"Pacific/Noumea",
|
||||
"Pacific/Pago_Pago",
|
||||
"Pacific/Palau",
|
||||
"Pacific/Pitcairn",
|
||||
"Pacific/Port_Moresby",
|
||||
"Pacific/Rarotonga",
|
||||
"Pacific/Tahiti",
|
||||
"Pacific/Tarawa",
|
||||
"Pacific/Tongatapu"
|
||||
]
|
|
@ -0,0 +1,28 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { BaseHttpRequest } from './core/BaseHttpRequest';
|
||||
import type { OpenAPIConfig } from './core/OpenAPI';
|
||||
import { FetchHttpRequest } from './core/FetchHttpRequest';
|
||||
import { DefaultService } from './services/DefaultService';
|
||||
type HttpRequestConstructor = new (config: OpenAPIConfig) => BaseHttpRequest;
|
||||
export class AvailabilitfClient {
|
||||
public readonly default: DefaultService;
|
||||
public readonly request: BaseHttpRequest;
|
||||
constructor(config?: Partial<OpenAPIConfig>, HttpRequest: HttpRequestConstructor = FetchHttpRequest) {
|
||||
this.request = new HttpRequest({
|
||||
BASE: config?.BASE ?? '',
|
||||
VERSION: config?.VERSION ?? '0.1.0',
|
||||
WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false,
|
||||
CREDENTIALS: config?.CREDENTIALS ?? 'include',
|
||||
TOKEN: config?.TOKEN,
|
||||
USERNAME: config?.USERNAME,
|
||||
PASSWORD: config?.PASSWORD,
|
||||
HEADERS: config?.HEADERS,
|
||||
ENCODE_PATH: config?.ENCODE_PATH,
|
||||
});
|
||||
this.default = new DefaultService(this.request);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ApiRequestOptions } from './ApiRequestOptions';
|
||||
import type { ApiResult } from './ApiResult';
|
||||
|
||||
export class ApiError extends Error {
|
||||
public readonly url: string;
|
||||
public readonly status: number;
|
||||
public readonly statusText: string;
|
||||
public readonly body: any;
|
||||
public readonly request: ApiRequestOptions;
|
||||
|
||||
constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
|
||||
super(message);
|
||||
|
||||
this.name = 'ApiError';
|
||||
this.url = response.url;
|
||||
this.status = response.status;
|
||||
this.statusText = response.statusText;
|
||||
this.body = response.body;
|
||||
this.request = request;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type ApiRequestOptions = {
|
||||
readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH';
|
||||
readonly url: string;
|
||||
readonly path?: Record<string, any>;
|
||||
readonly cookies?: Record<string, any>;
|
||||
readonly headers?: Record<string, any>;
|
||||
readonly query?: Record<string, any>;
|
||||
readonly formData?: Record<string, any>;
|
||||
readonly body?: any;
|
||||
readonly mediaType?: string;
|
||||
readonly responseHeader?: string;
|
||||
readonly errors?: Record<number, string>;
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type ApiResult = {
|
||||
readonly url: string;
|
||||
readonly ok: boolean;
|
||||
readonly status: number;
|
||||
readonly statusText: string;
|
||||
readonly body: any;
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ApiRequestOptions } from './ApiRequestOptions';
|
||||
import type { CancelablePromise } from './CancelablePromise';
|
||||
import type { OpenAPIConfig } from './OpenAPI';
|
||||
|
||||
export abstract class BaseHttpRequest {
|
||||
|
||||
constructor(public readonly config: OpenAPIConfig) {}
|
||||
|
||||
public abstract request<T>(options: ApiRequestOptions): CancelablePromise<T>;
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export class CancelError extends Error {
|
||||
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'CancelError';
|
||||
}
|
||||
|
||||
public get isCancelled(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export interface OnCancel {
|
||||
readonly isResolved: boolean;
|
||||
readonly isRejected: boolean;
|
||||
readonly isCancelled: boolean;
|
||||
|
||||
(cancelHandler: () => void): void;
|
||||
}
|
||||
|
||||
export class CancelablePromise<T> implements Promise<T> {
|
||||
#isResolved: boolean;
|
||||
#isRejected: boolean;
|
||||
#isCancelled: boolean;
|
||||
readonly #cancelHandlers: (() => void)[];
|
||||
readonly #promise: Promise<T>;
|
||||
#resolve?: (value: T | PromiseLike<T>) => void;
|
||||
#reject?: (reason?: any) => void;
|
||||
|
||||
constructor(
|
||||
executor: (
|
||||
resolve: (value: T | PromiseLike<T>) => void,
|
||||
reject: (reason?: any) => void,
|
||||
onCancel: OnCancel
|
||||
) => void
|
||||
) {
|
||||
this.#isResolved = false;
|
||||
this.#isRejected = false;
|
||||
this.#isCancelled = false;
|
||||
this.#cancelHandlers = [];
|
||||
this.#promise = new Promise<T>((resolve, reject) => {
|
||||
this.#resolve = resolve;
|
||||
this.#reject = reject;
|
||||
|
||||
const onResolve = (value: T | PromiseLike<T>): void => {
|
||||
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
|
||||
return;
|
||||
}
|
||||
this.#isResolved = true;
|
||||
if (this.#resolve) this.#resolve(value);
|
||||
};
|
||||
|
||||
const onReject = (reason?: any): void => {
|
||||
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
|
||||
return;
|
||||
}
|
||||
this.#isRejected = true;
|
||||
if (this.#reject) this.#reject(reason);
|
||||
};
|
||||
|
||||
const onCancel = (cancelHandler: () => void): void => {
|
||||
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
|
||||
return;
|
||||
}
|
||||
this.#cancelHandlers.push(cancelHandler);
|
||||
};
|
||||
|
||||
Object.defineProperty(onCancel, 'isResolved', {
|
||||
get: (): boolean => this.#isResolved,
|
||||
});
|
||||
|
||||
Object.defineProperty(onCancel, 'isRejected', {
|
||||
get: (): boolean => this.#isRejected,
|
||||
});
|
||||
|
||||
Object.defineProperty(onCancel, 'isCancelled', {
|
||||
get: (): boolean => this.#isCancelled,
|
||||
});
|
||||
|
||||
return executor(onResolve, onReject, onCancel as OnCancel);
|
||||
});
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() {
|
||||
return "Cancellable Promise";
|
||||
}
|
||||
|
||||
public then<TResult1 = T, TResult2 = never>(
|
||||
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
||||
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
||||
): Promise<TResult1 | TResult2> {
|
||||
return this.#promise.then(onFulfilled, onRejected);
|
||||
}
|
||||
|
||||
public catch<TResult = never>(
|
||||
onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
|
||||
): Promise<T | TResult> {
|
||||
return this.#promise.catch(onRejected);
|
||||
}
|
||||
|
||||
public finally(onFinally?: (() => void) | null): Promise<T> {
|
||||
return this.#promise.finally(onFinally);
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
|
||||
return;
|
||||
}
|
||||
this.#isCancelled = true;
|
||||
if (this.#cancelHandlers.length) {
|
||||
try {
|
||||
for (const cancelHandler of this.#cancelHandlers) {
|
||||
cancelHandler();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Cancellation threw an error', error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.#cancelHandlers.length = 0;
|
||||
if (this.#reject) this.#reject(new CancelError('Request aborted'));
|
||||
}
|
||||
|
||||
public get isCancelled(): boolean {
|
||||
return this.#isCancelled;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ApiRequestOptions } from './ApiRequestOptions';
|
||||
import { BaseHttpRequest } from './BaseHttpRequest';
|
||||
import type { CancelablePromise } from './CancelablePromise';
|
||||
import type { OpenAPIConfig } from './OpenAPI';
|
||||
import { request as __request } from './request';
|
||||
|
||||
export class FetchHttpRequest extends BaseHttpRequest {
|
||||
|
||||
constructor(config: OpenAPIConfig) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request method
|
||||
* @param options The request options from the service
|
||||
* @returns CancelablePromise<T>
|
||||
* @throws ApiError
|
||||
*/
|
||||
public override request<T>(options: ApiRequestOptions): CancelablePromise<T> {
|
||||
return __request(this.config, options);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ApiRequestOptions } from './ApiRequestOptions';
|
||||
|
||||
type Resolver<T> = (options: ApiRequestOptions) => Promise<T>;
|
||||
type Headers = Record<string, string>;
|
||||
|
||||
export type OpenAPIConfig = {
|
||||
BASE: string;
|
||||
VERSION: string;
|
||||
WITH_CREDENTIALS: boolean;
|
||||
CREDENTIALS: 'include' | 'omit' | 'same-origin';
|
||||
TOKEN?: string | Resolver<string> | undefined;
|
||||
USERNAME?: string | Resolver<string> | undefined;
|
||||
PASSWORD?: string | Resolver<string> | undefined;
|
||||
HEADERS?: Headers | Resolver<Headers> | undefined;
|
||||
ENCODE_PATH?: ((path: string) => string) | undefined;
|
||||
};
|
||||
|
||||
export const OpenAPI: OpenAPIConfig = {
|
||||
BASE: '',
|
||||
VERSION: '0.1.0',
|
||||
WITH_CREDENTIALS: false,
|
||||
CREDENTIALS: 'include',
|
||||
TOKEN: undefined,
|
||||
USERNAME: undefined,
|
||||
PASSWORD: undefined,
|
||||
HEADERS: undefined,
|
||||
ENCODE_PATH: undefined,
|
||||
};
|
|
@ -0,0 +1,322 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import { ApiError } from './ApiError';
|
||||
import type { ApiRequestOptions } from './ApiRequestOptions';
|
||||
import type { ApiResult } from './ApiResult';
|
||||
import { CancelablePromise } from './CancelablePromise';
|
||||
import type { OnCancel } from './CancelablePromise';
|
||||
import type { OpenAPIConfig } from './OpenAPI';
|
||||
|
||||
export const isDefined = <T>(value: T | null | undefined): value is Exclude<T, null | undefined> => {
|
||||
return value !== undefined && value !== null;
|
||||
};
|
||||
|
||||
export const isString = (value: any): value is string => {
|
||||
return typeof value === 'string';
|
||||
};
|
||||
|
||||
export const isStringWithValue = (value: any): value is string => {
|
||||
return isString(value) && value !== '';
|
||||
};
|
||||
|
||||
export const isBlob = (value: any): value is Blob => {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
typeof value.type === 'string' &&
|
||||
typeof value.stream === 'function' &&
|
||||
typeof value.arrayBuffer === 'function' &&
|
||||
typeof value.constructor === 'function' &&
|
||||
typeof value.constructor.name === 'string' &&
|
||||
/^(Blob|File)$/.test(value.constructor.name) &&
|
||||
/^(Blob|File)$/.test(value[Symbol.toStringTag])
|
||||
);
|
||||
};
|
||||
|
||||
export const isFormData = (value: any): value is FormData => {
|
||||
return value instanceof FormData;
|
||||
};
|
||||
|
||||
export const base64 = (str: string): string => {
|
||||
try {
|
||||
return btoa(str);
|
||||
} catch (err) {
|
||||
// @ts-ignore
|
||||
return Buffer.from(str).toString('base64');
|
||||
}
|
||||
};
|
||||
|
||||
export const getQueryString = (params: Record<string, any>): string => {
|
||||
const qs: string[] = [];
|
||||
|
||||
const append = (key: string, value: any) => {
|
||||
qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
||||
};
|
||||
|
||||
const process = (key: string, value: any) => {
|
||||
if (isDefined(value)) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(v => {
|
||||
process(key, v);
|
||||
});
|
||||
} else if (typeof value === 'object') {
|
||||
Object.entries(value).forEach(([k, v]) => {
|
||||
process(`${key}[${k}]`, v);
|
||||
});
|
||||
} else {
|
||||
append(key, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
process(key, value);
|
||||
});
|
||||
|
||||
if (qs.length > 0) {
|
||||
return `?${qs.join('&')}`;
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => {
|
||||
const encoder = config.ENCODE_PATH || encodeURI;
|
||||
|
||||
const path = options.url
|
||||
.replace('{api-version}', config.VERSION)
|
||||
.replace(/{(.*?)}/g, (substring: string, group: string) => {
|
||||
if (options.path?.hasOwnProperty(group)) {
|
||||
return encoder(String(options.path[group]));
|
||||
}
|
||||
return substring;
|
||||
});
|
||||
|
||||
const url = `${config.BASE}${path}`;
|
||||
if (options.query) {
|
||||
return `${url}${getQueryString(options.query)}`;
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
export const getFormData = (options: ApiRequestOptions): FormData | undefined => {
|
||||
if (options.formData) {
|
||||
const formData = new FormData();
|
||||
|
||||
const process = (key: string, value: any) => {
|
||||
if (isString(value) || isBlob(value)) {
|
||||
formData.append(key, value);
|
||||
} else {
|
||||
formData.append(key, JSON.stringify(value));
|
||||
}
|
||||
};
|
||||
|
||||
Object.entries(options.formData)
|
||||
.filter(([_, value]) => isDefined(value))
|
||||
.forEach(([key, value]) => {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(v => process(key, v));
|
||||
} else {
|
||||
process(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
return formData;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
type Resolver<T> = (options: ApiRequestOptions) => Promise<T>;
|
||||
|
||||
export const resolve = async <T>(options: ApiRequestOptions, resolver?: T | Resolver<T>): Promise<T | undefined> => {
|
||||
if (typeof resolver === 'function') {
|
||||
return (resolver as Resolver<T>)(options);
|
||||
}
|
||||
return resolver;
|
||||
};
|
||||
|
||||
export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise<Headers> => {
|
||||
const [token, username, password, additionalHeaders] = await Promise.all([
|
||||
resolve(options, config.TOKEN),
|
||||
resolve(options, config.USERNAME),
|
||||
resolve(options, config.PASSWORD),
|
||||
resolve(options, config.HEADERS),
|
||||
]);
|
||||
|
||||
const headers = Object.entries({
|
||||
Accept: 'application/json',
|
||||
...additionalHeaders,
|
||||
...options.headers,
|
||||
})
|
||||
.filter(([_, value]) => isDefined(value))
|
||||
.reduce((headers, [key, value]) => ({
|
||||
...headers,
|
||||
[key]: String(value),
|
||||
}), {} as Record<string, string>);
|
||||
|
||||
if (isStringWithValue(token)) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
if (isStringWithValue(username) && isStringWithValue(password)) {
|
||||
const credentials = base64(`${username}:${password}`);
|
||||
headers['Authorization'] = `Basic ${credentials}`;
|
||||
}
|
||||
|
||||
if (options.body !== undefined) {
|
||||
if (options.mediaType) {
|
||||
headers['Content-Type'] = options.mediaType;
|
||||
} else if (isBlob(options.body)) {
|
||||
headers['Content-Type'] = options.body.type || 'application/octet-stream';
|
||||
} else if (isString(options.body)) {
|
||||
headers['Content-Type'] = 'text/plain';
|
||||
} else if (!isFormData(options.body)) {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
}
|
||||
|
||||
return new Headers(headers);
|
||||
};
|
||||
|
||||
export const getRequestBody = (options: ApiRequestOptions): any => {
|
||||
if (options.body !== undefined) {
|
||||
if (options.mediaType?.includes('/json')) {
|
||||
return JSON.stringify(options.body)
|
||||
} else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) {
|
||||
return options.body;
|
||||
} else {
|
||||
return JSON.stringify(options.body);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const sendRequest = async (
|
||||
config: OpenAPIConfig,
|
||||
options: ApiRequestOptions,
|
||||
url: string,
|
||||
body: any,
|
||||
formData: FormData | undefined,
|
||||
headers: Headers,
|
||||
onCancel: OnCancel
|
||||
): Promise<Response> => {
|
||||
const controller = new AbortController();
|
||||
|
||||
const request: RequestInit = {
|
||||
headers,
|
||||
body: body ?? formData,
|
||||
method: options.method,
|
||||
signal: controller.signal,
|
||||
};
|
||||
|
||||
if (config.WITH_CREDENTIALS) {
|
||||
request.credentials = config.CREDENTIALS;
|
||||
}
|
||||
|
||||
onCancel(() => controller.abort());
|
||||
|
||||
return await fetch(url, request);
|
||||
};
|
||||
|
||||
export const getResponseHeader = (response: Response, responseHeader?: string): string | undefined => {
|
||||
if (responseHeader) {
|
||||
const content = response.headers.get(responseHeader);
|
||||
if (isString(content)) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getResponseBody = async (response: Response): Promise<any> => {
|
||||
if (response.status !== 204) {
|
||||
try {
|
||||
const contentType = response.headers.get('Content-Type');
|
||||
if (contentType) {
|
||||
const jsonTypes = ['application/json', 'application/problem+json']
|
||||
const isJSON = jsonTypes.some(type => contentType.toLowerCase().startsWith(type));
|
||||
if (isJSON) {
|
||||
return await response.json();
|
||||
} else {
|
||||
return await response.text();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => {
|
||||
const errors: Record<number, string> = {
|
||||
400: 'Bad Request',
|
||||
401: 'Unauthorized',
|
||||
403: 'Forbidden',
|
||||
404: 'Not Found',
|
||||
500: 'Internal Server Error',
|
||||
502: 'Bad Gateway',
|
||||
503: 'Service Unavailable',
|
||||
...options.errors,
|
||||
}
|
||||
|
||||
const error = errors[result.status];
|
||||
if (error) {
|
||||
throw new ApiError(options, result, error);
|
||||
}
|
||||
|
||||
if (!result.ok) {
|
||||
const errorStatus = result.status ?? 'unknown';
|
||||
const errorStatusText = result.statusText ?? 'unknown';
|
||||
const errorBody = (() => {
|
||||
try {
|
||||
return JSON.stringify(result.body, null, 2);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
})();
|
||||
|
||||
throw new ApiError(options, result,
|
||||
`Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Request method
|
||||
* @param config The OpenAPI configuration object
|
||||
* @param options The request options from the service
|
||||
* @returns CancelablePromise<T>
|
||||
* @throws ApiError
|
||||
*/
|
||||
export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise<T> => {
|
||||
return new CancelablePromise(async (resolve, reject, onCancel) => {
|
||||
try {
|
||||
const url = getUrl(config, options);
|
||||
const formData = getFormData(options);
|
||||
const body = getRequestBody(options);
|
||||
const headers = await getHeaders(config, options);
|
||||
|
||||
if (!onCancel.isCancelled) {
|
||||
const response = await sendRequest(config, options, url, body, formData, headers, onCancel);
|
||||
const responseBody = await getResponseBody(response);
|
||||
const responseHeader = getResponseHeader(response, options.responseHeader);
|
||||
|
||||
const result: ApiResult = {
|
||||
url,
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body: responseHeader ?? responseBody,
|
||||
};
|
||||
|
||||
catchErrorCodes(options, result);
|
||||
|
||||
resolve(result.body);
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export { AvailabilitfClient } from './AvailabilitfClient';
|
||||
|
||||
export { ApiError } from './core/ApiError';
|
||||
export { BaseHttpRequest } from './core/BaseHttpRequest';
|
||||
export { CancelablePromise, CancelError } from './core/CancelablePromise';
|
||||
export { OpenAPI } from './core/OpenAPI';
|
||||
export type { OpenAPIConfig } from './core/OpenAPI';
|
||||
|
||||
export type { AddPlayerJson } from './models/AddPlayerJson';
|
||||
export type { CreateTeamJson } from './models/CreateTeamJson';
|
||||
export type { PutScheduleForm } from './models/PutScheduleForm';
|
||||
export type { RoleSchema } from './models/RoleSchema';
|
||||
export { TeamRole } from './models/TeamRole';
|
||||
export type { TeamSchema } from './models/TeamSchema';
|
||||
export type { ValidationError } from './models/ValidationError';
|
||||
export type { ValidationErrorElement } from './models/ValidationErrorElement';
|
||||
export type { ViewAvailablePlayersForm } from './models/ViewAvailablePlayersForm';
|
||||
export type { ViewScheduleForm } from './models/ViewScheduleForm';
|
||||
export type { ViewScheduleResponse } from './models/ViewScheduleResponse';
|
||||
export type { ViewTeamMembersResponse } from './models/ViewTeamMembersResponse';
|
||||
export type { ViewTeamMembersResponseList } from './models/ViewTeamMembersResponseList';
|
||||
export type { ViewTeamResponse } from './models/ViewTeamResponse';
|
||||
export type { ViewTeamsResponse } from './models/ViewTeamsResponse';
|
||||
|
||||
export { DefaultService } from './services/DefaultService';
|
|
@ -0,0 +1,10 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { TeamRole } from './TeamRole';
|
||||
export type AddPlayerJson = {
|
||||
isTeamLeader?: boolean;
|
||||
teamRole?: TeamRole;
|
||||
};
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type CreateTeamJson = {
|
||||
discordWebhookUrl?: string;
|
||||
leagueTimezone: string;
|
||||
teamName: string;
|
||||
};
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type PutScheduleForm = {
|
||||
availability: Array<number>;
|
||||
teamId: number;
|
||||
windowSizeDays?: number;
|
||||
windowStart: string;
|
||||
};
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type RoleSchema = {
|
||||
isMain: boolean;
|
||||
role: string;
|
||||
};
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* An enumeration.
|
||||
*/
|
||||
export enum TeamRole {
|
||||
'_0' = 0,
|
||||
'_1' = 1,
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type TeamSchema = {
|
||||
discordWebhookUrl?: string;
|
||||
id: number;
|
||||
teamName: string;
|
||||
};
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ValidationErrorElement } from './ValidationErrorElement';
|
||||
/**
|
||||
* Model of a validation error response.
|
||||
*/
|
||||
export type ValidationError = Array<ValidationErrorElement>;
|
|
@ -0,0 +1,14 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Model of a validation error response element.
|
||||
*/
|
||||
export type ValidationErrorElement = {
|
||||
ctx?: Record<string, any>;
|
||||
loc: Array<string>;
|
||||
msg: string;
|
||||
type: string;
|
||||
};
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type ViewAvailablePlayersForm = {
|
||||
startTime: string;
|
||||
teamId: number;
|
||||
};
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type ViewScheduleForm = {
|
||||
teamId: number;
|
||||
windowSizeDays?: number;
|
||||
windowStart: string;
|
||||
};
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type ViewScheduleResponse = {
|
||||
availability: Array<number>;
|
||||
};
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { RoleSchema } from './RoleSchema';
|
||||
export type ViewTeamMembersResponse = {
|
||||
roles: Array<RoleSchema>;
|
||||
steamId: string;
|
||||
username: string;
|
||||
};
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ViewTeamMembersResponse } from './ViewTeamMembersResponse';
|
||||
export type ViewTeamMembersResponseList = Array<ViewTeamMembersResponse>;
|
|
@ -0,0 +1,9 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { TeamSchema } from './TeamSchema';
|
||||
export type ViewTeamResponse = {
|
||||
team: TeamSchema;
|
||||
};
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { TeamSchema } from './TeamSchema';
|
||||
export type ViewTeamsResponse = {
|
||||
teams: Array<TeamSchema>;
|
||||
};
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { AddPlayerJson } from '../models/AddPlayerJson';
|
||||
import type { CreateTeamJson } from '../models/CreateTeamJson';
|
||||
import type { PutScheduleForm } from '../models/PutScheduleForm';
|
||||
import type { ViewScheduleResponse } from '../models/ViewScheduleResponse';
|
||||
import type { ViewTeamMembersResponseList } from '../models/ViewTeamMembersResponseList';
|
||||
import type { ViewTeamResponse } from '../models/ViewTeamResponse';
|
||||
import type { ViewTeamsResponse } from '../models/ViewTeamsResponse';
|
||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||
import type { BaseHttpRequest } from '../core/BaseHttpRequest';
|
||||
export class DefaultService {
|
||||
constructor(public readonly httpRequest: BaseHttpRequest) {}
|
||||
/**
|
||||
* debug_set_cookie <GET>
|
||||
* @returns void
|
||||
* @throws ApiError
|
||||
*/
|
||||
public getApiDebugSetCookie(): CancelablePromise<void> {
|
||||
return this.httpRequest.request({
|
||||
method: 'GET',
|
||||
url: '/api/debug/set-cookie',
|
||||
});
|
||||
}
|
||||
/**
|
||||
* debug_set_cookie <POST>
|
||||
* @returns void
|
||||
* @throws ApiError
|
||||
*/
|
||||
public postApiDebugSetCookie(): CancelablePromise<void> {
|
||||
return this.httpRequest.request({
|
||||
method: 'POST',
|
||||
url: '/api/debug/set-cookie',
|
||||
});
|
||||
}
|
||||
/**
|
||||
* logout <DELETE>
|
||||
* @returns void
|
||||
* @throws ApiError
|
||||
*/
|
||||
public deleteApiLogin(): CancelablePromise<void> {
|
||||
return this.httpRequest.request({
|
||||
method: 'DELETE',
|
||||
url: '/api/login/',
|
||||
});
|
||||
}
|
||||
/**
|
||||
* index <GET>
|
||||
* @returns void
|
||||
* @throws ApiError
|
||||
*/
|
||||
public getApiLogin(): CancelablePromise<void> {
|
||||
return this.httpRequest.request({
|
||||
method: 'GET',
|
||||
url: '/api/login/',
|
||||
});
|
||||
}
|
||||
/**
|
||||
* steam_authenticate <POST>
|
||||
* @returns void
|
||||
* @throws ApiError
|
||||
*/
|
||||
public postApiLoginAuthenticate(): CancelablePromise<void> {
|
||||
return this.httpRequest.request({
|
||||
method: 'POST',
|
||||
url: '/api/login/authenticate',
|
||||
});
|
||||
}
|
||||
/**
|
||||
* get <GET>
|
||||
* @param windowStart
|
||||
* @param teamId
|
||||
* @param windowSizeDays
|
||||
* @returns ViewScheduleResponse OK
|
||||
* @throws ApiError
|
||||
*/
|
||||
public getApiSchedule(
|
||||
windowStart: string,
|
||||
teamId: number,
|
||||
windowSizeDays: number = 7,
|
||||
): CancelablePromise<ViewScheduleResponse> {
|
||||
return this.httpRequest.request({
|
||||
method: 'GET',
|
||||
url: '/api/schedule/',
|
||||
query: {
|
||||
'windowStart': windowStart,
|
||||
'teamId': teamId,
|
||||
'windowSizeDays': windowSizeDays,
|
||||
},
|
||||
errors: {
|
||||
422: `Unprocessable Entity`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* put <PUT>
|
||||
* @param requestBody
|
||||
* @returns void
|
||||
* @throws ApiError
|
||||
*/
|
||||
public putApiSchedule(
|
||||
requestBody?: PutScheduleForm,
|
||||
): CancelablePromise<void> {
|
||||
return this.httpRequest.request({
|
||||
method: 'PUT',
|
||||
url: '/api/schedule/',
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
});
|
||||
}
|
||||
/**
|
||||
* view_available <GET>
|
||||
* @param startTime
|
||||
* @param teamId
|
||||
* @returns void
|
||||
* @throws ApiError
|
||||
*/
|
||||
public getApiScheduleViewAvailable(
|
||||
startTime: string,
|
||||
teamId: number,
|
||||
): CancelablePromise<void> {
|
||||
return this.httpRequest.request({
|
||||
method: 'GET',
|
||||
url: '/api/schedule/view-available',
|
||||
query: {
|
||||
'startTime': startTime,
|
||||
'teamId': teamId,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* create_team <POST>
|
||||
* @param requestBody
|
||||
* @returns ViewTeamResponse OK
|
||||
* @throws ApiError
|
||||
*/
|
||||
public createTeam(
|
||||
requestBody?: CreateTeamJson,
|
||||
): CancelablePromise<ViewTeamResponse> {
|
||||
return this.httpRequest.request({
|
||||
method: 'POST',
|
||||
url: '/api/team/',
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
403: `Forbidden`,
|
||||
422: `Unprocessable Entity`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* view_teams <GET>
|
||||
* @returns ViewTeamsResponse OK
|
||||
* @throws ApiError
|
||||
*/
|
||||
public getTeams(): CancelablePromise<ViewTeamsResponse> {
|
||||
return this.httpRequest.request({
|
||||
method: 'GET',
|
||||
url: '/api/team/all/',
|
||||
errors: {
|
||||
403: `Forbidden`,
|
||||
404: `Not Found`,
|
||||
422: `Unprocessable Entity`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* delete_team <DELETE>
|
||||
* @param teamId
|
||||
* @returns any OK
|
||||
* @throws ApiError
|
||||
*/
|
||||
public deleteTeam(
|
||||
teamId: string,
|
||||
): CancelablePromise<any> {
|
||||
return this.httpRequest.request({
|
||||
method: 'DELETE',
|
||||
url: '/api/team/id/{team_id}/',
|
||||
path: {
|
||||
'team_id': teamId,
|
||||
},
|
||||
errors: {
|
||||
403: `Forbidden`,
|
||||
404: `Not Found`,
|
||||
422: `Unprocessable Entity`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* view_team <GET>
|
||||
* @param teamId
|
||||
* @returns ViewTeamResponse OK
|
||||
* @throws ApiError
|
||||
*/
|
||||
public getTeam(
|
||||
teamId: string,
|
||||
): CancelablePromise<ViewTeamResponse> {
|
||||
return this.httpRequest.request({
|
||||
method: 'GET',
|
||||
url: '/api/team/id/{team_id}/',
|
||||
path: {
|
||||
'team_id': teamId,
|
||||
},
|
||||
errors: {
|
||||
403: `Forbidden`,
|
||||
404: `Not Found`,
|
||||
422: `Unprocessable Entity`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* add_player <PUT>
|
||||
* @param teamId
|
||||
* @param playerId
|
||||
* @param requestBody
|
||||
* @returns any OK
|
||||
* @throws ApiError
|
||||
*/
|
||||
public createOrUpdatePlayer(
|
||||
teamId: string,
|
||||
playerId: string,
|
||||
requestBody?: AddPlayerJson,
|
||||
): CancelablePromise<any> {
|
||||
return this.httpRequest.request({
|
||||
method: 'PUT',
|
||||
url: '/api/team/id/{team_id}/player/{player_id}/',
|
||||
path: {
|
||||
'team_id': teamId,
|
||||
'player_id': playerId,
|
||||
},
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
403: `Forbidden`,
|
||||
404: `Not Found`,
|
||||
422: `Unprocessable Entity`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* view_team_members <GET>
|
||||
* @param teamId
|
||||
* @returns ViewTeamMembersResponseList OK
|
||||
* @throws ApiError
|
||||
*/
|
||||
public getTeamMembers(
|
||||
teamId: string,
|
||||
): CancelablePromise<ViewTeamMembersResponseList> {
|
||||
return this.httpRequest.request({
|
||||
method: 'GET',
|
||||
url: '/api/team/id/{team_id}/players',
|
||||
path: {
|
||||
'team_id': teamId,
|
||||
},
|
||||
errors: {
|
||||
403: `Forbidden`,
|
||||
404: `Not Found`,
|
||||
422: `Unprocessable Entity`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ const model = defineModel();
|
|||
|
||||
const props = defineProps({
|
||||
options: Array<String>,
|
||||
isDisabled: Boolean,
|
||||
});
|
||||
|
||||
const isOpen = ref(false);
|
||||
|
@ -18,15 +19,15 @@ function selectOption(index) {
|
|||
|
||||
<template>
|
||||
<div :class="{ 'dropdown-container': true, 'is-open': isOpen }">
|
||||
<button @click="isOpen = !isOpen">
|
||||
<button @click="isOpen = !isOpen" :disabled="isDisabled">
|
||||
{{ selectedOption }}
|
||||
<i class="bi bi-caret-down-fill"></i>
|
||||
</button>
|
||||
<ul class="dropdown" v-if="isOpen" @blur="isOpen = false">
|
||||
<li v-for="(option, i) in options" :key="i" @click="selectOption(i)">
|
||||
<button :class="{ 'is-selected': i == model }">
|
||||
<option :class="{ 'is-selected': i == model, 'option': true }">
|
||||
{{ option }}
|
||||
</button>
|
||||
</option>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -38,7 +39,7 @@ function selectOption(index) {
|
|||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dropdown-container button {
|
||||
.dropdown-container .option {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
@ -53,7 +54,19 @@ function selectOption(index) {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-container button:hover {
|
||||
.dropdown-container .option {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.dropdown-container .option:first-child {
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
.dropdown-container .option:last-child {
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
.dropdown-container .option:hover {
|
||||
background-color: var(--crust);
|
||||
}
|
||||
|
||||
|
@ -76,14 +89,14 @@ ul.dropdown > li {
|
|||
list-style-type: none;
|
||||
}
|
||||
|
||||
.dropdown li > button {
|
||||
.dropdown li > .option {
|
||||
padding: 8px 16px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.dropdown li > button.is-selected {
|
||||
.dropdown li > .option.is-selected {
|
||||
background-color: var(--accent-transparent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
|
|
@ -226,7 +226,7 @@ onUnmounted(() => {
|
|||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, defineModel, defineProps, ref } from "vue";
|
||||
|
||||
const model = defineModel();
|
||||
|
||||
const props = defineProps({
|
||||
options: Array<String>,
|
||||
isDisabled: Boolean,
|
||||
});
|
||||
|
||||
const isOpen = ref(false);
|
||||
const selectedOption = computed(() => props.options[model.value]);
|
||||
|
||||
function selectOption(index) {
|
||||
model.value = index;
|
||||
isOpen.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="{ 'dropdown-container': true, 'is-open': isOpen }">
|
||||
<button @click="isOpen = !isOpen" :disabled="isDisabled">
|
||||
{{ selectedOption }}
|
||||
<i class="bi bi-caret-down-fill"></i>
|
||||
</button>
|
||||
<ul class="dropdown" v-if="isOpen" @blur="isOpen = false">
|
||||
<li v-for="(option, i) in options" :key="i" @click="selectOption(i)">
|
||||
<button :class="{ 'is-selected': i == model }">
|
||||
{{ option }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dropdown-container {
|
||||
display: inline-block;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dropdown-container button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
padding: 4px;
|
||||
transition-duration: 200ms;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-container button:hover {
|
||||
background-color: var(--crust);
|
||||
}
|
||||
|
||||
.dropdown-container.is-open ul.dropdown {
|
||||
box-shadow: 1px 1px 8px var(--shadow);
|
||||
}
|
||||
|
||||
ul.dropdown {
|
||||
display: block;
|
||||
background-color: var(--base);
|
||||
position: absolute;
|
||||
margin-top: 8px;
|
||||
padding: 0;
|
||||
z-index: 2;
|
||||
border-radius: 8px;
|
||||
overflow: none;
|
||||
}
|
||||
|
||||
ul.dropdown > li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.dropdown li > button {
|
||||
padding: 8px 16px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.dropdown li > button.is-selected {
|
||||
background-color: var(--accent-transparent);
|
||||
color: var(--accent);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,125 @@
|
|||
<script setup lang="ts">
|
||||
import type { PlayerTeamRole } from "../player";
|
||||
import { computed, type PropType } from "vue";
|
||||
import { useRosterStore } from "../stores/roster";
|
||||
import { type ViewTeamMembersResponse } from "@/client";
|
||||
|
||||
const props = defineProps({
|
||||
player: Object as PropType<ViewTeamMembersResponse>,
|
||||
});
|
||||
|
||||
const rosterStore = useRosterStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr class="player-card">
|
||||
<td>
|
||||
<div class="status flex-middle" :availability="player.availability">
|
||||
<span class="dot"></span>
|
||||
<h3>
|
||||
{{ player.username }}
|
||||
</h3>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="role-icons flex-middle">
|
||||
<i
|
||||
v-for="role in player.roles"
|
||||
:class="{
|
||||
[rosterStore.roleIcons[role.role]]: true,
|
||||
main: role.is_main,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{{ player.playtime.toFixed(1) }} hours
|
||||
</td>
|
||||
<td>
|
||||
{{ new Date(player.created_at).toLocaleString() }}
|
||||
</td>
|
||||
<td>
|
||||
<div class="edit-group">
|
||||
<button>
|
||||
<i class="bi bi-pencil-fill edit-icon" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.player-card {
|
||||
border-radius: 8px;
|
||||
user-select: none;
|
||||
gap: 1em;
|
||||
align-items: center;
|
||||
border: 2px solid white;
|
||||
box-shadow: 1px 1px 8px var(--surface-0);
|
||||
}
|
||||
|
||||
.player-card > td {
|
||||
padding: 1em 2em;
|
||||
}
|
||||
|
||||
.player-card h3 {
|
||||
font-weight: 600;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
.dot {
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
background-color: var(--overlay-0);
|
||||
}
|
||||
|
||||
.status[availability="0"] h3 {
|
||||
color: var(--overlay-0);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.status[availability="1"] .dot {
|
||||
background-color: var(--yellow);
|
||||
}
|
||||
|
||||
.status[availability="2"] .dot {
|
||||
background-color: var(--green);
|
||||
}
|
||||
|
||||
.flex-middle {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.role-icons {
|
||||
font-size: 24px;
|
||||
line-height: 0;
|
||||
color: var(--overlay-0);
|
||||
}
|
||||
|
||||
.role-icons i.main {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.edit-group {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.edit-group > button {
|
||||
background-color: transparent;
|
||||
opacity: 0;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.edit-group > button:hover {
|
||||
background-color: var(--surface-0);
|
||||
}
|
||||
|
||||
.player-card:hover .edit-group > button {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,51 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted } from "vue";
|
||||
import { useTeamsStore } from "../stores/teams";
|
||||
import { RouterLink } from "vue-router";
|
||||
|
||||
const teams = useTeamsStore();
|
||||
|
||||
onMounted(() => {
|
||||
teams.fetchTeams();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside>
|
||||
<div>
|
||||
<div class="teams-header">
|
||||
<h3>Your Teams</h3>
|
||||
<RouterLink to="/team/register">
|
||||
<button class="small accent">
|
||||
<i class="bi bi-plus-circle-fill margin"></i>
|
||||
New
|
||||
</button>
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div
|
||||
v-if="teams.teams"
|
||||
v-for="team in teams.teams"
|
||||
>
|
||||
<RouterLink :to="'/team/id/' + team.id">
|
||||
{{ team.team_name }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
aside {
|
||||
flex-basis: 256px;
|
||||
}
|
||||
|
||||
.teams-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.teams-header h3 {
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
|
@ -1,14 +1,17 @@
|
|||
import './assets/main.css'
|
||||
import "./assets/main.css";
|
||||
import "vue-select/dist/vue-select.css";
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import VueSelect from "vue-select";
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.component("v-select", VueSelect);
|
||||
|
||||
app.mount('#app')
|
||||
app.mount("#app")
|
||||
|
|
|
@ -3,6 +3,8 @@ import HomeView from "../views/HomeView.vue";
|
|||
import ScheduleView from "../views/ScheduleView.vue";
|
||||
import RosterBuilderView from "../views/RosterBuilderView.vue";
|
||||
import LoginView from "../views/LoginView.vue";
|
||||
import TeamRegistrationView from "../views/TeamRegistrationView.vue";
|
||||
import TeamDetailsView from "../views/TeamDetailsView.vue";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
|
@ -27,6 +29,16 @@ const router = createRouter({
|
|||
name: "roster-builder",
|
||||
component: RosterBuilderView
|
||||
},
|
||||
{
|
||||
path: "/team/register",
|
||||
name: "team-registration",
|
||||
component: TeamRegistrationView
|
||||
},
|
||||
{
|
||||
path: "/team/id/:id",
|
||||
name: "team-details",
|
||||
component: TeamDetailsView
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import { AvailabilitfClient } from "@/client";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useClientStore = defineStore("client", () => {
|
||||
const client = new AvailabilitfClient({
|
||||
//BASE: import.meta.env.VITE_API_BASE_URL
|
||||
});
|
||||
|
||||
const calls = new Map<string, Promise<any>>();
|
||||
|
||||
function call<T>(
|
||||
key: string,
|
||||
apiCall: () => Promise<T>,
|
||||
thenOnce?: (result: T) => T
|
||||
): Promise<T> {
|
||||
console.log("Fetching call " + key);
|
||||
if (!calls.has(key)) {
|
||||
const promise = apiCall();
|
||||
calls.set(key, promise);
|
||||
|
||||
// remove from calls once completed
|
||||
promise.finally(() => calls.delete(key));
|
||||
|
||||
// only execute this "then" once if the call was just freshly made
|
||||
if (thenOnce) {
|
||||
promise.then(thenOnce);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
return calls.get(key) as Promise<T>;
|
||||
}
|
||||
|
||||
return {
|
||||
client,
|
||||
call,
|
||||
calls,
|
||||
}
|
||||
});
|
|
@ -177,6 +177,13 @@ export const useRosterStore = defineStore("roster", () => {
|
|||
"Roamer": "tf2-FlankSoldier",
|
||||
"Demoman": "tf2-Demo",
|
||||
"Medic": "tf2-Medic",
|
||||
|
||||
"Role.PocketScout": "tf2-PocketScout",
|
||||
"Role.FlankScout": "tf2-FlankScout",
|
||||
"Role.PocketSoldier": "tf2-PocketSoldier",
|
||||
"Role.Roamer": "tf2-FlankSoldier",
|
||||
"Role.Demoman": "tf2-Demo",
|
||||
"Role.Medic": "tf2-Medic",
|
||||
});
|
||||
|
||||
function selectPlayerForRole(player: PlayerTeamRole, role: string) {
|
||||
|
|
|
@ -2,56 +2,79 @@ import { computed } from "@vue/reactivity";
|
|||
import { defineStore } from "pinia";
|
||||
import { reactive, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useClientStore } from "./client";
|
||||
|
||||
export const useScheduleStore = defineStore("schedule", () => {
|
||||
const client = useClientStore().client;
|
||||
|
||||
const dateStart = ref(new Date(2024, 9, 21, 0, 30));
|
||||
|
||||
const windowStart = computed(() => Math.floor(dateStart.value.getTime() / 1000));
|
||||
|
||||
const availability = reactive(new Array(168));
|
||||
|
||||
availability.fill(0);
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const teamId = computed({
|
||||
get: () => route.query.teamId,
|
||||
get: () => Number(route.query.teamId),
|
||||
set: (value) => router.push({ query: { teamId: value } }),
|
||||
});
|
||||
|
||||
watch(dateStart, () => {
|
||||
availability.fill(0);
|
||||
fetchSchedule();
|
||||
});
|
||||
|
||||
watch(teamId, () => {
|
||||
fetchSchedule();
|
||||
});
|
||||
|
||||
async function fetchSchedule() {
|
||||
return fetch(import.meta.env.VITE_API_BASE_URL + "/schedule?" + new URLSearchParams({
|
||||
window_start: windowStart.value.toString(),
|
||||
team_id: "1",
|
||||
}).toString(),{
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
return client.default.getApiSchedule(
|
||||
Math.floor(dateStart.value.getTime() / 1000).toString(),
|
||||
teamId.value,
|
||||
)
|
||||
.then((response) => {
|
||||
response.availability.forEach((value: number, i: number) => {
|
||||
response.availability.forEach((value, i) => {
|
||||
availability[i] = value;
|
||||
});
|
||||
return response;
|
||||
});
|
||||
//return fetch(import.meta.env.VITE_API_BASE_URL + "/schedule?" + new URLSearchParams({
|
||||
// window_start: windowStart.value.toString(),
|
||||
// team_id: teamId.toString(),
|
||||
//}).toString(),{
|
||||
// credentials: "include",
|
||||
// })
|
||||
// .then((response) => response.json())
|
||||
// .then((response) => {
|
||||
// response.availability.forEach((value: number, i: number) => {
|
||||
// availability[i] = value;
|
||||
// });
|
||||
// return response;
|
||||
// });
|
||||
}
|
||||
|
||||
async function saveSchedule() {
|
||||
return fetch(import.meta.env.VITE_API_BASE_URL + "/schedule", {
|
||||
method: "PUT",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
window_start: Math.floor(dateStart.value.getTime() / 1000),
|
||||
team_id: 1,
|
||||
availability: availability,
|
||||
})
|
||||
return client.default.putApiSchedule({
|
||||
windowStart: Math.floor(dateStart.value.getTime() / 1000).toString(),
|
||||
teamId: teamId.value,
|
||||
availability,
|
||||
});
|
||||
//return fetch(import.meta.env.VITE_API_BASE_URL + "/schedule", {
|
||||
// method: "PUT",
|
||||
// credentials: "include",
|
||||
// headers: {
|
||||
// "Content-Type": "application/json",
|
||||
// },
|
||||
// body: JSON.stringify({
|
||||
// window_start: Math.floor(dateStart.value.getTime() / 1000),
|
||||
// team_id: teamId.toString(),
|
||||
// availability: availability,
|
||||
// })
|
||||
//});
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -60,5 +83,6 @@ export const useScheduleStore = defineStore("schedule", () => {
|
|||
availability,
|
||||
fetchSchedule,
|
||||
saveSchedule,
|
||||
teamId,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,36 +1,81 @@
|
|||
import Cacheable from "@/cacheable";
|
||||
import { AvailabilitfClient, type TeamSpec, type ViewTeamMembersResponse, type ViewTeamResponse, type ViewTeamsResponse } from "@/client";
|
||||
import { defineStore } from "pinia";
|
||||
import { computed, reactive, ref, type Reactive, type Ref } from "vue";
|
||||
import { useClientStore } from "./client";
|
||||
|
||||
interface Team {
|
||||
id: number,
|
||||
teamName: string,
|
||||
}
|
||||
export type TeamMap = { [id: number]: TeamSpec };
|
||||
|
||||
export const useTeamsStore = defineStore("teams", () => {
|
||||
//const teams: Reactive<Cacheable<Team[]>> =
|
||||
// reactive(new Cacheable<Team[]>([], 0));
|
||||
const teams: Ref<{ [id: number]: Team }> = ref({ });
|
||||
const clientStore = useClientStore();
|
||||
const client = clientStore.client;
|
||||
|
||||
const teams: Reactive<{ [id: number]: TeamSpec }> = reactive({ });
|
||||
const teamMembers: Reactive<{ [id: number]: ViewTeamMembersResponse[] }> = reactive({ });
|
||||
|
||||
const isFetchingTeams = ref(false);
|
||||
|
||||
async function fetchTeams() {
|
||||
return new Promise((res, rej) => {
|
||||
fetch(import.meta.env.VITE_API_BASE_URL + "/team/view", {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((response: Array<any>) => {
|
||||
teams.value = response
|
||||
.reduce((acc, team: Team) => {
|
||||
return { ...acc, [team.id]: team }
|
||||
});
|
||||
res(teams.value);
|
||||
})
|
||||
.catch(() => rej());
|
||||
return clientStore.call(
|
||||
fetchTeams.name,
|
||||
() => client.default.getTeams(),
|
||||
(response) => {
|
||||
response.teams.forEach((team) => {
|
||||
teams[team.id] = team;
|
||||
});
|
||||
return response;
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async function fetchTeam(id: number) {
|
||||
return clientStore.call(
|
||||
fetchTeam.name,
|
||||
() => client.default.getTeam(id.toString()),
|
||||
(response) => {
|
||||
teams[response.team.id] = response.team;
|
||||
return response;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function fetchTeamMembers(id: number) {
|
||||
return clientStore.call(
|
||||
fetchTeam.name,
|
||||
() => client.default.getTeamMembers(id.toString()),
|
||||
(response) => {
|
||||
response = response
|
||||
.map((member): ViewTeamMembersResponse => {
|
||||
// TODO: snake_case to camelCase
|
||||
member.roles = member.roles.sort((a, b) => {
|
||||
if (a.is_main == b.is_main) {
|
||||
return 0;
|
||||
}
|
||||
return a.is_main ? -1 : 1;
|
||||
});
|
||||
return member;
|
||||
});
|
||||
console.log(response);
|
||||
teamMembers[id] = response;
|
||||
return response;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function createTeam(teamName: string, tz: string, webhook?: string) {
|
||||
return await client.default.createTeam({
|
||||
teamName,
|
||||
leagueTimezone: tz,
|
||||
discordWebhookUrl: webhook,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
teams,
|
||||
teamMembers,
|
||||
fetchTeams,
|
||||
}
|
||||
fetchTeam,
|
||||
fetchTeamMembers,
|
||||
createTeam,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import TeamsListSidebar from "../components/TeamsListSidebar.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TeamsListSidebar />
|
||||
<main>
|
||||
<h2>JustGetAHouse</h2>
|
||||
<div>
|
||||
test
|
||||
</div>
|
||||
<h1>Your Teams</h1>
|
||||
</main>
|
||||
</template>
|
||||
|
|
|
@ -2,17 +2,17 @@
|
|||
import AvailabilityGrid from "../components/AvailabilityGrid.vue";
|
||||
import AvailabilityComboBox from "../components/AvailabilityComboBox.vue";
|
||||
import WeekSelectionBox from "../components/WeekSelectionBox.vue";
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { computed, onMounted, reactive, ref, watch } from "vue";
|
||||
import { useTeamsStore } from "../stores/teams";
|
||||
import { useScheduleStore } from "../stores/schedule";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
const teams = useTeamsStore();
|
||||
const teamsStore = useTeamsStore();
|
||||
const schedule = useScheduleStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const options = ref([
|
||||
"TEAM PEPEJA forsenCD",
|
||||
"The Snus Brotherhood",
|
||||
]);
|
||||
const options = ref([ ]);
|
||||
|
||||
const firstHour = computed(() => shouldShowAllHours.value ? 0 : 14);
|
||||
const lastHour = computed(() => shouldShowAllHours.value ? 23 : 22);
|
||||
|
@ -20,13 +20,20 @@ const shouldShowAllHours = ref(false);
|
|||
|
||||
const comboBoxIndex = ref(0);
|
||||
|
||||
//const availability = reactive(new Array(168));
|
||||
const availability = schedule.availability;
|
||||
|
||||
const selectionMode = ref(1);
|
||||
|
||||
const isEditing = ref(false);
|
||||
|
||||
const selectedTeam = ref();
|
||||
|
||||
watch(selectedTeam, (newTeam) => {
|
||||
if (newTeam) {
|
||||
schedule.teamId = newTeam.id;
|
||||
}
|
||||
});
|
||||
|
||||
function saveSchedule() {
|
||||
schedule.saveSchedule()
|
||||
.then(() => {
|
||||
|
@ -35,14 +42,18 @@ function saveSchedule() {
|
|||
}
|
||||
|
||||
onMounted(() => {
|
||||
teams.fetchTeams()
|
||||
teamsStore.fetchTeams()
|
||||
.then((teamsList) => {
|
||||
options.value = Object.values(teamsList);
|
||||
schedule.fetchSchedule()
|
||||
.then(() => {
|
||||
|
||||
});
|
||||
})
|
||||
options.value = Object.values(teamsList.teams);
|
||||
// select team with id in query parameter if exists
|
||||
const queryTeam = teamsList.teams.find(x => x.id == route.query.teamId);
|
||||
console.log(queryTeam);
|
||||
if (queryTeam) {
|
||||
selectedTeam.value = queryTeam;
|
||||
//schedule.teamId = queryTeam.id;
|
||||
schedule.fetchSchedule(schedule.teamId);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -52,7 +63,11 @@ onMounted(() => {
|
|||
<div class="top-menu">
|
||||
<div class="subtext">
|
||||
Availability for
|
||||
<AvailabilityComboBox :options="options" v-model="comboBoxIndex" />
|
||||
<v-select
|
||||
:options="options"
|
||||
label="team_name"
|
||||
v-model="selectedTeam"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<WeekSelectionBox
|
||||
|
@ -60,42 +75,44 @@ onMounted(() => {
|
|||
:is-disabled="isEditing" />
|
||||
</div>
|
||||
</div>
|
||||
<AvailabilityGrid v-model="availability"
|
||||
:selection-mode="selectionMode"
|
||||
:is-disabled="!isEditing"
|
||||
:date-start="schedule.dateStart"
|
||||
:first-hour="firstHour"
|
||||
:last-hour="lastHour"
|
||||
/>
|
||||
<div class="button-group">
|
||||
<button v-if="shouldShowAllHours" @click="shouldShowAllHours = false">
|
||||
Show designated times
|
||||
</button>
|
||||
<button v-else @click="shouldShowAllHours = true">
|
||||
Show all times
|
||||
</button>
|
||||
<template v-if="isEditing">
|
||||
<div class="radio-group">
|
||||
<button
|
||||
:class="{ 'radio': true, 'selected': selectionMode == 1, 'left': true }"
|
||||
@click="selectionMode = 1"
|
||||
>
|
||||
Available if needed
|
||||
</button>
|
||||
<button
|
||||
:class="{ 'radio': true, 'selected': selectionMode == 2, 'right': true }"
|
||||
@click="selectionMode = 2"
|
||||
>
|
||||
Definitely available
|
||||
</button>
|
||||
</div>
|
||||
<button @click="saveSchedule()">
|
||||
<i class="bi bi-check-circle-fill"></i>
|
||||
<div class="grid-container">
|
||||
<AvailabilityGrid v-model="availability"
|
||||
:selection-mode="selectionMode"
|
||||
:is-disabled="!isEditing"
|
||||
:date-start="schedule.dateStart"
|
||||
:first-hour="firstHour"
|
||||
:last-hour="lastHour"
|
||||
/>
|
||||
<div class="button-group">
|
||||
<button v-if="shouldShowAllHours" @click="shouldShowAllHours = false">
|
||||
Show designated times
|
||||
</button>
|
||||
</template>
|
||||
<button v-else class="accent" @click="isEditing = true">
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
</button>
|
||||
<button v-else @click="shouldShowAllHours = true">
|
||||
Show all times
|
||||
</button>
|
||||
<template v-if="isEditing">
|
||||
<div class="radio-group">
|
||||
<button
|
||||
:class="{ 'radio': true, 'selected': selectionMode == 1, 'left': true }"
|
||||
@click="selectionMode = 1"
|
||||
>
|
||||
Available if needed
|
||||
</button>
|
||||
<button
|
||||
:class="{ 'radio': true, 'selected': selectionMode == 2, 'right': true }"
|
||||
@click="selectionMode = 2"
|
||||
>
|
||||
Definitely available
|
||||
</button>
|
||||
</div>
|
||||
<button @click="saveSchedule()">
|
||||
<i class="bi bi-check-circle-fill"></i>
|
||||
</button>
|
||||
</template>
|
||||
<button v-else class="accent" @click="isEditing = true">
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
|
@ -106,7 +123,10 @@ onMounted(() => {
|
|||
|
||||
<style scoped>
|
||||
.schedule-view-container {
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.top-menu {
|
||||
|
@ -115,8 +135,14 @@ onMounted(() => {
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
gap: 8px;
|
||||
justify-content: end;
|
||||
}
|
||||
|
@ -140,10 +166,16 @@ button.radio.selected {
|
|||
}
|
||||
|
||||
button.left {
|
||||
border-radius: 8px 0 0 8px;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
button.right {
|
||||
border-radius: 0 8px 8px 0;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.v-select {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
min-width: 11em;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<script setup lang="ts">
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useTeamsStore } from "../stores/teams";
|
||||
import { computed, onMounted } from "vue";
|
||||
import PlayerTeamCard from "../components/PlayerTeamCard.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const teamsStore = useTeamsStore();
|
||||
|
||||
const team = computed(() => {
|
||||
return teamsStore.teams[route.params.id];
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
teamsStore.fetchTeam(route.params.id)
|
||||
.then(() => teamsStore.fetchTeamMembers(route.params.id));
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<template v-if="team">
|
||||
<h1>
|
||||
{{ team.team_name }}
|
||||
</h1>
|
||||
<table class="member-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Name
|
||||
</th>
|
||||
<th>
|
||||
Roles
|
||||
</th>
|
||||
<th>
|
||||
Playtime on team
|
||||
</th>
|
||||
<th>
|
||||
Joined
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<PlayerTeamCard
|
||||
v-for="member in teamsStore.teamMembers[route.params.id]"
|
||||
:player="member"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
table.member-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.member-table th {
|
||||
text-align: left;
|
||||
padding-left: 2em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/*
|
||||
div.member-grid {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
*/
|
||||
</style>
|
|
@ -0,0 +1,143 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref, watch } from "vue";
|
||||
import { useTeamsStore } from "../stores/teams.ts"
|
||||
import timezones from "../assets/timezones.json";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const teams = useTeamsStore();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const teamName = ref("");
|
||||
|
||||
const timezone = ref(
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone ??
|
||||
"Etc/UTC"
|
||||
);
|
||||
|
||||
const minuteOffset = ref(0);
|
||||
|
||||
watch(minuteOffset, (newValue) => {
|
||||
minuteOffset.value = Math.min(Math.max(0, newValue), 59);
|
||||
});
|
||||
|
||||
const webhook = ref("");
|
||||
|
||||
function createTeam() {
|
||||
teams.createTeam(teamName.value, timezone.value, webhook.value)
|
||||
.then(() => {
|
||||
router.push("/");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<div class="team-registration-container">
|
||||
<h1>Create a new team</h1>
|
||||
<p>
|
||||
Register your team to streamline match scheduling, role assignments,
|
||||
and overall team communication.
|
||||
</p>
|
||||
<div class="form-group margin">
|
||||
<h3>Team Name</h3>
|
||||
<input v-model="teamName" />
|
||||
</div>
|
||||
<div class="form-group margin">
|
||||
<div class="form-group row">
|
||||
<div class="form-group">
|
||||
<h3>
|
||||
Timezone
|
||||
<a
|
||||
class="aside"
|
||||
href="https://nodatime.org/TimeZones"
|
||||
target="_blank"
|
||||
>
|
||||
(view all timezones)
|
||||
</a>
|
||||
</h3>
|
||||
<v-select :options="timezones" v-model="timezone" />
|
||||
</div>
|
||||
<div class="form-group" id="minute-offset-group">
|
||||
<h3>Minute Offset</h3>
|
||||
<input type="number" v-model="minuteOffset" min="0" max="59" />
|
||||
</div>
|
||||
</div>
|
||||
<em class="aside">
|
||||
Matches will be scheduled against {{ timezone }} at
|
||||
{{ minuteOffset }}
|
||||
<span v-if="minuteOffset == 1">
|
||||
minute
|
||||
</span>
|
||||
<span v-else>
|
||||
minutes
|
||||
</span>
|
||||
past the hour.
|
||||
</em>
|
||||
</div>
|
||||
<div class="form-group margin">
|
||||
<h3>
|
||||
Announcements Webhook URL
|
||||
<span class="aside">(optional)</span>
|
||||
</h3>
|
||||
<input v-model="webhook" />
|
||||
</div>
|
||||
<div class="form-group margin">
|
||||
<div class="action-buttons">
|
||||
<button class="accent" @click="createTeam">Create team</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.team-registration-container {
|
||||
align-items: center;
|
||||
max-width: 500px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.team-registration-container h3 {
|
||||
font-size: 11pt;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.team-registration-container .aside {
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.form-group.margin {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-group.row {
|
||||
flex-direction: row;
|
||||
margin: none;
|
||||
}
|
||||
|
||||
#minute-offset-group {
|
||||
flex-grow: unset;
|
||||
flex-shrink: 1;
|
||||
flex-basis: 25%;
|
||||
}
|
||||
|
||||
input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
</style>
|
|
@ -16,7 +16,8 @@ export default defineConfig({
|
|||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:5000',
|
||||
//target: 'http://localhost:5000',
|
||||
target: 'https://on-indirectly-firefly.ngrok-free.app',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
configure: (proxy) => {
|
||||
|
|
Loading…
Reference in New Issue