Compare commits
27 Commits
2c33c9a68d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 87d9291bfb | |||
| c646ae15ee | |||
|
|
8128ba4744 | ||
|
|
19e4372006 | ||
|
|
7fd84005f9 | ||
|
|
a56317fecd | ||
|
|
ecbb16776c | ||
|
|
3008f45dfa | ||
|
|
5baf1228f7 | ||
|
|
aacf06e203 | ||
| 368aa53b9d | |||
| 335577fa4d | |||
| 1f55acecd0 | |||
| c1fedd728b | |||
| 0e9e8d1e0f | |||
|
|
0e1e1932a1 | ||
| fb36ee1a05 | |||
| 9cb3568770 | |||
| d387452044 | |||
| 2cdccc4dd8 | |||
| 8083e7fc5f | |||
| 0fd693aa42 | |||
| edaffc82e4 | |||
| 1dbc2f1ca9 | |||
| 21fa706b45 | |||
| f2ee2b467d | |||
| cdf27f1948 |
546
package-lock.json
generated
546
package-lock.json
generated
@@ -8,23 +8,24 @@
|
||||
"name": "swiss-client",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@angular/animations": "^20.2.1",
|
||||
"@angular/cdk": "^20.2.0",
|
||||
"@angular/common": "^20.2.1",
|
||||
"@angular/compiler": "^20.2.1",
|
||||
"@angular/core": "^20.2.1",
|
||||
"@angular/forms": "^20.2.1",
|
||||
"@angular/material": "^20.2.0",
|
||||
"@angular/material-moment-adapter": "^20.2.0",
|
||||
"@angular/platform-browser": "^20.2.1",
|
||||
"@angular/platform-browser-dynamic": "^20.2.1",
|
||||
"@angular/platform-server": "^20.2.1",
|
||||
"@angular/router": "^20.2.1",
|
||||
"@angular/ssr": "^20.2.0",
|
||||
"@angular/animations": "^20.3.2",
|
||||
"@angular/cdk": "^20.2.5",
|
||||
"@angular/common": "^20.3.2",
|
||||
"@angular/compiler": "^20.3.2",
|
||||
"@angular/core": "^20.3.2",
|
||||
"@angular/forms": "^20.3.2",
|
||||
"@angular/material": "^20.2.5",
|
||||
"@angular/material-moment-adapter": "^20.2.5",
|
||||
"@angular/platform-browser": "^20.3.2",
|
||||
"@angular/platform-browser-dynamic": "^20.3.2",
|
||||
"@angular/platform-server": "^20.3.2",
|
||||
"@angular/router": "^20.3.2",
|
||||
"@angular/ssr": "^20.3.3",
|
||||
"@ng-bootstrap/ng-bootstrap": "^19.0.1",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"bootstrap": "^5.3.7",
|
||||
"express": "^4.21.2",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"moment": "2.30.1",
|
||||
"ngx-cookie-service-ssr": "^20.1.0",
|
||||
"ngx-mask": "^20.0.3",
|
||||
@@ -34,10 +35,10 @@
|
||||
"zone.js": "~0.15.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/build": "^20.2.0",
|
||||
"@angular/cli": "^20.2.0",
|
||||
"@angular/compiler-cli": "^20.2.1",
|
||||
"@angular/localize": "^20.2.1",
|
||||
"@angular/build": "^20.3.3",
|
||||
"@angular/cli": "^20.3.3",
|
||||
"@angular/compiler-cli": "^20.3.2",
|
||||
"@angular/localize": "^20.3.2",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^24.2.1",
|
||||
@@ -273,13 +274,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/architect": {
|
||||
"version": "0.2002.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2002.0.tgz",
|
||||
"integrity": "sha512-PaBXFP1kdUuNtMie0lWnitlYbq8o1gz/s0YIa8oY1X3swOJ7bP6kBfxTb9opV5uXAOkXg2zCdnZ4Eu1aVkgPGw==",
|
||||
"version": "0.2003.3",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.3.tgz",
|
||||
"integrity": "sha512-DOnGyv9g24vaDzf5koLOcVri1kYJIBD9UKiJWOWk4H5cFlcpTXQ+PilPmDq6A+X94Tt4MZHImmKsk6LLRPIwFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "20.2.0",
|
||||
"@angular-devkit/core": "20.3.3",
|
||||
"rxjs": "7.8.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -289,9 +290,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/core": {
|
||||
"version": "20.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.2.0.tgz",
|
||||
"integrity": "sha512-3CM6Zsr09Kf92ItFkxijlnC4+ZOgkxdCk0vFYvuw9UuvTDNwyIqJi6693PRPRbcXgpdY2vs6u99elSvQVmoEEw==",
|
||||
"version": "20.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.3.tgz",
|
||||
"integrity": "sha512-2T5mX2duLapZYPYmXUSUe9VW8Dhu10nVBVvEp31jSE6xvjbPM5mlsv6+fks1E4RjhzvaamY9bm3WgwYwNiEV5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -317,13 +318,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics": {
|
||||
"version": "20.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.2.0.tgz",
|
||||
"integrity": "sha512-TCPIN6Bd04oGuNocETmsd9hzGYrjrivisbMKb0WOuDi3OnCkmWqsPR+QA2kYwTOGqG3HXkz/z3CA0g04M2fgrQ==",
|
||||
"version": "20.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.3.tgz",
|
||||
"integrity": "sha512-LDn39BjyQLAK/DaVamLElMtI0UoCZIs4jKcMEv8PJ/nnBmrYFHVavWPggeFWMycjeXsdX34Msiml88HZWlXypw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "20.2.0",
|
||||
"@angular-devkit/core": "20.3.3",
|
||||
"jsonc-parser": "3.3.1",
|
||||
"magic-string": "0.30.17",
|
||||
"ora": "8.2.0",
|
||||
@@ -336,9 +337,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/animations": {
|
||||
"version": "20.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.2.1.tgz",
|
||||
"integrity": "sha512-g4yLXwXCF7OAahx1xI4FXRwG4dIXfBqHsvlpx2TappaMRpiPp7PfP2cW6l3ox+KRpTWhSvcRqbJyIOWad0f7Rw==",
|
||||
"version": "20.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.2.tgz",
|
||||
"integrity": "sha512-za7onSElEUbaI9iS8j7nKf8FjyvVng6wFsb2ZuHxr71dMgnYkqPfMu0KMP+mkZ3yUVc//7SllXcSkGBHShyCcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@@ -347,19 +348,18 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "20.2.1",
|
||||
"@angular/core": "20.2.1"
|
||||
"@angular/core": "20.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build": {
|
||||
"version": "20.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/build/-/build-20.2.0.tgz",
|
||||
"integrity": "sha512-/Yhqhg01UvX0E+tx4WAeK3AnwpZLqcw+XKTmsPsH5rbqpLKNRR9XsC3PJ4qBFU1u9/Lh13mmmr1+pG2p8ixMug==",
|
||||
"version": "20.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.3.tgz",
|
||||
"integrity": "sha512-WhwAbovHAxDbNeR5jB2IS/SVs+yQg9NETFeJ5f7T3n/414ULkGOhXn+29i1rzwJhf1uqM9lsedcv2tKn1N24/A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "2.3.0",
|
||||
"@angular-devkit/architect": "0.2002.0",
|
||||
"@angular-devkit/architect": "0.2003.3",
|
||||
"@babel/core": "7.28.3",
|
||||
"@babel/helper-annotate-as-pure": "7.27.3",
|
||||
"@babel/helper-split-export-declaration": "7.24.7",
|
||||
@@ -377,12 +377,12 @@
|
||||
"parse5-html-rewriting-stream": "8.0.0",
|
||||
"picomatch": "4.0.3",
|
||||
"piscina": "5.1.3",
|
||||
"rolldown": "1.0.0-beta.32",
|
||||
"rolldown": "1.0.0-beta.38",
|
||||
"sass": "1.90.0",
|
||||
"semver": "7.7.2",
|
||||
"source-map-support": "0.5.21",
|
||||
"tinyglobby": "0.2.14",
|
||||
"vite": "7.1.2",
|
||||
"vite": "7.1.5",
|
||||
"watchpack": "2.4.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -401,7 +401,7 @@
|
||||
"@angular/platform-browser": "^20.0.0",
|
||||
"@angular/platform-server": "^20.0.0",
|
||||
"@angular/service-worker": "^20.0.0",
|
||||
"@angular/ssr": "^20.2.0",
|
||||
"@angular/ssr": "^20.3.3",
|
||||
"karma": "^6.4.0",
|
||||
"less": "^4.2.0",
|
||||
"ng-packagr": "^20.0.0",
|
||||
@@ -464,9 +464,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cdk": {
|
||||
"version": "20.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.0.tgz",
|
||||
"integrity": "sha512-BZkhRMr3nEOHHCzEgKZM537G4aq0VAwoejhYn7oIvY0UU+arHKz+U7Gc44KH5GaAgVLojtJtkFXsArifzYUwzw==",
|
||||
"version": "20.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.5.tgz",
|
||||
"integrity": "sha512-1cpR/5jeKXLR1D+PsEvRn0QhSWD3/AjtbugJF5nlx/7L90YXhNFCmNAxAkdFKSn4YIDoPwMHgvOpS7yb51wohQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parse5": "^8.0.0",
|
||||
@@ -479,19 +479,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli": {
|
||||
"version": "20.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.2.0.tgz",
|
||||
"integrity": "sha512-p62hkuQOxf5kJsVq6AT7B1MHYo1uPGoZV4lf47qOrLjl0WANwfxEgLvyuVgL47ylnINbPnITeeUdoadVn4t1sw==",
|
||||
"version": "20.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.3.tgz",
|
||||
"integrity": "sha512-3c8xCklJ0C0T6ETSncAoXlOYNi3x7vLT3PS56rIaQ0jtlvD4Y+RQakd3+iffVAapvh/JB27WNor8pJRThLZ/jg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/architect": "0.2002.0",
|
||||
"@angular-devkit/core": "20.2.0",
|
||||
"@angular-devkit/schematics": "20.2.0",
|
||||
"@angular-devkit/architect": "0.2003.3",
|
||||
"@angular-devkit/core": "20.3.3",
|
||||
"@angular-devkit/schematics": "20.3.3",
|
||||
"@inquirer/prompts": "7.8.2",
|
||||
"@listr2/prompt-adapter-inquirer": "3.0.1",
|
||||
"@modelcontextprotocol/sdk": "1.17.3",
|
||||
"@schematics/angular": "20.2.0",
|
||||
"@schematics/angular": "20.3.3",
|
||||
"@yarnpkg/lockfile": "1.1.0",
|
||||
"algoliasearch": "5.35.0",
|
||||
"ini": "5.0.0",
|
||||
@@ -553,9 +553,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/common": {
|
||||
"version": "20.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.2.1.tgz",
|
||||
"integrity": "sha512-T6RYnDZA9TyYhj2hUz4set8p4RbBCg6IKUvy6qzdKTl4nn4xQ0XUV7aGBYN4LKiGrse9lzlVUAyXtkhmwuBbCQ==",
|
||||
"version": "20.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.2.tgz",
|
||||
"integrity": "sha512-5V9AzLhCA1dNhF+mvihmdHoZHbEhIb1jNYRA1/JMheR+G7NR8Mznu6RmWaKSWZ4AJeSJN8rizWN2wpVPWTKjSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@@ -564,14 +564,14 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": "20.2.1",
|
||||
"@angular/core": "20.3.2",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/compiler": {
|
||||
"version": "20.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.2.1.tgz",
|
||||
"integrity": "sha512-ghVt1E8xmwjMwqyGRwXYJkr7fz40VEreUSX1q+gEzbGTftVrK1foxPT8jcueIn0ztArDf7+zSMtu314FiJZyYA==",
|
||||
"version": "20.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.2.tgz",
|
||||
"integrity": "sha512-5fSzkPmRomZ9H43c82FJWLwdOi7MICMimP1y1oYJZcUh3jYRhXUrQvD0jifdRVkkgKNjaZYlMr0NkrYQFgFong==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@@ -581,9 +581,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/compiler-cli": {
|
||||
"version": "20.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.2.1.tgz",
|
||||
"integrity": "sha512-VpbcRqNPJvy1L9RDtGGQsQiOrMzxodUWklphbtnh9MrrK6lLuy6Qj2ROiW7vKL9WfLTCXWA24gBAcMAR76dq3Q==",
|
||||
"version": "20.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.2.tgz",
|
||||
"integrity": "sha512-rLox2THiALVQqYGUaxZ6YD8qUoXIOGTw3s0tim9/U65GuXGRtYgG0ZQWYp3yjEBes0Ksx2/15eFPp1Ol4FdEKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "7.28.3",
|
||||
@@ -603,7 +603,7 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler": "20.2.1",
|
||||
"@angular/compiler": "20.3.2",
|
||||
"typescript": ">=5.8 <6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
@@ -613,9 +613,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/core": {
|
||||
"version": "20.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.2.1.tgz",
|
||||
"integrity": "sha512-/hl3AkmdQ62P9ttmfULEDg9GIz7BkzhGv9bSH2ssiU3Y4ax6eM8uQXEbMxBA8OUKOvg1Q4POcNHIiJQgO5t28Q==",
|
||||
"version": "20.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.2.tgz",
|
||||
"integrity": "sha512-88uPgs5LjtnywnQaZE2ShBb1wa8IuD6jWs4nc4feo32QdBc55tjebTBFJSHbi3mUVAp0eS4wI6ITo0YIb01H4g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@@ -624,7 +624,7 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler": "20.2.1",
|
||||
"@angular/compiler": "20.3.2",
|
||||
"rxjs": "^6.5.3 || ^7.4.0",
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
@@ -638,9 +638,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/forms": {
|
||||
"version": "20.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.2.1.tgz",
|
||||
"integrity": "sha512-SfkiHEIFPLtTKeaXUTpRfYnpJDxaeKiTi0YqfvzEjKE68qH0t+pQ4rL0Poch2/l4snP6JS1XzO/nDve1dk3vZw==",
|
||||
"version": "20.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.2.tgz",
|
||||
"integrity": "sha512-ECIbtwc7n9fPbiZXZVaoZpSiOksgcNbZ27oUN9BT7EmoXRzBw6yDL2UX6Ig7pEKhQGyBkKB+TMerRwTDVkkCWg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@@ -649,16 +649,16 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "20.2.1",
|
||||
"@angular/core": "20.2.1",
|
||||
"@angular/platform-browser": "20.2.1",
|
||||
"@angular/common": "20.3.2",
|
||||
"@angular/core": "20.3.2",
|
||||
"@angular/platform-browser": "20.3.2",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/localize": {
|
||||
"version": "20.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.2.1.tgz",
|
||||
"integrity": "sha512-vemzYcHt6YX4FutpgNXiXTpKCMVaJdOG/m2+oJyvnr8KvdlrJKczXraPVY4ER+WJiHC5IQSg24otdSFc0UH2JA==",
|
||||
"version": "20.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.2.tgz",
|
||||
"integrity": "sha512-RZMHgLZV1Aka7rUKvQbg08Dn+dMyVBEGTlUS6/bTDoB1Xq2UE9L8YKmlnEDQyzveO5vTsPvZZQRL4iLc4IokzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "7.28.3",
|
||||
@@ -675,20 +675,20 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler": "20.2.1",
|
||||
"@angular/compiler-cli": "20.2.1"
|
||||
"@angular/compiler": "20.3.2",
|
||||
"@angular/compiler-cli": "20.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/material": {
|
||||
"version": "20.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/material/-/material-20.2.0.tgz",
|
||||
"integrity": "sha512-lwkV1VP7PkC/dhPRXLeYaNtPaIAOjI8/zfpkPnmxJuGA7t7hkngtTxmY+6gElDAtfnle9ZJulW4KndKGr3ng/g==",
|
||||
"version": "20.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/material/-/material-20.2.5.tgz",
|
||||
"integrity": "sha512-zgmHqPykH3InEsVmNSpcVicXLWcYKHIt9Nv/J86K3NZDw4/IQgpfujnr7IotLwc9VpgI4Cl7Jbo95tFVFQAYmw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/cdk": "20.2.0",
|
||||
"@angular/cdk": "20.2.5",
|
||||
"@angular/common": "^20.0.0 || ^21.0.0",
|
||||
"@angular/core": "^20.0.0 || ^21.0.0",
|
||||
"@angular/forms": "^20.0.0 || ^21.0.0",
|
||||
@@ -697,23 +697,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/material-moment-adapter": {
|
||||
"version": "20.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-20.2.0.tgz",
|
||||
"integrity": "sha512-JzzUp3ZHhKkK8SNMPaDf+YUfFmR27WGzm5W4dKGwyLlMk9rXcVY1icHslzsO6ndion6QUJkKBgihpcG5jxvhvg==",
|
||||
"version": "20.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-20.2.5.tgz",
|
||||
"integrity": "sha512-d3unwWDlaleN82gf6EWTQxPrRp46OUIVr/5/lO1xscy5sINTZJcw2OxQikpKZvZiMb+obeKgjzUHiJuLqRJbmQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": "^20.0.0 || ^21.0.0",
|
||||
"@angular/material": "20.2.0",
|
||||
"@angular/material": "20.2.5",
|
||||
"moment": "^2.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/platform-browser": {
|
||||
"version": "20.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.2.1.tgz",
|
||||
"integrity": "sha512-oxDih/A8G7W+I6oAip+sev+kebioYmzhB/NMzF8C8zx/ieVDzatJ+YeEZQt7eDaJLH94S4sIC25SPq3OFIabxg==",
|
||||
"version": "20.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.2.tgz",
|
||||
"integrity": "sha512-d9XcT2UuWZCc0UOtkCcPEnMcOFKNczahamT/Izg3H9jLS3IcT6l0ry23d/Xf0DRwhLYQdOZiG7l8HMZ1sWPMOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@@ -722,9 +722,9 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/animations": "20.2.1",
|
||||
"@angular/common": "20.2.1",
|
||||
"@angular/core": "20.2.1"
|
||||
"@angular/animations": "20.3.2",
|
||||
"@angular/common": "20.3.2",
|
||||
"@angular/core": "20.3.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@angular/animations": {
|
||||
@@ -733,9 +733,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/platform-browser-dynamic": {
|
||||
"version": "20.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.2.1.tgz",
|
||||
"integrity": "sha512-bzBeDnRZFzlA5w5q5GskuKhLgAeJ3pU0B3Ch7V2fhfaAZDOTEczBFvL7I1pcXhDg8Y/8aoz4/OwqnilKLO3FUg==",
|
||||
"version": "20.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.2.tgz",
|
||||
"integrity": "sha512-ehoV67Vxr3ZE8BJ3g7Q4ZLHo3qJVoDUDz/4UeCqmDeOnKxcdD53HTA/pgOO4QhKStUFbzgU19OQD4e6fkP8YoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@@ -744,16 +744,16 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "20.2.1",
|
||||
"@angular/compiler": "20.2.1",
|
||||
"@angular/core": "20.2.1",
|
||||
"@angular/platform-browser": "20.2.1"
|
||||
"@angular/common": "20.3.2",
|
||||
"@angular/compiler": "20.3.2",
|
||||
"@angular/core": "20.3.2",
|
||||
"@angular/platform-browser": "20.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/platform-server": {
|
||||
"version": "20.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.2.1.tgz",
|
||||
"integrity": "sha512-yjos8jgHwcih9lF/CKjbKxzzc83NM+ZoIdm/XSVv9yg+QDnTsc6bLF3QZ+OChCoaCks/UtWUwyM7Ux2g/VvVFA==",
|
||||
"version": "20.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.2.tgz",
|
||||
"integrity": "sha512-D7tf5S5xxQQUDtw/dkMa2XePnxHwyZElN5FQP99ByiEy9PjT1iFjyKuP9jjHsI4Nmi+Juq0F1uo4azPfPaV/3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0",
|
||||
@@ -763,17 +763,17 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "20.2.1",
|
||||
"@angular/compiler": "20.2.1",
|
||||
"@angular/core": "20.2.1",
|
||||
"@angular/platform-browser": "20.2.1",
|
||||
"@angular/common": "20.3.2",
|
||||
"@angular/compiler": "20.3.2",
|
||||
"@angular/core": "20.3.2",
|
||||
"@angular/platform-browser": "20.3.2",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/router": {
|
||||
"version": "20.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-20.2.1.tgz",
|
||||
"integrity": "sha512-f8KfG55EVnFDC9ud+MbxAP6voKi7hVQH4YaqPK0Lm6pyc1Xp0I5W25iRbg+Y1rO1csHKHauBPkUEESEuVGBGqg==",
|
||||
"version": "20.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.2.tgz",
|
||||
"integrity": "sha512-+Crx6QpK00juoNU3A1vbVf4DQ7fduLe3DUdAob6a9Uj+IoWj2Ijd8zUWF8E0cfNNFotJ4Gost0lJORDvqKcC7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@@ -782,16 +782,16 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "20.2.1",
|
||||
"@angular/core": "20.2.1",
|
||||
"@angular/platform-browser": "20.2.1",
|
||||
"@angular/common": "20.3.2",
|
||||
"@angular/core": "20.3.2",
|
||||
"@angular/platform-browser": "20.3.2",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/ssr": {
|
||||
"version": "20.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-20.2.0.tgz",
|
||||
"integrity": "sha512-1IEojPGdXiqtn8ylQ1AZJVw4tgxP1Hn9pYmy9Uk8Qegof8HSbme4DHw5KOTFbnj09WGDfkETCMNVXi0MZRZAJQ==",
|
||||
"version": "20.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-20.3.3.tgz",
|
||||
"integrity": "sha512-DdwpwfNcoiaiaPvcm3aL+k24JWB0OOTq8/oM8HY4gAZbGNTnn8n1gTbTq3qjLt8zFtCWWqVU0+ejBgHIEvmDOw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@@ -1093,21 +1093,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz",
|
||||
"integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz",
|
||||
"integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.0.4",
|
||||
"@emnapi/wasi-threads": "1.1.0",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz",
|
||||
"integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
|
||||
"integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -1116,9 +1116,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz",
|
||||
"integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
|
||||
"integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -2876,16 +2876,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.3.tgz",
|
||||
"integrity": "sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q==",
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.5.tgz",
|
||||
"integrity": "sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.4.5",
|
||||
"@emnapi/runtime": "^1.4.5",
|
||||
"@tybys/wasm-util": "^0.10.0"
|
||||
"@emnapi/core": "^1.5.0",
|
||||
"@emnapi/runtime": "^1.5.0",
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@ng-bootstrap/ng-bootstrap": {
|
||||
@@ -3181,20 +3181,10 @@
|
||||
"node": "^18.17.0 || >=20.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/runtime": {
|
||||
"version": "0.81.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.81.0.tgz",
|
||||
"integrity": "sha512-zm/LDVOq9FEmHiuM8zO4DWirv0VP2Tv2VsgaiHby9nvpq+FVrcqNYgv+TysLKOITQXWZj/roluTxFvpkHP0Iuw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.81.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.81.0.tgz",
|
||||
"integrity": "sha512-CnOqkybZK8z6Gx7Wb1qF7AEnSzbol1WwcIzxYOr8e91LytGOjo0wCpgoYWZo8sdbpqX+X+TJayIzo4Pv0R/KjA==",
|
||||
"version": "0.89.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.89.0.tgz",
|
||||
"integrity": "sha512-yuo+ECPIW5Q9mSeNmCDC2im33bfKuwW18mwkaHMQh8KakHYDzj4ci/q7wxf2qS3dMlVVCIyrs3kFtH5LmnlYnw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -3555,9 +3545,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-Gs+313LfR4Ka3hvifdag9r44WrdKQaohya7ZXUXzARF7yx0atzFlVZjsvxtKAw1Vmtr4hB/RjUD1jf73SW7zDw==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-AE3HFQrjWCKLFZD1Vpiy+qsqTRwwoil1oM5WsKPSmfQ5fif/A+ZtOZetF32erZdsR7qyvns6qHEteEsF6g6rsQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3566,12 +3556,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-W8oMqzGcI7wKPXUtS3WJNXzbghHfNiuM1UBAGpVb+XlUCgYRQJd2PRGP7D3WGql3rR3QEhUvSyAuCBAftPQw6Q==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-RaoWOKc0rrFsVmKOjQpebMY6c6/I7GR1FBc25v7L/R7NlM0166mUotwGEv7vxu7ruXH4SJcFeVrfADFUUXUmmQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3580,12 +3573,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-pM4c4sKUk37noJrnnDkJknLhCsfZu7aWyfe67bD0GQHfzAPjV16wPeD9CmQg4/0vv+5IfHYaa4VE536xbA+W0Q==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-Ymojqc2U35iUc8NFU2XX1WQPfBRRHN6xHcrxAf9WS8BFFBn8pDrH5QPvH1tYs3lDkw6UGGbanr1RGzARqdUp1g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3594,12 +3590,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-M8SUgFlYb5kJJWcFC8gUMRiX4WLFxPKMed3SJ2YrxontgIrEcpizPU8nLNVsRYEStoSfKHKExpQw3OP6fm+5bw==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-0ermTQ//WzSI0nOL3z/LUWMNiE9xeM5cLGxjewPFEexqxV/0uM8/lNp9QageQ8jfc/VO1OURsGw34HYO5PaL8w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3608,12 +3607,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-FuQpbNC/hE//bvv29PFnk0AtpJzdPdYl5CMhlWPovd9g3Kc3lw9TrEPIbL7gRPUdhKAiq6rVaaGvOnXxsa0eww==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-GADxzVUTCTp6EWI52831A29Tt7PukFe94nhg/SUsfkI33oTiNQtPxyLIT/3oRegizGuPSZSlrdBurkjDwxyEUQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -3622,12 +3624,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-hRZygRlaGCjcNTNY9GV7dDI18sG1dK3cc7ujHq72LoDad23zFDUGMQjiSxHWK+/r92iMV+j2MiHbvzayxqynsg==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-SKO7Exl5Yem/OSNoA5uLHzyrptUQ8Hg70kHDxuwEaH0+GUg+SQe9/7PWmc4hFKBMrJGdQtii8WZ0uIz9Dofg5Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3636,12 +3641,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-HzgT6h+CXLs+GKAU0Wvkt3rvcv0CmDBsDjlPhh4GHysOKbG9NjpKYX2zvjx671E9pGbTvcPpwy7gGsy7xpu+8g==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-SOo6+WqhXPBaShLxLT0eCgH17d3Yu1lMAe4mFP0M9Bvr/kfMSOPQXuLxBcbBU9IFM9w3N6qP9xWOHO+oUJvi8Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3650,12 +3658,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-Ab/wbf6gdzphDbsg51UaxsC93foQ7wxhtg0SVCXd25BrV4MAJ1HoDtKN/f4h0maFmJobkqYub2DlmoasUzkvBg==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-yvsQ3CyrodOX+lcoi+lejZGCOvJZa9xTsNB8OzpMDmHeZq3QzJfpYjXSAS6vie70fOkLVJb77UqYO193Cl8XBQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3664,12 +3675,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-VoxqGEfh5A1Yx+zBp/FR5QwAbtzbuvky2SVc+ii4g1gLD4zww6mt/hPi5zG+b88zYPFBKHpxMtsz9cWqXU5V5Q==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-84qzKMwUwikfYeOuJ4Kxm/3z15rt0nFGGQArHYIQQNSTiQdxGHxOkqXtzPFqrVfBJUdxBAf+jYzR1pttFJuWyg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3678,12 +3692,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-qZ1ViyOUDGbiZrSAJ/FIAhYUElDfVxxFW6DLT/w4KeoZN3HsF4jmRP95mXtl51/oGrqzU9l9Q2f7/P4O/o2ZZA==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-QrNiWlce01DYH0rL8K3yUBu+lNzY+B0DyCbIc2Atan6/S6flxOL0ow5DLQvMamOI/oKhrJ4xG+9MkMb9dDHbLQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3692,12 +3709,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-hEkG3wD+f3wytV0lqwb/uCrXc4r4Ny/DWJFJPfQR3VeMWplhWGgSHNwZc2Q7k86Yi36f9NNzzWmrIuvHI9lCVw==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-fnLtHyjwEsG4/aNV3Uv3Qd1ZbdH+CopwJNoV0RgBqrcQB8V6/Qdikd5JKvnO23kb3QvIpP+dAMGZMv1c2PJMzw==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -3705,16 +3725,16 @@
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@napi-rs/wasm-runtime": "^1.0.3"
|
||||
"@napi-rs/wasm-runtime": "^1.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-k3MvDf8SiA7uP2ikP0unNouJ2YCrnwi7xcVW+RDgMp5YXVr3Xu6svmT3HGn0tkCKUuPmf+uy8I5uiHt5qWQbew==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-19cTfnGedem+RY+znA9J6ARBOCEFD4YSjnx0p5jiTm9tR6pHafRfFIfKlTXhun+NL0WWM/M0eb2IfPPYUa8+wg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3723,12 +3743,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-ia32-msvc": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-wAi/FxGh7arDOUG45UmnXE1sZUa0hY4cXAO2qWAjFa3f7bTgz/BqwJ7XN5SUezvAJPNkME4fEpInfnBvM25a0w==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-HcICm4YzFJZV+fI0O0bFLVVlsWvRNo/AB9EfUXvNYbtAxakCnQZ15oq22deFdz6sfi9Y4/SagH2kPU723dhCFA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -3737,12 +3760,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-Ej0i4PZk8ltblZtzVK8ouaGUacUtxRmTm5S9794mdyU/tYxXjAJNseOfxrnHpMWKjMDrOKbqkPqJ52T9NR4LQQ==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-4Qx6cgEPXLb0XsCyLoQcUgYBpfL0sjugftob+zhUH0EOk/NVCAIT+h0NJhY+jn7pFpeKxhNMqhvTNx3AesxIAQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3751,12 +3777,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-QReCdvxiUZAPkvp1xpAg62IeNzykOFA6syH2CnClif4YmALN1XKpB39XneL80008UbtMShthSVDKmrx05N1q/g==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -4041,14 +4070,14 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@schematics/angular": {
|
||||
"version": "20.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.2.0.tgz",
|
||||
"integrity": "sha512-7sZVj7hOcytQrPE17ixjzul9ih81IfXGcEZvr7fT77qy7Hm5rbMjxqSYxCTf3kAyBFRSLq/E8GTapPAjk2coOg==",
|
||||
"version": "20.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.3.tgz",
|
||||
"integrity": "sha512-lqIP1pNKp8yaqd663R3graZWaTBjXH+Cl72BQl1Ghl7lFGReZJALr4GiSMiBR9r30Epklcw5TwOSi+Bs4UKmbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "20.2.0",
|
||||
"@angular-devkit/schematics": "20.2.0",
|
||||
"@angular-devkit/core": "20.3.3",
|
||||
"@angular-devkit/schematics": "20.3.3",
|
||||
"jsonc-parser": "3.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4195,9 +4224,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz",
|
||||
"integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==",
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -4519,9 +4548,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ansis": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz",
|
||||
"integrity": "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz",
|
||||
"integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
@@ -4957,9 +4986,9 @@
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz",
|
||||
"integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==",
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -5877,10 +5906,13 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.4.6",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
|
||||
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
@@ -6804,6 +6836,15 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jwt-decode": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
|
||||
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/karma": {
|
||||
"version": "6.4.4",
|
||||
"resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz",
|
||||
@@ -8683,35 +8724,37 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-vxI2sPN07MMaoYKlFrVva5qZ1Y7DAZkgp7MQwTnyHt4FUMz9Sh+YeCzNFV9JYHI6ZNwoGWLCfCViE3XVsRC1cg==",
|
||||
"version": "1.0.0-beta.38",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.38.tgz",
|
||||
"integrity": "sha512-58frPNX55Je1YsyrtPJv9rOSR3G5efUZpRqok94Efsj0EUa8dnqJV3BldShyI7A+bVPleucOtzXHwVpJRcR0kQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/runtime": "=0.81.0",
|
||||
"@oxc-project/types": "=0.81.0",
|
||||
"@rolldown/pluginutils": "1.0.0-beta.32",
|
||||
"@oxc-project/types": "=0.89.0",
|
||||
"@rolldown/pluginutils": "1.0.0-beta.38",
|
||||
"ansis": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"rolldown": "bin/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.0.0-beta.32",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.0-beta.32",
|
||||
"@rolldown/binding-darwin-x64": "1.0.0-beta.32",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.0-beta.32",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.32",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.32",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.0-beta.32",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.0-beta.32",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.0-beta.32",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.0-beta.32",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.0-beta.32",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.32",
|
||||
"@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.32",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.0-beta.32"
|
||||
"@rolldown/binding-android-arm64": "1.0.0-beta.38",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.0-beta.38",
|
||||
"@rolldown/binding-darwin-x64": "1.0.0-beta.38",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.0-beta.38",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.38",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.38",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.0-beta.38",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.0-beta.38",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.0-beta.38",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.0-beta.38",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.0-beta.38",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.38",
|
||||
"@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.38",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.0-beta.38"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
@@ -9890,18 +9933,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz",
|
||||
"integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==",
|
||||
"version": "7.1.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz",
|
||||
"integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.6",
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.3",
|
||||
"postcss": "^8.5.6",
|
||||
"rollup": "^4.43.0",
|
||||
"tinyglobby": "^0.2.14"
|
||||
"tinyglobby": "^0.2.15"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
@@ -9964,6 +10007,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
|
||||
|
||||
35
package.json
35
package.json
@@ -11,23 +11,24 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^20.2.1",
|
||||
"@angular/cdk": "^20.2.0",
|
||||
"@angular/common": "^20.2.1",
|
||||
"@angular/compiler": "^20.2.1",
|
||||
"@angular/core": "^20.2.1",
|
||||
"@angular/forms": "^20.2.1",
|
||||
"@angular/material": "^20.2.0",
|
||||
"@angular/material-moment-adapter": "^20.2.0",
|
||||
"@angular/platform-browser": "^20.2.1",
|
||||
"@angular/platform-browser-dynamic": "^20.2.1",
|
||||
"@angular/platform-server": "^20.2.1",
|
||||
"@angular/router": "^20.2.1",
|
||||
"@angular/ssr": "^20.2.0",
|
||||
"@angular/animations": "^20.3.2",
|
||||
"@angular/cdk": "^20.2.5",
|
||||
"@angular/common": "^20.3.2",
|
||||
"@angular/compiler": "^20.3.2",
|
||||
"@angular/core": "^20.3.2",
|
||||
"@angular/forms": "^20.3.2",
|
||||
"@angular/material": "^20.2.5",
|
||||
"@angular/material-moment-adapter": "^20.2.5",
|
||||
"@angular/platform-browser": "^20.3.2",
|
||||
"@angular/platform-browser-dynamic": "^20.3.2",
|
||||
"@angular/platform-server": "^20.3.2",
|
||||
"@angular/router": "^20.3.2",
|
||||
"@angular/ssr": "^20.3.3",
|
||||
"@ng-bootstrap/ng-bootstrap": "^19.0.1",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"bootstrap": "^5.3.7",
|
||||
"express": "^4.21.2",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"moment": "2.30.1",
|
||||
"ngx-cookie-service-ssr": "^20.1.0",
|
||||
"ngx-mask": "^20.0.3",
|
||||
@@ -37,10 +38,10 @@
|
||||
"zone.js": "~0.15.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/build": "^20.2.0",
|
||||
"@angular/cli": "^20.2.0",
|
||||
"@angular/compiler-cli": "^20.2.1",
|
||||
"@angular/localize": "^20.2.1",
|
||||
"@angular/build": "^20.3.3",
|
||||
"@angular/cli": "^20.3.3",
|
||||
"@angular/compiler-cli": "^20.3.2",
|
||||
"@angular/localize": "^20.3.2",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^24.2.1",
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
<mat-icon>group</mat-icon>
|
||||
Spelers
|
||||
</a>
|
||||
@if (this.userService.isLoggedIn()) {
|
||||
@if (this.authService.isLoggedIn()) {
|
||||
<button mat-flat-button [matMenuTriggerFor]="accountMenu">
|
||||
<mat-icon>person</mat-icon>
|
||||
{{ user }}
|
||||
{{ this.authService.getUsername() }}
|
||||
</button>
|
||||
<mat-menu #accountMenu="matMenu">
|
||||
<button mat-menu-item (click)="logOut()">
|
||||
|
||||
@@ -6,9 +6,10 @@ import {MatIcon} from "@angular/material/icon";
|
||||
import {MatToolbar} from "@angular/material/toolbar";
|
||||
import {filter, map, Subscription} from "rxjs";
|
||||
import {MatMenu, MatMenuItem, MatMenuTrigger} from "@angular/material/menu";
|
||||
import {UserService} from "./authentication/user.service";
|
||||
// import {UserService} from "./authentication/user.service";
|
||||
import {TournamentService} from "./service/tournament.service";
|
||||
import {HeaderService} from "./service/header.service";
|
||||
import {AuthService} from "./auth/auth.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -19,11 +20,11 @@ import {HeaderService} from "./service/header.service";
|
||||
export class AppComponent implements OnInit {
|
||||
|
||||
header: string;
|
||||
user: string;
|
||||
userSubscription: Subscription;
|
||||
// user: string;
|
||||
// userSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
protected userService: UserService,
|
||||
protected authService: AuthService,
|
||||
private router: Router,
|
||||
protected activatedRoute: ActivatedRoute,
|
||||
private tournamentService: TournamentService,
|
||||
@@ -32,7 +33,7 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.userSubscription = this.userService.currentUser.subscribe(newUser => this.user = newUser);
|
||||
// this.userSubscription = this.userService.currentUser.subscribe(newUser => this.user = newUser);
|
||||
|
||||
this.router.events.pipe(
|
||||
filter(event => event instanceof NavigationEnd),
|
||||
@@ -55,8 +56,8 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
logOut() {
|
||||
this.userService.removeUser();
|
||||
this.router.navigate(['/auth/login']);
|
||||
this.authService.logout();
|
||||
this.router.navigate(['/login']);
|
||||
}
|
||||
|
||||
addTestData() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ApplicationConfig, provideZoneChangeDetection} from '@angular/core';
|
||||
import {ApplicationConfig, inject, provideAppInitializer, provideZoneChangeDetection} from '@angular/core';
|
||||
import {provideRouter} from '@angular/router';
|
||||
|
||||
import {routes} from './app.routes';
|
||||
@@ -7,10 +7,9 @@ import {HTTP_INTERCEPTORS, provideHttpClient, withFetch, withInterceptorsFromDi}
|
||||
import {provideAnimationsAsync} from '@angular/platform-browser/animations/async';
|
||||
import {provideMomentDateAdapter} from "@angular/material-moment-adapter";
|
||||
import {MAT_SNACK_BAR_DEFAULT_OPTIONS} from "@angular/material/snack-bar";
|
||||
import {AuthGuard} from "./authentication/authguard";
|
||||
import {TokenInterceptor} from "./authentication/tokenInterceptor";
|
||||
import {ErrorInterceptor} from "./authentication/errorInterceptor";
|
||||
import {provideEnvironmentNgxMask, provideNgxMask} from "ngx-mask";
|
||||
import {provideNgxMask} from "ngx-mask";
|
||||
import {provideAnimations} from "@angular/platform-browser/animations";
|
||||
import {AuthInterceptor} from "./auth/auth.interceptor";
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
@@ -18,12 +17,11 @@ export const appConfig: ApplicationConfig = {
|
||||
provideRouter(routes),
|
||||
provideClientHydration(),
|
||||
provideHttpClient(withFetch(), withInterceptorsFromDi()),
|
||||
provideAnimationsAsync(),
|
||||
provideAnimations(),
|
||||
provideMomentDateAdapter(undefined, {useUtc: false}),
|
||||
{ provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: { duration: 2500}},
|
||||
AuthGuard,
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
|
||||
provideNgxMask(),
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
@@ -8,24 +8,24 @@ import {TournamentDrawComponent} from "./components/tournament-draw/tournament-d
|
||||
import {TournamentManageComponent} from "./components/tournament-manage/tournament-manage.component";
|
||||
import {MatchSheetsComponent} from "./components/match-sheets/match-sheets.component";
|
||||
import {RoundOverviewComponent} from "./components/round-overview/round-overview.component";
|
||||
import {AuthGuard} from "./authentication/authguard";
|
||||
import {LoginComponent} from "./components/login/login.component";
|
||||
import {TournamentPlayersComponent} from "./components/tournament-players/tournament-players.component";
|
||||
import {AuthGuard} from "./auth/auth-guard.service";
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: '', component: TournamentListComponent, canActivate: [AuthGuard], data: { header: 'Toernooien' }},
|
||||
{ path: 'tournaments', component: TournamentListComponent, canActivate: [AuthGuard], data: { header: 'Toernooien' }},
|
||||
{ path: 'tournaments/add', component: TournamentEditComponent, canActivate: [AuthGuard], data: { header: 'Nieuw Toernooi' }},
|
||||
{ path: 'tournaments/:id/edit', component: TournamentEditComponent, canActivate: [AuthGuard], data: { header: 'Bewerk Toernooi' }},
|
||||
{ path: 'tournaments/:id/registrations', component: TournamentPlayersComponent, canActivate: [AuthGuard], data: { header: 'Inschrijvingen' }},
|
||||
{ path: 'tournaments/:id/draw', component: TournamentDrawComponent, canActivate: [AuthGuard], data: { header: 'Toernooi loten' }},
|
||||
{ path: 'tournaments/:id/manage', component: TournamentManageComponent, canActivate: [AuthGuard]},
|
||||
{ path: 'tournaments/:id/manage/:tab', component: TournamentManageComponent, canActivate: [AuthGuard], data: { header: 'Toernooien' }},
|
||||
{ path: 'players', component: PlayerListComponent, canActivate: [AuthGuard], data: { header: 'Spelers' }},
|
||||
{ path: 'players/add', component: PlayerEditComponent, canActivate: [AuthGuard], data: { header: 'Nieuwe Speler' }},
|
||||
{ path: 'players/:id/edit', component: PlayerEditComponent, canActivate: [AuthGuard], data: { header: 'Bewerk Speler' }},
|
||||
{ path: 'players/:id/registrations', component: PlayerRegistrationsComponent, canActivate: [AuthGuard]},
|
||||
{ path: 'tournaments/:id/rounds/:roundId/matchsheets', component: MatchSheetsComponent, canActivate: [AuthGuard]},
|
||||
{ path: 'tournaments/:id/rounds/:roundId/overview', component: RoundOverviewComponent, canActivate: [AuthGuard], data: { header: 'Rondeoverzicht' }},
|
||||
{ path: 'auth/login', component: LoginComponent, data: { header: 'Inloggen'}}
|
||||
{ path: '', component: TournamentListComponent, canActivate: [AuthGuard], data: { role: 'ROLE_USER', header: 'Toernooien' }},
|
||||
{ path: 'tournaments', component: TournamentListComponent, canActivate: [AuthGuard], data: { role: 'ROLE_USER', header: 'Toernooien' }},
|
||||
{ path: 'tournaments/add', component: TournamentEditComponent, canActivate: [AuthGuard], data: { role: 'ROLE_USER', header: 'Nieuw Toernooi' }},
|
||||
{ path: 'tournaments/:id/edit', component: TournamentEditComponent, canActivate: [AuthGuard], data: { role: 'ROLE_USER', header: 'Bewerk Toernooi' }},
|
||||
{ path: 'tournaments/:id/registrations', component: TournamentPlayersComponent, canActivate: [AuthGuard], data: { role: 'ROLE_USER', header: 'Inschrijvingen' }},
|
||||
{ path: 'tournaments/:id/draw', component: TournamentDrawComponent, canActivate: [AuthGuard], data: { role: 'ROLE_USER', header: 'Toernooi loten' }},
|
||||
{ path: 'tournaments/:id/manage', component: TournamentManageComponent, canActivate: [AuthGuard], data: { role: 'ROLE_USER' }},
|
||||
{ path: 'tournaments/:id/manage/:tab', component: TournamentManageComponent, canActivate: [AuthGuard], data: { role: 'ROLE_USER', header: 'Toernooien' }},
|
||||
{ path: 'players', component: PlayerListComponent, canActivate: [AuthGuard], data: { role: 'ROLE_USER', header: 'Spelers' }},
|
||||
{ path: 'players/add', component: PlayerEditComponent, canActivate: [AuthGuard], data: { role: 'ROLE_USER', header: 'Nieuwe Speler' }},
|
||||
{ path: 'players/:id/edit', component: PlayerEditComponent, canActivate: [AuthGuard], data: { role: 'ROLE_USER', header: 'Bewerk Speler' }},
|
||||
{ path: 'players/:id/registrations', component: PlayerRegistrationsComponent, canActivate: [AuthGuard], data: { role: 'ROLE_USER' }},
|
||||
{ path: 'tournaments/:id/rounds/:roundId/matchsheets', component: MatchSheetsComponent, canActivate: [AuthGuard], data: { role: 'ROLE_USER' }},
|
||||
{ path: 'tournaments/:id/rounds/:roundId/overview', component: RoundOverviewComponent, canActivate: [AuthGuard], data: { role: 'ROLE_USER', header: 'Rondeoverzicht' }},
|
||||
{ path: 'login', component: LoginComponent, data: { header: 'Inloggen'}}
|
||||
];
|
||||
|
||||
22
src/app/auth/auth-guard.service.ts
Normal file
22
src/app/auth/auth-guard.service.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
|
||||
import {AuthService} from './auth.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthGuard implements CanActivate {
|
||||
|
||||
constructor(private authService: AuthService, private router: Router) {
|
||||
}
|
||||
|
||||
public canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
|
||||
if (this.authService.isLoggedIn() && this.authService.isUserInRole(next.routeConfig?.data?.['role'])) {
|
||||
return true;
|
||||
} else {
|
||||
// this.router.navigateByUrl("/login");
|
||||
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/app/auth/auth.interceptor.ts
Normal file
35
src/app/auth/auth.interceptor.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
|
||||
import {catchError, Observable, throwError} from 'rxjs';
|
||||
import {Router} from '@angular/router';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
|
||||
constructor(private router: Router) {
|
||||
}
|
||||
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
let token = localStorage.getItem("app.token");
|
||||
if (token) {
|
||||
request = request.clone({
|
||||
setHeaders: {
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return next.handle(request).pipe(
|
||||
catchError((error: HttpErrorResponse) => this.handleErrorRes(error))
|
||||
);
|
||||
}
|
||||
|
||||
private handleErrorRes(error: HttpErrorResponse): Observable<never> {
|
||||
if (error.status === 401) {
|
||||
this.router.navigateByUrl("/login", {replaceUrl: true});
|
||||
}
|
||||
return throwError(() => error);
|
||||
}
|
||||
}
|
||||
85
src/app/auth/auth.service.ts
Normal file
85
src/app/auth/auth.service.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import {Inject, Injectable, PLATFORM_ID} from '@angular/core';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {Observable} from 'rxjs';
|
||||
import {environment} from "../../environments/environment";
|
||||
import {isPlatformBrowser} from "@angular/common";
|
||||
import {jwtDecode, JwtPayload} from "jwt-decode";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
|
||||
private readonly authUrl: string
|
||||
|
||||
constructor(private http: HttpClient,
|
||||
@Inject(PLATFORM_ID) private platformId: Object) {
|
||||
this.authUrl = `${environment.backendUrl}/api/auth`;
|
||||
}
|
||||
|
||||
private get isBrowser(): boolean {
|
||||
return isPlatformBrowser(this.platformId);
|
||||
}
|
||||
|
||||
isLoggedIn(): boolean {
|
||||
if (!this.isBrowser) return false;
|
||||
return localStorage.getItem("app.token") != null;
|
||||
}
|
||||
|
||||
login(username: string, password: string): Observable<string> {
|
||||
if (!this.isBrowser) {
|
||||
throw new Error('Login can only be performed in browser');
|
||||
}
|
||||
|
||||
const credentials = btoa(`${username}:${password}`);
|
||||
|
||||
const httpOptions = {
|
||||
headers: {
|
||||
'Authorization': `Basic ${credentials}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
responseType: 'text' as 'text',
|
||||
};
|
||||
return this.http.post(this.authUrl, null, httpOptions);
|
||||
}
|
||||
|
||||
logout() {
|
||||
if (!this.isBrowser) return;
|
||||
localStorage.removeItem("app.token");
|
||||
localStorage.removeItem("app.roles");
|
||||
}
|
||||
|
||||
isUserInRole(roleFromRoute: string) {
|
||||
if (!this.isBrowser) return false;
|
||||
|
||||
const roles = localStorage.getItem("app.roles");
|
||||
|
||||
if (roles!.includes(",")) {
|
||||
if (roles === roleFromRoute) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
const roleArray = roles!.split(",");
|
||||
for (let role of roleArray) {
|
||||
if (role === roleFromRoute) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getUsername(): string | null {
|
||||
if (!this.isBrowser) return null;
|
||||
|
||||
const token = localStorage.getItem("app.token");
|
||||
if (!token) return null;
|
||||
|
||||
try {
|
||||
const decodedToken = jwtDecode<JwtPayload>(token);
|
||||
return decodedToken.sub || null; // 'sub' is the standard JWT claim for subject/username
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import {environment} from "../../environments/environment";
|
||||
import {LoginCredentials} from "./loginCredentials";
|
||||
import {TokenModel} from "./tokenModel";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthenticationService {
|
||||
|
||||
private readonly authUrl: string
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
this.authUrl = `${environment.backendUrl}`
|
||||
}
|
||||
|
||||
public login(loginCredentials: LoginCredentials) {
|
||||
return this.http.post<any>(`${this.authUrl}/authenticate`, loginCredentials);
|
||||
}
|
||||
|
||||
public logout(tokenModel: TokenModel) {
|
||||
return this.http.post<string>(
|
||||
`${this.authUrl}/logout`,
|
||||
tokenModel
|
||||
);
|
||||
}
|
||||
|
||||
public logoutEverywhere() {
|
||||
return this.http.post<string>(
|
||||
`${this.authUrl}/logout-everywhere`,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import {Injectable} from "@angular/core";
|
||||
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from "@angular/router";
|
||||
import {UserService} from "./user.service";
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate {
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private userService: UserService
|
||||
) {}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
if (this.userService.getUser()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.router.navigate(['/auth/login'], {
|
||||
queryParams: { returnUrl: state.url },
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import {Injectable} from "@angular/core";
|
||||
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
|
||||
import {catchError, Observable, throwError} from "rxjs";
|
||||
import {UserService} from "./user.service";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ErrorInterceptor implements HttpInterceptor {
|
||||
|
||||
constructor(private userService: UserService) { }
|
||||
|
||||
intercept(
|
||||
request: HttpRequest<any>,
|
||||
next: HttpHandler
|
||||
): Observable<HttpEvent<any>> {
|
||||
|
||||
return next.handle(request).pipe(catchError(error=>{
|
||||
if((error.status == 401 || error.status == 403) && !this.isLoginPage(request)){
|
||||
this.userService.removeUser();
|
||||
}
|
||||
// const errMsg = error.error.message || error.statusText;
|
||||
return throwError(() => error);
|
||||
// return throwError(()=> errMsg);
|
||||
}));
|
||||
}
|
||||
|
||||
private isLoginPage(request: HttpRequest<any>){
|
||||
return request.url.includes("/authenticate")
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class IpService {
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
public getIPAddress() {
|
||||
return this.http.get("http://api.ipify.org/?format=json");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export class LoginCredentials {
|
||||
username: string;
|
||||
password: string;
|
||||
// ipAddress: string;
|
||||
// recaptcha: string;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import {Injectable} from "@angular/core";
|
||||
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
|
||||
import {Observable} from "rxjs";
|
||||
import {UserService} from "./user.service";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TokenInterceptor implements HttpInterceptor {
|
||||
|
||||
constructor(private userService: UserService) { }
|
||||
|
||||
intercept(
|
||||
request: HttpRequest<any>,
|
||||
next: HttpHandler
|
||||
): Observable<HttpEvent<any>> {
|
||||
if (this.userService.isLoggedIn()) {
|
||||
let newRequest = request.clone({
|
||||
setHeaders: {
|
||||
Authorization: `Bearer ${this.userService.getUser()?.accessToken}`,
|
||||
},
|
||||
});
|
||||
return next.handle(newRequest);
|
||||
}
|
||||
return next.handle(request);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
export class TokenModel {
|
||||
constructor(token: string, refreshToken: string, ipAddress: string) {
|
||||
this.token = token;
|
||||
this.refreshToken = refreshToken;
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
token: string;
|
||||
refreshToken: string;
|
||||
ipAddress: string;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import {EventEmitter, Injectable, Output} from "@angular/core";
|
||||
import {SsrCookieService} from "ngx-cookie-service-ssr";
|
||||
import {Router} from "@angular/router";
|
||||
import {User} from "./user";
|
||||
import {BehaviorSubject} from "rxjs";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserService {
|
||||
user?: User;
|
||||
|
||||
private userEmitter = new BehaviorSubject('');
|
||||
currentUser = this.userEmitter.asObservable();
|
||||
|
||||
constructor(
|
||||
private cookieService: SsrCookieService,
|
||||
private router: Router,
|
||||
) {}
|
||||
|
||||
public getUser(): User | undefined {
|
||||
const user = this.cookieService.get('swissuser');
|
||||
|
||||
if (user) {
|
||||
this.user = JSON.parse(user);
|
||||
this.userEmitter.next(JSON.parse(user).username);
|
||||
} else {
|
||||
this.user = undefined;
|
||||
}
|
||||
|
||||
return this.user;
|
||||
}
|
||||
|
||||
public isLoggedIn(): boolean {
|
||||
const user = this.cookieService.get('swissuser');
|
||||
|
||||
return !(user == undefined || false || user == "");
|
||||
}
|
||||
|
||||
public setUser(user: User) {
|
||||
this.cookieService.set('swissuser', JSON.stringify(user));
|
||||
|
||||
this.user = user;
|
||||
|
||||
this.userEmitter.next(this.user.username);
|
||||
}
|
||||
|
||||
public removeUser() {
|
||||
this.cookieService.delete('swissuser');
|
||||
this.user = undefined;
|
||||
this.router.navigate(['/tournaments']);
|
||||
this.userEmitter.next('');
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export class User {
|
||||
accessToken: string;
|
||||
username: string;
|
||||
// passwordHash: string;
|
||||
// email: string;
|
||||
// token: string;
|
||||
// refreshToken: string;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<mat-dialog-content>
|
||||
<h3>Kies een teller:</h3>
|
||||
<div class="col-md-6">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Teller</mat-label>
|
||||
<mat-select [(ngModel)]="counter">
|
||||
<mat-option>Geen</mat-option>
|
||||
@for (player of data.availableCounters; track player.playerId) {
|
||||
<mat-option [value]="player">
|
||||
{{ player.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
@if (counter) {
|
||||
<button mat-button [mat-dialog-close]="{counter: counter}">Opslaan</button>
|
||||
}
|
||||
<button mat-button (click)="onAnnulerenClick()">Annuleren</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,4 @@
|
||||
button:disabled {
|
||||
cursor: not-allowed;
|
||||
pointer-events: all !important;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import {Component, inject, Inject} from '@angular/core';
|
||||
import {
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogActions,
|
||||
MatDialogClose,
|
||||
MatDialogContent,
|
||||
MatDialogRef
|
||||
} from "@angular/material/dialog";
|
||||
import {Match} from "../../model/match";
|
||||
import {MatButton} from "@angular/material/button";
|
||||
import {TournamentPlayer} from "../../model/tournamentPlayer";
|
||||
import {MatFormField, MatLabel} from "@angular/material/form-field";
|
||||
import {MatOption, MatSelect} from "@angular/material/select";
|
||||
import {FormsModule} from "@angular/forms";
|
||||
|
||||
@Component({
|
||||
selector: 'app-counter-selection',
|
||||
imports: [
|
||||
MatDialogContent,
|
||||
MatButton,
|
||||
MatDialogClose,
|
||||
MatDialogActions,
|
||||
MatFormField,
|
||||
MatLabel,
|
||||
MatOption,
|
||||
MatSelect,
|
||||
FormsModule,
|
||||
|
||||
],
|
||||
templateUrl: './counter-selection.component.html',
|
||||
standalone: true,
|
||||
styleUrl: './counter-selection.component.scss'
|
||||
})
|
||||
export class CounterSelectionComponent {
|
||||
|
||||
counter: TournamentPlayer;
|
||||
|
||||
readonly dialogRef = inject(MatDialogRef<CounterSelectionComponent>);
|
||||
|
||||
constructor(@Inject(MAT_DIALOG_DATA) public data: {
|
||||
match: Match,
|
||||
availableCounters: TournamentPlayer[]
|
||||
}) {}
|
||||
|
||||
onAnnulerenClick() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +1,23 @@
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<form class="form-horizontal" [formGroup]="form">
|
||||
<form class="form-horizontal" (ngSubmit)="login()">
|
||||
<div class="row">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Gebruikersnaam</mat-label>
|
||||
<input matInput placeholder="Gebruikersnaam" formControlName="username" required>
|
||||
<input matInput name="username" placeholder="Gebruikersnaam" required [(ngModel)]="username">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Wachtwoord</mat-label>
|
||||
<input matInput placeholder="Wachtwoord" type="password" formControlName="password" required>
|
||||
<input matInput name="password" placeholder="Wachtwoord" type="password" required [(ngModel)]="password">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<button class="w-100 mt-2" mat-button mat-flat-button color="primary"
|
||||
(click)="login()" [disabled]="form.invalid">
|
||||
(click)="login()">
|
||||
Inloggen
|
||||
</button>
|
||||
</form>
|
||||
<p>{{message}}</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute, Router, RouterLink} from '@angular/router';
|
||||
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from "@angular/material/card";
|
||||
import {MatError, MatFormField, MatLabel} from "@angular/material/form-field";
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {MatCard, MatCardContent} from "@angular/material/card";
|
||||
import {MatFormField, MatLabel} from "@angular/material/form-field";
|
||||
import {MatButton} from "@angular/material/button";
|
||||
import {MatInput} from "@angular/material/input";
|
||||
import {NgIf} from "@angular/common";
|
||||
import {AuthenticationService} from "../../authentication/authentication.service";
|
||||
import {UserService} from "../../authentication/user.service";
|
||||
import {LoginCredentials} from "../../authentication/loginCredentials";
|
||||
import {User} from "../../authentication/user";
|
||||
import {jwtDecode, JwtPayload} from "jwt-decode";
|
||||
import {AuthService} from "../../auth/auth.service";
|
||||
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -23,6 +21,7 @@ import {User} from "../../authentication/user";
|
||||
MatInput,
|
||||
MatLabel,
|
||||
MatCard,
|
||||
FormsModule,
|
||||
],
|
||||
standalone: true,
|
||||
styleUrls: ['./login.component.scss']
|
||||
@@ -32,48 +31,42 @@ export class LoginComponent implements OnInit {
|
||||
private returnUrl: string;
|
||||
private ipAddress: string;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private fb: FormBuilder,
|
||||
private authenticationService: AuthenticationService,
|
||||
private router: Router,
|
||||
private userPersistenceService: UserService,
|
||||
) {
|
||||
this.initializeForm();
|
||||
username: string = "";
|
||||
password: string = "";
|
||||
message: string = "";
|
||||
|
||||
constructor(private authService: AuthService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private snackBar: MatSnackBar) {
|
||||
}
|
||||
|
||||
private initializeForm() {
|
||||
this.form = this.fb.group({
|
||||
username: ['', [Validators.required]],
|
||||
password: ['', [Validators.required]],
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
|
||||
}
|
||||
|
||||
login() {
|
||||
if (this.form.invalid) {
|
||||
return;
|
||||
}
|
||||
public login(): void {
|
||||
localStorage.removeItem("app.token");
|
||||
|
||||
let loginCredentials = new LoginCredentials();
|
||||
loginCredentials = {
|
||||
...loginCredentials,
|
||||
...this.form.value,
|
||||
};
|
||||
this.authService.login(this.username, this.password)
|
||||
.subscribe({
|
||||
next: (token) => {
|
||||
localStorage.setItem("app.token", token);
|
||||
|
||||
this.authenticationService
|
||||
.login(loginCredentials)
|
||||
.subscribe(
|
||||
{
|
||||
next: (user: User) => {
|
||||
this.userPersistenceService.setUser(user);
|
||||
this.router.navigate([this.returnUrl]);
|
||||
}
|
||||
const decodedToken = jwtDecode<JwtPayload>(token);
|
||||
// @ts-ignore
|
||||
localStorage.setItem("app.roles", decodedToken.scope);
|
||||
|
||||
// this.router.navigateByUrl("/persons");
|
||||
|
||||
// this.router.navigate([this.returnUrl]);
|
||||
this.router.navigate([this.route.snapshot.queryParams['returnUrl'] || '/']);
|
||||
},
|
||||
error: (error) => {
|
||||
this.snackBar.open(`Login failed: ${error.status}`, "OK")
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,76 +7,76 @@
|
||||
<b>{{ data.group.name }} {{ data.round.name }}</b>
|
||||
</div>
|
||||
</mat-grid-tile>
|
||||
<mat-grid-tile>
|
||||
<button type="button" class="btn btn-primary btn-lg" (click)="set21(1, 1)">21</button>
|
||||
</mat-grid-tile>
|
||||
<mat-grid-tile>
|
||||
<button type="button" class="btn btn-primary btn-lg" (click)="set21(1, 2)">21</button>
|
||||
</mat-grid-tile>
|
||||
<mat-grid-tile>
|
||||
<button type="button" class="btn btn-primary btn-lg" (click)="set21(1, 3)">21</button>
|
||||
</mat-grid-tile>
|
||||
|
||||
@for (gameNum of [1, 2, 3]; track gameNum) {
|
||||
<mat-grid-tile>
|
||||
<button type="button" class="btn btn-primary btn-lg" (click)="set21(1, gameNum)">21</button>
|
||||
</mat-grid-tile>
|
||||
}
|
||||
<mat-grid-tile colspan="2">
|
||||
<div class="w-100" [ngClass]="{'winner': validateResult() == 1}">
|
||||
{{ data.match.team1 | teamText }}
|
||||
<div class="w-100" [ngClass]="{'winner': isValidResult == 1}">
|
||||
<app-team-display
|
||||
[team]="data.match.team1"
|
||||
[event]="data.event"
|
||||
[tournament]="data.tournament"
|
||||
[inline]="false">
|
||||
</app-team-display>
|
||||
</div>
|
||||
</mat-grid-tile>
|
||||
|
||||
<mat-grid-tile>
|
||||
<mat-form-field appearance="outline" (change)="validateResult()">
|
||||
<input matInput type="number" min="0" max="30" [(ngModel)]="result.games[0].score1">
|
||||
</mat-form-field>
|
||||
</mat-grid-tile>
|
||||
<mat-grid-tile>
|
||||
<mat-form-field appearance="outline" (change)="validateResult()">
|
||||
<input matInput type="number" min="0" max="30" [(ngModel)]="result.games[1].score1">
|
||||
</mat-form-field>
|
||||
</mat-grid-tile>
|
||||
<mat-grid-tile>
|
||||
<mat-form-field appearance="outline" (change)="validateResult()">
|
||||
<input matInput type="number" min="0" max="30" [(ngModel)]="result.games[2].score1">
|
||||
</mat-form-field>
|
||||
</mat-grid-tile>
|
||||
@for (game of result.games; track $index; let i = $index) {
|
||||
<mat-grid-tile>
|
||||
<mat-form-field appearance="outline">
|
||||
<input matInput
|
||||
type="number"
|
||||
min="0"
|
||||
max="30"
|
||||
[(ngModel)]="game.score1"
|
||||
(blur)="complementScores()"
|
||||
(ngModelChange)="validateResult()"
|
||||
[tabindex]="i * 2 + 1">
|
||||
</mat-form-field>
|
||||
</mat-grid-tile>
|
||||
}
|
||||
|
||||
<mat-grid-tile colspan="2">
|
||||
<div class="w-100" [ngClass]="{'winner': validateResult() == -1}">
|
||||
{{ data.match.team2 | teamText }}
|
||||
<div class="w-100" [ngClass]="{'winner': isValidResult == -1}">
|
||||
<app-team-display
|
||||
[team]="data.match.team2"
|
||||
[event]="data.event"
|
||||
[tournament]="data.tournament"
|
||||
[inline]="false">
|
||||
</app-team-display>
|
||||
</div>
|
||||
</mat-grid-tile>
|
||||
<mat-grid-tile>
|
||||
<mat-form-field appearance="outline" (change)="validateResult()">
|
||||
<input matInput type="number" min="0" max="30" [(ngModel)]="result.games[0].score2">
|
||||
</mat-form-field>
|
||||
</mat-grid-tile>
|
||||
<mat-grid-tile>
|
||||
<mat-form-field appearance="outline" (change)="validateResult()">
|
||||
<input matInput type="number" min="0" max="30" [(ngModel)]="result.games[1].score2">
|
||||
</mat-form-field>
|
||||
</mat-grid-tile>
|
||||
<mat-grid-tile>
|
||||
<mat-form-field appearance="outline" (change)="validateResult()">
|
||||
<input matInput type="number" min="0" max="30" [(ngModel)]="result.games[2].score2">
|
||||
</mat-form-field>
|
||||
</mat-grid-tile>
|
||||
|
||||
<mat-grid-tile>
|
||||
@for (game of result.games; track $index; let i = $index) {
|
||||
<mat-grid-tile>
|
||||
<mat-form-field appearance="outline">
|
||||
<input matInput
|
||||
type="number"
|
||||
min="0"
|
||||
max="30"
|
||||
[(ngModel)]="game.score2"
|
||||
(blur)="complementScores()"
|
||||
(ngModelChange)="validateResult()"
|
||||
[tabindex]="i * 2 + 2">
|
||||
</mat-form-field>
|
||||
</mat-grid-tile>
|
||||
}
|
||||
<mat-grid-tile></mat-grid-tile>
|
||||
<mat-grid-tile></mat-grid-tile>
|
||||
|
||||
@for (gameNum of [1, 2, 3]; track gameNum) {
|
||||
<mat-grid-tile>
|
||||
<button type="button" class="btn btn-primary btn-lg" (click)="set21(2, gameNum)">21</button>
|
||||
</mat-grid-tile>
|
||||
}
|
||||
|
||||
</mat-grid-tile>
|
||||
<mat-grid-tile>
|
||||
</mat-grid-tile>
|
||||
<mat-grid-tile>
|
||||
<button type="button" class="btn btn-primary btn-lg" (click)="set21(2, 1)">21</button>
|
||||
</mat-grid-tile>
|
||||
<mat-grid-tile>
|
||||
<button type="button" class="btn btn-primary btn-lg" (click)="set21(2, 2)">21</button>
|
||||
</mat-grid-tile>
|
||||
<mat-grid-tile>
|
||||
<button type="button" class="btn btn-primary btn-lg" (click)="set21(2, 3)">21</button>
|
||||
</mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="onAnnulerenClick()">Annuleren</button>
|
||||
<button mat-button [disabled]="validateResult() == 0" [mat-dialog-close]="result">Opslaan</button>
|
||||
<button mat-button [disabled]="isValidResult == 0" [mat-dialog-close]="result">Opslaan</button>
|
||||
</mat-dialog-actions>
|
||||
|
||||
@@ -19,6 +19,9 @@ import {MatGridList, MatGridTile} from "@angular/material/grid-list";
|
||||
import {Round} from "../../model/round";
|
||||
import {Group} from "../../model/group";
|
||||
import {Game} from "../../model/game";
|
||||
import {TeamDisplayComponent} from "../team-display/team-display.component";
|
||||
import {Event} from "../../model/event";
|
||||
import {Tournament} from "../../model/tournament";
|
||||
|
||||
@Component({
|
||||
selector: 'app-match-result',
|
||||
@@ -28,14 +31,14 @@ import {Game} from "../../model/game";
|
||||
MatButton,
|
||||
MatDialogClose,
|
||||
MatDialogTitle,
|
||||
TeamPipe,
|
||||
MatInput,
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
MatFormField,
|
||||
MatGridList,
|
||||
MatGridTile,
|
||||
NgClass
|
||||
NgClass,
|
||||
TeamDisplayComponent
|
||||
],
|
||||
providers: [
|
||||
FullNamePipe,
|
||||
@@ -48,8 +51,9 @@ import {Game} from "../../model/game";
|
||||
export class MatchResultComponent {
|
||||
|
||||
result: Result = new Result();
|
||||
isValidResult: number = 0;
|
||||
|
||||
constructor(@Inject(MAT_DIALOG_DATA) public data: {match: Match, group: Group, round: Round}) {
|
||||
constructor(@Inject(MAT_DIALOG_DATA) public data: {match: Match, tournament: Tournament, event: Event, group: Group, round: Round}) {
|
||||
this.result.matchId = this.data.match.id;
|
||||
|
||||
if (data.match.games.length == 0) {
|
||||
@@ -64,6 +68,8 @@ export class MatchResultComponent {
|
||||
this.result.games.push(new Game());
|
||||
}
|
||||
}
|
||||
|
||||
this.validateResult();
|
||||
}
|
||||
|
||||
readonly dialogRef = inject(MatDialogRef<MatchResultComponent>);
|
||||
@@ -76,13 +82,18 @@ export class MatchResultComponent {
|
||||
this.result.games[game - 1].score2 = 21;
|
||||
}
|
||||
|
||||
this.validateResult();
|
||||
}
|
||||
|
||||
onAnnulerenClick() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
validateResult(): number {
|
||||
complementScores() {
|
||||
// console.log("in complementScores");
|
||||
}
|
||||
|
||||
validateResult(): void {
|
||||
let valid : boolean = true;
|
||||
valid &&= this.gameValid(this.result.games[0].score1, this.result.games[0].score2);
|
||||
valid &&= this.gameValid(this.result.games[1].score1, this.result.games[1].score2);
|
||||
@@ -91,7 +102,7 @@ export class MatchResultComponent {
|
||||
valid &&= this.gameValid(this.result.games[2].score1, this.result.games[2].score2);
|
||||
}
|
||||
|
||||
return valid ? this.matchResult(this.result) : 0;
|
||||
this.isValidResult = valid ? this.matchResult(this.result) : 0;
|
||||
}
|
||||
|
||||
gameValid(score1: number, score2: number): boolean {
|
||||
@@ -114,29 +125,31 @@ export class MatchResultComponent {
|
||||
}
|
||||
|
||||
matchResult(result: Result): number {
|
||||
let gameBalance = 0;
|
||||
if (result.games[0].score1 < result.games[0].score2) {
|
||||
gameBalance--;
|
||||
} else {
|
||||
gameBalance++;
|
||||
}
|
||||
if (result.games[1].score1 < result.games[1].score2) {
|
||||
gameBalance--;
|
||||
} else {
|
||||
gameBalance++;
|
||||
}
|
||||
let team1Wins = 0;
|
||||
let team2Wins = 0;
|
||||
|
||||
if (Math.abs(gameBalance) == 2 && (result.games[2].score1 != undefined || result.games[2].score2 != undefined)) {
|
||||
return 0;
|
||||
}
|
||||
// Count wins for games 1 and 2
|
||||
if (result.games[0].score1 > result.games[0].score2) team1Wins++;
|
||||
else team2Wins++;
|
||||
|
||||
if (result.games[2].score1 != undefined && result.games[2].score2 != undefined) {
|
||||
if (result.games[2].score1 < result.games[2].score2) {
|
||||
gameBalance--;
|
||||
} else {
|
||||
gameBalance++;
|
||||
if (result.games[1].score1 > result.games[1].score2) team1Wins++;
|
||||
else team2Wins++;
|
||||
|
||||
// If match is already decided (2-0), game 3 shouldn't have scores
|
||||
if (Math.max(team1Wins, team2Wins) == 2) {
|
||||
if (result.games[2].score1 != undefined || result.games[2].score2 != undefined) {
|
||||
return 0; // Invalid
|
||||
}
|
||||
return team1Wins > team2Wins ? 1 : -1;
|
||||
}
|
||||
return Math.sign(gameBalance);
|
||||
|
||||
// Match is 1-1, check game 3
|
||||
if (result.games[2].score1 != undefined && result.games[2].score2 != undefined) {
|
||||
if (result.games[2].score1 > result.games[2].score2) team1Wins++;
|
||||
else team2Wins++;
|
||||
return team1Wins > team2Wins ? 1 : -1;
|
||||
}
|
||||
|
||||
return 0; // Incomplete
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,26 @@
|
||||
<div class="nobreak">
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-content>
|
||||
<h6>{{ tournament.name }}</h6>
|
||||
<br>
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<b>{{ match.team1 | teamText }}</b>
|
||||
<h5>{{ tournament.name }}</h5>
|
||||
</div>
|
||||
<div class="col-6 text-end">
|
||||
{{ group.name }} {{ round.name }}
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
<div class="row align-middle">
|
||||
<div class="col-6">
|
||||
<b>
|
||||
<app-team-display
|
||||
[team]="match.team1"
|
||||
[event]="this.event"
|
||||
[tournament]="this.tournament"
|
||||
[inline]="true">
|
||||
</app-team-display>
|
||||
</b>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<mat-form-field appearance="outline">
|
||||
@@ -28,7 +42,14 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<b>{{ match.team2 | teamText }}</b>
|
||||
<b>
|
||||
<app-team-display
|
||||
[team]="match.team2"
|
||||
[event]="this.event"
|
||||
[tournament]="this.tournament"
|
||||
[inline]="true">
|
||||
</app-team-display>
|
||||
</b>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<mat-form-field appearance="outline">
|
||||
@@ -50,9 +71,6 @@
|
||||
<div class="col-6">
|
||||
<u>Graag de winnaar omcirkelen</u>
|
||||
</div>
|
||||
<div class="col-6 text-end">
|
||||
{{ group.name }} {{ round.name }}
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
@@ -11,16 +11,18 @@ import {MatFormField} from "@angular/material/form-field";
|
||||
import {MatInput} from "@angular/material/input";
|
||||
import {ReactiveFormsModule} from "@angular/forms";
|
||||
import {HeaderService} from "../../service/header.service";
|
||||
import {TeamDisplayComponent} from "../team-display/team-display.component";
|
||||
import {Event} from "../../model/event";
|
||||
|
||||
@Component({
|
||||
selector: 'app-match-sheets',
|
||||
imports: [
|
||||
MatCard,
|
||||
MatCardContent,
|
||||
TeamPipe,
|
||||
MatFormField,
|
||||
MatInput,
|
||||
ReactiveFormsModule
|
||||
ReactiveFormsModule,
|
||||
TeamDisplayComponent
|
||||
],
|
||||
providers: [
|
||||
TeamPipe,
|
||||
@@ -33,6 +35,7 @@ import {HeaderService} from "../../service/header.service";
|
||||
export class MatchSheetsComponent implements OnInit, OnDestroy {
|
||||
|
||||
tournament: Tournament;
|
||||
event: Event;
|
||||
group: Group;
|
||||
round: Round;
|
||||
|
||||
@@ -54,6 +57,7 @@ export class MatchSheetsComponent implements OnInit, OnDestroy {
|
||||
for (let group of event.groups) {
|
||||
for (let round of group.rounds) {
|
||||
if (round.id == roundId) {
|
||||
this.event = event;
|
||||
this.group = group;
|
||||
this.round = round;
|
||||
this.headerService.setTitle(`Wedstrijdbriefjes ${this.group.name} ${this.round.name}`);
|
||||
|
||||
@@ -12,17 +12,24 @@ import {MatTooltip} from '@angular/material/tooltip';
|
||||
styleUrls: ['./player-display.component.scss'],
|
||||
template: `
|
||||
@let substitute = getSubstituteForEvent(player, event);
|
||||
<span [class.has-substitute]="substitute"
|
||||
[matTooltip]="substitute ? 'Valt in voor ' + (player | fullName) : ''"
|
||||
matTooltipPosition="below">
|
||||
{{ substitute || (player | fullName) }}
|
||||
</span>
|
||||
@if (exlicitSubstitute) {
|
||||
@if (substitute) {
|
||||
{{ substitute }} (valt in voor {{ player | fullName }})
|
||||
} @else {
|
||||
{{ player | fullName }}
|
||||
}
|
||||
} @else {
|
||||
<span [class.has-substitute]="substitute"
|
||||
[matTooltip]="substitute ? 'Valt in voor ' + (player | fullName) : ''"
|
||||
matTooltipPosition="below">{{ substitute || (player | fullName) }}</span>
|
||||
}
|
||||
`
|
||||
})
|
||||
export class PlayerDisplayComponent {
|
||||
@Input({ required: true }) player!: Player;
|
||||
@Input({ required: true }) event!: Event;
|
||||
@Input({ required: true }) tournament!: Tournament;
|
||||
@Input({ required: false }) exlicitSubstitute: boolean = false;
|
||||
|
||||
getSubstituteForEvent(player: Player, event: Event): string | undefined {
|
||||
const tournamentPlayer = this.tournament.tournamentPlayers.find(
|
||||
|
||||
@@ -45,7 +45,6 @@ export class PlayerEditComponent implements OnInit {
|
||||
player: Player;
|
||||
isEditMode: boolean = false;
|
||||
|
||||
|
||||
constructor(
|
||||
private playerService: PlayerService,
|
||||
private route: ActivatedRoute,
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
<mat-paginator [pageSizeOptions]="[10, 20, 50]"
|
||||
<mat-paginator [pageSizeOptions]="[10, 25, 100]"
|
||||
[pageSize]="100"
|
||||
showFirstLastButtons
|
||||
aria-label="Select page of periodic elements">
|
||||
</mat-paginator>
|
||||
|
||||
@@ -5,3 +5,7 @@ a {
|
||||
td, th {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.mat-mdc-row:hover {
|
||||
background-color: rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
|
||||
@@ -18,14 +18,23 @@
|
||||
@if (eventRegistration.doublesEvent) {
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Partner</mat-label>
|
||||
<mat-select [value]="eventRegistration.partner" [disabled]="!tournamentRegistration.editable || !eventRegistration.registered" [(ngModel)]="eventRegistration.partner">
|
||||
<mat-option>Geen</mat-option>
|
||||
@for (player of getRelevantPlayers(eventRegistration.type); track player.id) {
|
||||
<input type="text"
|
||||
matInput
|
||||
[matAutocomplete]="auto"
|
||||
[disabled]="!tournamentRegistration.editable || !eventRegistration.registered"
|
||||
[(ngModel)]="eventRegistration.partner"
|
||||
(input)="onPartnerSearch($any($event.target).value, eventRegistration)"
|
||||
[name]="'partner-' + eventRegistration.id">
|
||||
<mat-autocomplete #auto="matAutocomplete"
|
||||
[displayWith]="displayPartnerName.bind(this)"
|
||||
(optionSelected)="onPartnerSelected($event, eventRegistration)">
|
||||
<mat-option [value]="null">Geen</mat-option>
|
||||
@for (player of getFilteredPlayers(eventRegistration); track player.id) {
|
||||
<mat-option [value]="player.id">
|
||||
{{ player | fullName }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -9,12 +9,13 @@ import {RegistrationService} from "../../service/registration.service";
|
||||
import {MatCheckbox, MatCheckboxChange} from "@angular/material/checkbox";
|
||||
import {EventRegistration, TournamentRegistration} from "../../model/tournamentRegistration";
|
||||
import {MatOption} from "@angular/material/core";
|
||||
import {MatSelect} from "@angular/material/select";
|
||||
import {MatIcon} from "@angular/material/icon";
|
||||
import {MatAnchor, MatButton} from "@angular/material/button";
|
||||
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||
import {FullNamePipe} from "../../pipes/fullname-pipe";
|
||||
import {HeaderService} from "../../service/header.service";
|
||||
import {MatAutocomplete, MatAutocompleteTrigger} from "@angular/material/autocomplete";
|
||||
import {MatInput} from "@angular/material/input";
|
||||
|
||||
@Component({
|
||||
selector: 'app-player-registrations',
|
||||
@@ -30,11 +31,14 @@ import {HeaderService} from "../../service/header.service";
|
||||
MatCardActions,
|
||||
RouterLink,
|
||||
MatOption,
|
||||
MatSelect,
|
||||
// MatSelect,
|
||||
MatIcon,
|
||||
MatButton,
|
||||
MatAnchor,
|
||||
FullNamePipe
|
||||
FullNamePipe,
|
||||
MatAutocomplete,
|
||||
MatAutocompleteTrigger,
|
||||
MatInput,
|
||||
],
|
||||
providers: [
|
||||
FullNamePipe
|
||||
@@ -52,6 +56,8 @@ export class PlayerRegistrationsComponent implements OnInit {
|
||||
|
||||
waitingForBackend: boolean = false;
|
||||
|
||||
private partnerSearchTerms: Map<number, string> = new Map();
|
||||
|
||||
constructor(
|
||||
private _snackBar: MatSnackBar,
|
||||
private playerService: PlayerService,
|
||||
@@ -76,6 +82,40 @@ export class PlayerRegistrationsComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
onPartnerSearch(searchTerm: any, eventRegistration: EventRegistration) {
|
||||
// Only treat as search if it's a string (typed input)
|
||||
if (typeof searchTerm === 'string') {
|
||||
this.partnerSearchTerms.set(eventRegistration.id, searchTerm?.toLowerCase() || '');
|
||||
}
|
||||
}
|
||||
|
||||
getFilteredPlayers(eventRegistration: EventRegistration): Player[] {
|
||||
const allRelevant = this.getRelevantPlayers(eventRegistration.type);
|
||||
const searchTerm = this.partnerSearchTerms.get(eventRegistration.id);
|
||||
|
||||
if (!searchTerm) {
|
||||
return allRelevant;
|
||||
}
|
||||
|
||||
return allRelevant.filter(player => {
|
||||
const fullName = this.fullNamePipe.transform(player).toLowerCase();
|
||||
return fullName.includes(searchTerm);
|
||||
});
|
||||
}
|
||||
|
||||
displayPartnerName(playerId: number | null): string {
|
||||
if (!playerId || !this.allPlayers) return '';
|
||||
const player = this.allPlayers.find(p => p.id === playerId);
|
||||
return player ? this.fullNamePipe.transform(player) : '';
|
||||
}
|
||||
|
||||
onPartnerSelected(event: any, eventRegistration: EventRegistration) {
|
||||
eventRegistration.partner = event.option.value;
|
||||
// Clear the search term when a partner is selected
|
||||
this.partnerSearchTerms.delete(eventRegistration.id);
|
||||
}
|
||||
|
||||
|
||||
saveRegistration(tournamentRegistration: TournamentRegistration, event: MouseEvent) {
|
||||
this.waitingForBackend = true;
|
||||
this.registrationService.saveTournamentRegistrations(tournamentRegistration, this.player.id).subscribe(data => {
|
||||
|
||||
@@ -5,9 +5,23 @@
|
||||
<tbody>
|
||||
@for (match of round.matches; track match.id) {
|
||||
<tr>
|
||||
<td class="align-middle" style="width: 45%;">{{ match.team1 | teamText }}</td>
|
||||
<td class="align-middle" style="width: 45%;">
|
||||
<app-team-display
|
||||
[team]="match.team1"
|
||||
[event]="this.event"
|
||||
[tournament]="this.tournament"
|
||||
[inline]="true">
|
||||
</app-team-display>
|
||||
</td>
|
||||
<td class="align-middle w-sep">-</td>
|
||||
<td class="align-middle" style="width: 45%;">{{ match.team2 | teamText }}</td>
|
||||
<td class="align-middle" style="width: 45%;">
|
||||
<app-team-display
|
||||
[team]="match.team2"
|
||||
[event]="this.event"
|
||||
[tournament]="this.tournament"
|
||||
[inline]="true">
|
||||
</app-team-display>
|
||||
</td>
|
||||
<td class="align-middle w-sep"></td>
|
||||
</tr>
|
||||
}
|
||||
@@ -24,18 +38,20 @@
|
||||
@for (match of round.matches; track match.id) {
|
||||
<tr>
|
||||
<td class="align-middle" style="width: 30%;">
|
||||
@if (event.doublesEvent) {
|
||||
{{ match.team1.player1 | fullName }} /<br>{{ match.team1.player2 | fullName }}
|
||||
} @else {
|
||||
{{ match.team1.player1 | fullName }}
|
||||
}
|
||||
<app-team-display
|
||||
[team]="match.team1"
|
||||
[event]="this.event"
|
||||
[tournament]="this.tournament"
|
||||
[inline]="true">
|
||||
</app-team-display>
|
||||
<td class="align-middle w-sep">-</td>
|
||||
<td class="align-middle" style="width: 30%;">
|
||||
@if (event.doublesEvent) {
|
||||
{{ match.team2.player1 | fullName }} /<br>{{ match.team2.player2 | fullName }}
|
||||
} @else {
|
||||
{{ match.team2.player1 | fullName }}
|
||||
}
|
||||
<app-team-display
|
||||
[team]="match.team2"
|
||||
[event]="this.event"
|
||||
[tournament]="this.tournament"
|
||||
[inline]="true">
|
||||
</app-team-display>
|
||||
</td>
|
||||
<td class="align-middle" style="width: 35%;">
|
||||
<div class="row result align-items-center">
|
||||
@@ -87,7 +103,15 @@
|
||||
@for (entry of round.standings.entries; track entry.position) {
|
||||
<tr>
|
||||
<td class="align-middle">{{ entry.position }}</td>
|
||||
<td class="align-middle">{{ entry.team | teamText }}</td>
|
||||
<td class="align-middle">
|
||||
<app-team-display
|
||||
[team]="entry.team"
|
||||
[event]="this.event"
|
||||
[tournament]="this.tournament"
|
||||
[inline]="true"
|
||||
[explicitSubstitute]="true">
|
||||
</app-team-display>
|
||||
</td>
|
||||
<td class="align-middle">{{ entry.played }}</td>
|
||||
<td class="align-middle">{{ entry.points / entry.played | number: '1.0-2' }}</td>
|
||||
<td class="align-middle">{{ (entry.gamesWon - entry.gamesLost) / entry.played | number: '1.0-2' }}</td>
|
||||
|
||||
@@ -8,13 +8,14 @@ import {ActivatedRoute, Router} from "@angular/router";
|
||||
import {DecimalPipe} from "@angular/common";
|
||||
import {TeamPipe} from "../../pipes/team-pipe";
|
||||
import {FullNamePipe} from "../../pipes/fullname-pipe";
|
||||
import {TeamDisplayComponent} from "../team-display/team-display.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-round-overview',
|
||||
imports: [
|
||||
TeamPipe,
|
||||
DecimalPipe,
|
||||
FullNamePipe
|
||||
TeamDisplayComponent
|
||||
],
|
||||
providers: [
|
||||
TeamPipe,
|
||||
|
||||
@@ -12,11 +12,16 @@ import {Team} from "../../model/team";
|
||||
<app-player-display
|
||||
[player]="team.player1"
|
||||
[event]="event"
|
||||
[tournament]="tournament">
|
||||
[tournament]="tournament"
|
||||
[exlicitSubstitute]="explicitSubstitute">
|
||||
</app-player-display>
|
||||
|
||||
@if (event.doublesEvent && team.player2) {
|
||||
/
|
||||
@if (this.inline) {
|
||||
/
|
||||
} @else {
|
||||
<br />
|
||||
}
|
||||
<app-player-display
|
||||
[player]="team.player2"
|
||||
[event]="event"
|
||||
@@ -29,4 +34,6 @@ export class TeamDisplayComponent {
|
||||
@Input({ required: true }) team!: Team;
|
||||
@Input({ required: true }) event!: Event;
|
||||
@Input({ required: true }) tournament!: Tournament;
|
||||
@Input({ required: false }) inline: boolean = true;
|
||||
@Input({ required: false }) explicitSubstitute: boolean = false;
|
||||
}
|
||||
|
||||
@@ -122,41 +122,61 @@
|
||||
<span class="badge text-bg-success">{{ this.activeMatches().length }}</span>
|
||||
}
|
||||
</ng-template>
|
||||
<div class="tab-content-wrapper">
|
||||
@if (this.activeMatches().length > 0) {
|
||||
<h6 class="mt-3"></h6>
|
||||
|
||||
@if (this.activeMatches().length > 0) {
|
||||
<h6 class="mt-3">Actieve wedstrijden</h6>
|
||||
|
||||
@for (activeMatch of this.activeMatches(); track activeMatch.match.id) {
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<div class="col-md-2">Baan {{ activeMatch.match.court }}</div>
|
||||
<div class="col-md-3">{{ activeMatch.match.team1 | teamText }}</div>
|
||||
<div class="col-md-1">-</div>
|
||||
<div class="col-md-3">{{ activeMatch.match.team2 | teamText }}</div>
|
||||
<div class="col-md-3">{{ activeMatch.group.name }} {{ activeMatch.round.name }}</div>
|
||||
</mat-expansion-panel-header>
|
||||
<div class="row">
|
||||
<div class="col-md-3">Teller: {{ activeMatch.match.counter | fullName }}</div>
|
||||
<div class="col-md-5"></div>
|
||||
<div class="col-md-2">Starttijd: {{ activeMatch.match.startTime | date: 'HH:mm' }}</div>
|
||||
<div class="col-md-2">Duur: {{ getDuration(activeMatch.match.startTime) | date: 'mm:ss' }}</div>
|
||||
</div>
|
||||
<mat-action-row>
|
||||
<button class="align-baseline" mat-button (click)="editResult(activeMatch.match, activeMatch.group, activeMatch.round)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
Uitslag invoeren
|
||||
</button>
|
||||
<button mat-button (click)="stopMatch(activeMatch.match)">
|
||||
<mat-icon>stop</mat-icon>
|
||||
Wedstrijd stoppen
|
||||
</button>
|
||||
</mat-action-row>
|
||||
</mat-expansion-panel>
|
||||
<br>
|
||||
@for (activeMatch of this.activeMatches(); track activeMatch.match.id) {
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<div class="col-md-2 d-flex align-items-center">Baan {{ activeMatch.match.court }}</div>
|
||||
<div class="col-md-3">
|
||||
<app-team-display
|
||||
[team]="activeMatch.match.team1"
|
||||
[event]="activeMatch.event"
|
||||
[tournament]="this.tournament"
|
||||
[inline]="false">
|
||||
</app-team-display>
|
||||
</div>
|
||||
<div class="col-md-1 d-flex align-items-center">-</div>
|
||||
<div class="col-md-3">
|
||||
<app-team-display
|
||||
[team]="activeMatch.match.team2"
|
||||
[event]="activeMatch.event"
|
||||
[tournament]="this.tournament"
|
||||
[inline]="false">
|
||||
</app-team-display>
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-center">{{ activeMatch.group.name }} {{ activeMatch.round.name }}</div>
|
||||
</mat-expansion-panel-header>
|
||||
<div class="row">
|
||||
<hr/>
|
||||
<div class="col-md-3">Teller: {{ activeMatch.match.counter | fullName }}</div>
|
||||
<div class="col-md-5"></div>
|
||||
<div class="col-md-2">Starttijd: {{ activeMatch.match.startTime | date: 'HH:mm' }}</div>
|
||||
<div class="col-md-2">Duur: {{ getDuration(activeMatch.match.startTime) | date: 'mm:ss' }}</div>
|
||||
</div>
|
||||
<mat-action-row>
|
||||
<button class="align-baseline" mat-button (click)="editResult(activeMatch.match, activeMatch.event, activeMatch.group, activeMatch.round)">
|
||||
<mat-icon>leaderboard</mat-icon>
|
||||
Uitslag invoeren
|
||||
</button>
|
||||
<button mat-button (click)="changeCounter(activeMatch.match)">
|
||||
<mat-icon>person</mat-icon>
|
||||
Teller wijzigen
|
||||
</button>
|
||||
<button mat-button (click)="stopMatch(activeMatch.match)">
|
||||
<mat-icon>stop</mat-icon>
|
||||
Wedstrijd stoppen
|
||||
</button>
|
||||
</mat-action-row>
|
||||
</mat-expansion-panel>
|
||||
<br>
|
||||
}
|
||||
} @else {
|
||||
<h6 class="mt-3">Geen actieve wedstrijden</h6>
|
||||
}
|
||||
} @else {
|
||||
<h6 class="mt-3">Geen actieve wedstrijden</h6>
|
||||
}
|
||||
</div>
|
||||
</mat-tab>
|
||||
}
|
||||
@if (tournament.status == 'ONGOING' || tournament.status == 'DRAWN') {
|
||||
@@ -281,7 +301,6 @@
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- }-->
|
||||
} @else if (round.status == 'IN_PROGRESS') {
|
||||
<table class="table table-hover m-4 wide w-95">
|
||||
<tbody>
|
||||
@@ -304,18 +323,31 @@
|
||||
</td>
|
||||
<td class="align-middle w-fill">
|
||||
@if (match.status == 'NOT_STARTED') {
|
||||
<button mat-button (click)="startMatch(match)">
|
||||
<mat-icon>play_arrow</mat-icon>
|
||||
Wedstrijd starten
|
||||
</button>
|
||||
@if (match.canStart) {
|
||||
<button mat-button (click)="startMatch(match)">
|
||||
<mat-icon>play_arrow</mat-icon>
|
||||
Wedstrijd starten
|
||||
</button>
|
||||
} @else {
|
||||
<span [matTooltip]="match.cantStartReason" style="cursor: not-allowed;">
|
||||
<button mat-button disabled>
|
||||
<mat-icon>play_arrow</mat-icon>
|
||||
Wedstrijd starten
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
} @else if (match.status == 'IN_PROGRESS') {
|
||||
<button mat-button (click)="editResult(match, group, round)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
Uitslag invoeren
|
||||
<button mat-button (click)="editResult(match, event, group, round)">
|
||||
<mat-icon>leaderboard</mat-icon>
|
||||
Uitslag
|
||||
</button>
|
||||
<button mat-button (click)="changeCounter(match)">
|
||||
<mat-icon>person</mat-icon>
|
||||
Teller
|
||||
</button>
|
||||
<button mat-button (click)="stopMatch(match)">
|
||||
<mat-icon>stop</mat-icon>
|
||||
Wedstrijd stoppen
|
||||
Stoppen
|
||||
</button>
|
||||
} @else if (match.status == 'FINISHED') {
|
||||
<div class="row result align-items-center">
|
||||
@@ -330,8 +362,8 @@
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<mat-menu #finishedMatchMenu="matMenu">
|
||||
<button mat-menu-item (click)="editResult(match, group, round)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
<button mat-menu-item (click)="editResult(match, event, group, round)">
|
||||
<mat-icon>leaderboard</mat-icon>
|
||||
Uitslag bewerken
|
||||
</button>
|
||||
</mat-menu>
|
||||
@@ -413,7 +445,14 @@
|
||||
@for (entry of getStandingsForRound(round, group).entries; track entry.position) {
|
||||
<tr>
|
||||
<td class="align-middle">{{ entry.position }}</td>
|
||||
<td class="align-middle">{{ entry.team | teamText }}</td>
|
||||
<td class="align-middle">
|
||||
<app-team-display
|
||||
[team]="entry.team"
|
||||
[event]="event"
|
||||
[tournament]="this.tournament"
|
||||
[inline]="true">
|
||||
</app-team-display>
|
||||
</td>
|
||||
<td class="align-middle">{{ entry.played }}</td>
|
||||
<td class="align-middle">
|
||||
@if (entry.played > 0 ) {
|
||||
|
||||
@@ -39,3 +39,6 @@ td.w-fill {
|
||||
z-index: 1000 !important;
|
||||
}
|
||||
|
||||
.tab-content-wrapper {
|
||||
margin: 0 4px 0 4px;
|
||||
}
|
||||
|
||||
@@ -33,8 +33,10 @@ import {Standings} from "../../model/standings";
|
||||
import {HeaderService} from "../../service/header.service";
|
||||
import {TournamentPlayersComponent} from "../tournament-players/tournament-players.component";
|
||||
import {TournamentPlayer} from "../../model/tournamentPlayer";
|
||||
import {MatTooltip} from "@angular/material/tooltip";
|
||||
import {TeamDisplayComponent} from "../team-display/team-display.component";
|
||||
import {CounterSelectionComponent} from "../counter-selection/counter-selection.component";
|
||||
import {Observable, Subscription, tap} from "rxjs";
|
||||
import {MatTooltip} from '@angular/material/tooltip';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tournament-manage',
|
||||
@@ -64,8 +66,8 @@ import {TeamDisplayComponent} from "../team-display/team-display.component";
|
||||
TournamentValidateComponent,
|
||||
TournamentPlayersComponent,
|
||||
MatExpansionPanelActionRow,
|
||||
MatTooltip,
|
||||
TeamDisplayComponent,
|
||||
MatTooltip,
|
||||
],
|
||||
providers: [
|
||||
FullNamePipe,
|
||||
@@ -98,10 +100,10 @@ export class TournamentManageComponent implements OnInit, OnDestroy {
|
||||
this.activeRoundTab = params['tab'];
|
||||
}
|
||||
})
|
||||
this.tournamentService.getById(Number(id)).subscribe(data => {
|
||||
this.tournament = data;
|
||||
this.headerService.setTitle(this.tournament.name);
|
||||
});
|
||||
this.loadTournament(
|
||||
this.tournamentService.getById(Number(id)),
|
||||
(tournament) => this.headerService.setTitle(tournament.name)
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -152,56 +154,42 @@ export class TournamentManageComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
startRound(round: Round) {
|
||||
this.tournamentService.startRound(this.tournament.id, round.id).subscribe(data => {
|
||||
this.tournament = data;
|
||||
});
|
||||
this.loadTournament(this.tournamentService.startRound(this.tournament.id, round.id));
|
||||
}
|
||||
|
||||
finishRound(round: Round) {
|
||||
this.tournamentService.finishRound(this.tournament.id, round.id).subscribe(data => {
|
||||
this.tournament = data;
|
||||
});
|
||||
this.loadTournament(this.tournamentService.finishRound(this.tournament.id, round.id));
|
||||
}
|
||||
|
||||
finishGroup(group: Group) {
|
||||
this.tournamentService.finishGroup(this.tournament.id, group.id).subscribe(data => {
|
||||
this.tournament = data;
|
||||
});
|
||||
this.loadTournament(this.tournamentService.finishGroup(this.tournament.id, group.id));
|
||||
}
|
||||
|
||||
reopenGroup(group: Group) {
|
||||
this.tournamentService.reopenGroup(this.tournament.id, group.id).subscribe(data => {
|
||||
this.tournament = data;
|
||||
});
|
||||
this.loadTournament(this.tournamentService.reopenGroup(this.tournament.id, group.id));
|
||||
}
|
||||
|
||||
divideTournament() {
|
||||
this.tournamentService.divide(this.tournament.id).subscribe(data => {
|
||||
this.tournament = data;
|
||||
});
|
||||
this.loadTournament(this.tournamentService.divide(this.tournament.id));
|
||||
}
|
||||
|
||||
clearDivision() {
|
||||
this.tournamentService.clearDivision(this.tournament.id).subscribe(data => {
|
||||
this.tournament = data;
|
||||
});
|
||||
this.loadTournament(this.tournamentService.clearDivision(this.tournament.id));
|
||||
}
|
||||
|
||||
drawTournament() {
|
||||
this.tournamentService.draw(this.tournament.id).subscribe(data => {
|
||||
this.tournament = data;
|
||||
});
|
||||
this.loadTournament(this.tournamentService.draw(this.tournament.id));
|
||||
}
|
||||
|
||||
startMatch(match: Match) {
|
||||
const availableCourts = this.getAvailableCourts();
|
||||
|
||||
if (availableCourts.length == 0) {
|
||||
alert('Geen banen beschikbaar!');
|
||||
this._snackBar.open('Geen banen beschikbaar.')
|
||||
} else if (this.matchContainsPlayersThatArePlaying(match)) {
|
||||
alert('Deze wedstrijd bevat spelers die al aan het spelen zijn!');
|
||||
this._snackBar.open('Deze wedstrijd bevat spelers die al aan het spelen zijn.')
|
||||
} else if (this.matchContainsPlayersThatAreCounting(match)) {
|
||||
alert('Deze wedstrijd bevat spelers die aan het tellen zijn!');
|
||||
this._snackBar.open('Deze wedstrijd bevat spelers die aan het tellen zijn.')
|
||||
} else {
|
||||
this.courtSelectionDialog.open(CourtSelectionComponent, {
|
||||
data: {
|
||||
@@ -214,46 +202,72 @@ export class TournamentManageComponent implements OnInit, OnDestroy {
|
||||
minHeight: '250px'
|
||||
}).afterClosed().subscribe(result => {
|
||||
if (result != undefined) {
|
||||
console.log('Start match on court ' + result.court + ' with counter ' + result.counter.name);
|
||||
this.tournamentService.startMatch(this.tournament.id, match.id, result.court, result.counter.playerId).subscribe(data => {
|
||||
this.tournament = data;
|
||||
});
|
||||
this.loadTournament(this.tournamentService.startMatch(this.tournament.id, match.id, result.court, result.counter.playerId));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
matchContainsPlayersThatArePlaying(match: Match): boolean {
|
||||
let activePlayers: number[] = [];
|
||||
for (let activeMatch of this.activeMatches()) {
|
||||
activePlayers.push(activeMatch.match.team1.player1.id);
|
||||
if (activeMatch.match.team1.player2) activePlayers.push(activeMatch.match.team1.player2.id);
|
||||
activePlayers.push(activeMatch.match.team2.player1.id);
|
||||
if (activeMatch.match.team2.player2) activePlayers.push(activeMatch.match.team2.player2.id);
|
||||
}
|
||||
let matchPlayers: number[] = [];
|
||||
matchPlayers.push(match.team1.player1.id);
|
||||
if (match.team1.player2) matchPlayers.push(match.team1.player2.id);
|
||||
matchPlayers.push(match.team2.player1.id);
|
||||
if (match.team2.player2) matchPlayers.push(match.team2.player2.id);
|
||||
let playersThatArePlaying = activePlayers.filter(Set.prototype.has, new Set(matchPlayers));
|
||||
return playersThatArePlaying.length > 0;
|
||||
matchPlayers.push(this.getPlayerOrSubstitute(match.team1.player1, match.type).id);
|
||||
if (match.team1.player2) matchPlayers.push(this.getPlayerOrSubstitute(match.team1.player2, match.type).id);
|
||||
matchPlayers.push(this.getPlayerOrSubstitute(match.team2.player1, match.type).id);
|
||||
if (match.team2.player2) matchPlayers.push(this.getPlayerOrSubstitute(match.team2.player2, match.type).id);
|
||||
|
||||
let matchPlayersThatArePlaying = this.tournament.playersPlaying.filter(Set.prototype.has, new Set(matchPlayers));
|
||||
return matchPlayersThatArePlaying.length > 0;
|
||||
}
|
||||
|
||||
matchPlayersThatArePlaying(match: Match): number[] {
|
||||
let matchPlayers: number[] = [];
|
||||
|
||||
matchPlayers.push(this.getPlayerOrSubstitute(match.team1.player1, match.type).id);
|
||||
if (match.team1.player2) matchPlayers.push(this.getPlayerOrSubstitute(match.team1.player2, match.type).id);
|
||||
matchPlayers.push(this.getPlayerOrSubstitute(match.team2.player1, match.type).id);
|
||||
if (match.team2.player2) matchPlayers.push(this.getPlayerOrSubstitute(match.team2.player2, match.type).id);
|
||||
|
||||
return this.tournament.playersPlaying.filter(Set.prototype.has, new Set(matchPlayers));
|
||||
}
|
||||
|
||||
matchContainsPlayersThatAreCounting(match: Match): boolean {
|
||||
let currentCounters: number[] = [];
|
||||
for (let activeMatch of this.activeMatches()) {
|
||||
currentCounters.push(activeMatch.match.counter.id);
|
||||
}
|
||||
|
||||
let matchPlayers: number[] = [];
|
||||
matchPlayers.push(match.team1.player1.id);
|
||||
if (match.team1.player2) matchPlayers.push(match.team1.player2.id);
|
||||
matchPlayers.push(match.team2.player1.id);
|
||||
if (match.team2.player2) matchPlayers.push(match.team2.player2.id);
|
||||
matchPlayers.push(this.getPlayerOrSubstitute(match.team1.player1, match.type).id);
|
||||
if (match.team1.player2) matchPlayers.push(this.getPlayerOrSubstitute(match.team1.player2, match.type).id);
|
||||
matchPlayers.push(this.getPlayerOrSubstitute(match.team2.player1, match.type).id);
|
||||
if (match.team2.player2) matchPlayers.push(this.getPlayerOrSubstitute(match.team2.player2, match.type).id);
|
||||
|
||||
let playersThatAreCounting = currentCounters.filter(Set.prototype.has, new Set(matchPlayers));
|
||||
return playersThatAreCounting.length > 0;
|
||||
let matchPlayersThatAreCounting = this.tournament.playersCounting.filter(Set.prototype.has, new Set(matchPlayers));
|
||||
return matchPlayersThatAreCounting.length > 0;
|
||||
}
|
||||
|
||||
matchPlayersThatAreCounting(match: Match): number[] {
|
||||
let matchPlayers: number[] = [];
|
||||
|
||||
matchPlayers.push(this.getPlayerOrSubstitute(match.team1.player1, match.type).id);
|
||||
if (match.team1.player2) matchPlayers.push(this.getPlayerOrSubstitute(match.team1.player2, match.type).id);
|
||||
matchPlayers.push(this.getPlayerOrSubstitute(match.team2.player1, match.type).id);
|
||||
if (match.team2.player2) matchPlayers.push(this.getPlayerOrSubstitute(match.team2.player2, match.type).id);
|
||||
|
||||
return this.tournament.playersCounting.filter(Set.prototype.has, new Set(matchPlayers));
|
||||
}
|
||||
|
||||
getTournamentPlayerFromPlayer(player: Player): TournamentPlayer {
|
||||
for (let tournamentPlayer of this.tournament.tournamentPlayers) {
|
||||
if (tournamentPlayer.playerId == player.id) {
|
||||
return tournamentPlayer;
|
||||
}
|
||||
}
|
||||
return null!;
|
||||
}
|
||||
|
||||
getTournamentPlayerFromId(tournamentPlayerId: number): TournamentPlayer {
|
||||
for (let tournamentPlayer of this.tournament.tournamentPlayers) {
|
||||
if (tournamentPlayer.id == tournamentPlayerId) {
|
||||
return tournamentPlayer;
|
||||
}
|
||||
}
|
||||
return null!;
|
||||
}
|
||||
|
||||
getAvailableCourts(): number[] {
|
||||
@@ -267,40 +281,41 @@ export class TournamentManageComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
getAvailableCounters(match: Match): TournamentPlayer[] {
|
||||
const activePlayerIds = new Set(
|
||||
this.activeMatches().flatMap(activeMatch => [
|
||||
activeMatch.match.team1.player1.id,
|
||||
activeMatch.match.team1.player2?.id,
|
||||
activeMatch.match.team2.player1.id,
|
||||
activeMatch.match.team2.player2?.id
|
||||
].filter(id => id !== undefined))
|
||||
);
|
||||
const matchPlayers: number[] = [];
|
||||
matchPlayers.push(this.getPlayerOrSubstitute(match.team1.player1, match.type).id);
|
||||
if (match.team1.player2) matchPlayers.push(this.getPlayerOrSubstitute(match.team1.player2, match.type).id);
|
||||
matchPlayers.push(this.getPlayerOrSubstitute(match.team2.player1, match.type).id);
|
||||
if (match.team2.player2) matchPlayers.push(this.getPlayerOrSubstitute(match.team2.player2, match.type).id);
|
||||
|
||||
const playerIdsInMatchToBeStarted = new Set([
|
||||
match.team1.player1.id,
|
||||
match.team1.player2?.id,
|
||||
match.team2.player1.id,
|
||||
match.team2.player2?.id
|
||||
].filter(id => id !== undefined));
|
||||
const counterIds = this.tournament.playersAvailable.filter(player => !matchPlayers.includes(player));
|
||||
|
||||
return this.tournament.tournamentPlayers.filter(
|
||||
player =>
|
||||
!player.counting
|
||||
&& !activePlayerIds.has(player.playerId)
|
||||
&& !playerIdsInMatchToBeStarted.has(player.playerId)
|
||||
return counterIds.map(id => this.getTournamentPlayerFromId(id));
|
||||
}
|
||||
|
||||
getPlayerOrSubstitute(player: Player, type: String): TournamentPlayer {
|
||||
return this.getSubstituteForEvent(player, type) || this.getTournamentPlayerFromPlayer(player);
|
||||
}
|
||||
|
||||
getSubstituteForEvent(player: Player, type: String): TournamentPlayer | undefined {
|
||||
const tournamentPlayer = this.tournament.tournamentPlayers.find(
|
||||
tp => tp.playerId === player.id
|
||||
);
|
||||
if (!tournamentPlayer) return undefined;
|
||||
|
||||
const substitution = tournamentPlayer.substitutions.find(
|
||||
s => s.event === type
|
||||
);
|
||||
if (!substitution) return undefined;
|
||||
|
||||
return this.getTournamentPlayerFromId(substitution.substitute);
|
||||
}
|
||||
|
||||
stopMatch(match: Match) {
|
||||
this.tournamentService.stopMatch(this.tournament.id, match.id).subscribe(data => {
|
||||
this.tournament = data;
|
||||
})
|
||||
this.loadTournament(this.tournamentService.stopMatch(this.tournament.id, match.id));
|
||||
}
|
||||
|
||||
newRound(group: Group) {
|
||||
this.tournamentService.newRound(this.tournament.id, group.id).subscribe(data => {
|
||||
this.tournament = data;
|
||||
})
|
||||
this.loadTournament(this.tournamentService.newRound(this.tournament.id, group.id));
|
||||
}
|
||||
|
||||
getStrength(strength: string | undefined) {
|
||||
@@ -326,7 +341,7 @@ export class TournamentManageComponent implements OnInit, OnDestroy {
|
||||
for (const round of group.rounds) {
|
||||
for (const match of round.matches) {
|
||||
if (match.status == 'IN_PROGRESS') {
|
||||
matches.push(new ActiveMatch(match, round, group));
|
||||
matches.push(new ActiveMatch(match, round, group, event));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,16 +372,15 @@ export class TournamentManageComponent implements OnInit, OnDestroy {
|
||||
|
||||
matchResultDialog = inject(MatDialog);
|
||||
courtSelectionDialog = inject(MatDialog);
|
||||
counterSelectionDialog = inject(MatDialog);
|
||||
|
||||
editResult(match: Match, group: Group, round: Round) {
|
||||
editResult(match: Match, event: Event, group: Group, round: Round) {
|
||||
this.matchResultDialog.open(MatchResultComponent, {
|
||||
data: {match: match, group: group, round: round},
|
||||
data: {match: match, tournament: this.tournament, event: event, group: group, round: round},
|
||||
minWidth: '800px'
|
||||
}).afterClosed().subscribe(result => {
|
||||
if (result != undefined) {
|
||||
this.tournamentService.saveResult(this.tournament.id, result.matchId, result).subscribe(data => {
|
||||
this.tournament = data;
|
||||
})
|
||||
this.loadTournament(this.tournamentService.saveResult(this.tournament.id, result.matchId, result));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -417,16 +431,96 @@ export class TournamentManageComponent implements OnInit, OnDestroy {
|
||||
return count;
|
||||
}
|
||||
|
||||
changeCounter(match: Match) {
|
||||
this.counterSelectionDialog.open(CounterSelectionComponent, {
|
||||
data: {
|
||||
match: match,
|
||||
availableCounters: this.getAvailableCounters(match)
|
||||
},
|
||||
minWidth: '800px',
|
||||
minHeight: '250px'
|
||||
}).afterClosed().subscribe(result => {
|
||||
if (result != undefined) {
|
||||
this.loadTournament(this.tournamentService.updateCounter(this.tournament.id, match.id, result.counter.playerId));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private loadTournament(
|
||||
observable: Observable<Tournament>,
|
||||
afterLoad?: (tournament: Tournament) => void
|
||||
): Subscription {
|
||||
return observable.subscribe(data => {
|
||||
this.tournament = data;
|
||||
this.updateMatchAvailability();
|
||||
afterLoad?.(data); // Optional chaining
|
||||
});
|
||||
}
|
||||
|
||||
private updateMatchAvailability() {
|
||||
for (const event of this.tournament.events) {
|
||||
for (const group of event.groups) {
|
||||
for (const round of group.rounds) {
|
||||
for (const match of round.matches) {
|
||||
let canStart = true;
|
||||
let matchPlayersThatArePlaying = this.matchPlayersThatArePlaying(match);
|
||||
let matchPlayersThatAreCounting = this.matchPlayersThatAreCounting(match);
|
||||
|
||||
let cantStartReason = "";
|
||||
|
||||
if (matchPlayersThatArePlaying.length > 0) {
|
||||
canStart = false;
|
||||
cantStartReason = this.joinWithEn(matchPlayersThatArePlaying.map(m => this.getTournamentPlayerFromId(m).name));
|
||||
if (matchPlayersThatArePlaying.length == 1) {
|
||||
cantStartReason += " is al aan het spelen";
|
||||
} else {
|
||||
cantStartReason += " zijn al aan het spelen";
|
||||
}
|
||||
}
|
||||
|
||||
if (matchPlayersThatAreCounting.length > 0) {
|
||||
canStart = false;
|
||||
if (cantStartReason.length > 0) {
|
||||
cantStartReason += " en ";
|
||||
}
|
||||
cantStartReason += this.joinWithEn(matchPlayersThatAreCounting.map(m => this.getTournamentPlayerFromId(m).name));
|
||||
if (matchPlayersThatAreCounting.length == 1) {
|
||||
cantStartReason += " is aan het tellen";
|
||||
} else {
|
||||
cantStartReason += " zijn aan het tellen";
|
||||
}
|
||||
}
|
||||
|
||||
match.canStart = canStart;
|
||||
match.cantStartReason = cantStartReason;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
joinWithEn(items: string[], separator = ", "): string {
|
||||
const len = items.length;
|
||||
|
||||
if (len === 0) return "";
|
||||
if (len === 1) return items[0];
|
||||
if (len === 2) return items.join(" en ");
|
||||
|
||||
return items.slice(0, -1).join(separator) + `${separator}and ${items[len - 1]}`;
|
||||
}
|
||||
}
|
||||
|
||||
class ActiveMatch {
|
||||
constructor(match: Match, round: Round, group: Group) {
|
||||
constructor(match: Match, round: Round, group: Group, event: Event) {
|
||||
this.match = match;
|
||||
this.round = round;
|
||||
this.group = group;
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
match: Match;
|
||||
round: Round;
|
||||
group: Group;
|
||||
event: Event;
|
||||
}
|
||||
|
||||
@@ -2,14 +2,13 @@ import {Component, inject, Input, OnInit} from '@angular/core';
|
||||
import {CurrencyPipe} from "@angular/common";
|
||||
import {MatSlideToggle, MatSlideToggleChange} from "@angular/material/slide-toggle";
|
||||
import {TournamentService} from "../../service/tournament.service";
|
||||
import {ActivatedRoute, Router} from "@angular/router";
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
import {Tournament} from "../../model/tournament";
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||
import {MatIcon} from "@angular/material/icon";
|
||||
import {MatIconButton} from "@angular/material/button";
|
||||
import {MatMenu, MatMenuItem, MatMenuTrigger} from "@angular/material/menu";
|
||||
import {CourtSelectionComponent} from "../court-selection/court-selection.component";
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
import {SubstituteSelectionComponent} from "../substitute-selection/substitute-selection.component";
|
||||
import {TournamentPlayer} from "../../model/tournamentPlayer";
|
||||
@@ -40,17 +39,19 @@ export class TournamentPlayersComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private tournamentService: TournamentService,
|
||||
private _snackBar: MatSnackBar,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private _snackBar: MatSnackBar,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
console.log('Tournament received from parent:', this.tournament);
|
||||
// const id = this.route.snapshot.paramMap.get('id');
|
||||
// this.tournamentService.getById(Number(id)).subscribe(data => {
|
||||
// this.tournament = data;
|
||||
// });
|
||||
if (!this.tournament) {
|
||||
const id = Number(this.route.snapshot.paramMap.get('id'));
|
||||
if (id) {
|
||||
this.tournamentService.getById(Number(id)).subscribe(data => {
|
||||
this.tournament = data;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
playerPaid($event: MatSlideToggleChange, playerId: number) {
|
||||
@@ -77,13 +78,8 @@ export class TournamentPlayersComponent implements OnInit {
|
||||
minHeight: '250px'
|
||||
}).afterClosed().subscribe(result => {
|
||||
if (result != undefined) {
|
||||
console.log('Substitutes selected for ' + player.name + ': ');
|
||||
console.log(result.substitutions);
|
||||
console.log(result.substitutions[0].event + ': ' + result.substitutions[0].substitute);
|
||||
console.log(result.substitutions[1].event + ': ' + result.substitutions[1].substitute);
|
||||
this.tournamentService.playerSubstitute(this.tournament.id, player.playerId, result.substitutions).subscribe(data => {
|
||||
this.tournament = data;
|
||||
console.log(this.tournament);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,4 +13,6 @@ export class Match {
|
||||
games: Game[];
|
||||
court: number;
|
||||
counter: Player;
|
||||
canStart?: boolean;
|
||||
cantStartReason?: string;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ export class Tournament {
|
||||
costsPerEvent: number[] = [10, 20, 0];
|
||||
courts: number;
|
||||
active: boolean;
|
||||
playersPlaying: number[];
|
||||
playersCounting: number[];
|
||||
playersAvailable: number[];
|
||||
|
||||
static getStatus(tournament: Tournament): string {
|
||||
if (tournament.status == "CLOSED") return "Afgerond";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// header.service.ts
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
|
||||
@@ -97,6 +97,10 @@ export class TournamentService {
|
||||
return this.http.post<Tournament>(`${this.tournamentsUrl}/${tournamentId}/players/${playerId}/substitutions`, substitutions)
|
||||
}
|
||||
|
||||
public updateCounter(tournamentId: number, matchId: number, counter: number): Observable<Tournament> {
|
||||
return this.http.patch<Tournament>(`${this.tournamentsUrl}/${tournamentId}/matches/${matchId}/update?counter=${counter}`, null);
|
||||
}
|
||||
|
||||
public addTestData(): Observable<void> {
|
||||
return this.http.get<void>(`${environment.backendUrl}/testdata`);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { bootstrapApplication, BootstrapContext } from '@angular/platform-browser';
|
||||
import { AppComponent } from './app/app.component';
|
||||
import { config } from './app/app.config.server';
|
||||
|
||||
const bootstrap = () => bootstrapApplication(AppComponent, config);
|
||||
const bootstrap = (context: BootstrapContext) => bootstrapApplication(AppComponent, config, context);
|
||||
|
||||
export default bootstrap;
|
||||
|
||||
@@ -7,3 +7,6 @@ body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
|
||||
z-index: 2000 !important;
|
||||
}
|
||||
*/
|
||||
.mat-tooltip {
|
||||
white-space: pre-line !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user