Move to new server
16
.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Editor configuration, see https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
||||||
42
.gitignore
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
||||||
|
|
||||||
|
# Compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
/bazel-out
|
||||||
|
|
||||||
|
# Node
|
||||||
|
/node_modules*
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
.idea/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.history/*
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
/.angular/cache
|
||||||
|
.sass-cache/
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# System files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
16
Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
FROM node:20.13.1-alpine3.19 as build
|
||||||
|
WORKDIR /app/src
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN apk update
|
||||||
|
RUN apk add yarn
|
||||||
|
RUN yarn install --frozen-lockfile --no-progress
|
||||||
|
COPY . ./
|
||||||
|
RUN yarn build
|
||||||
|
|
||||||
|
FROM node:20.13.1-alpine3.19
|
||||||
|
RUN addgroup -S k8s-group && adduser -S k8s-user -G k8s-group
|
||||||
|
USER root
|
||||||
|
WORKDIR /usr/app
|
||||||
|
COPY --from=build /app/src/dist/swiss-client/ ./
|
||||||
|
CMD node server/server.mjs
|
||||||
|
EXPOSE 4000
|
||||||
58
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// This Jenkinsfile defines a declarative pipeline
|
||||||
|
pipeline {
|
||||||
|
|
||||||
|
tools {
|
||||||
|
nodejs 'node'
|
||||||
|
}
|
||||||
|
|
||||||
|
options {
|
||||||
|
disableConcurrentBuilds()
|
||||||
|
}
|
||||||
|
agent any
|
||||||
|
|
||||||
|
// Defines the sequence of stages that will be executed
|
||||||
|
stages {
|
||||||
|
// This stage checks out the source code from the SCM (Source Code Management) system
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
// This command checks out the source code from the SCM into the Jenkins workspace
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Build') {
|
||||||
|
steps {
|
||||||
|
sh 'yarn install --no-progress'
|
||||||
|
sh 'npm run ng build --prod'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Docker Build') {
|
||||||
|
steps {
|
||||||
|
sh 'docker build -t upquark/swiss-client:latest -t upquark/swiss-client:${BUILD_NUMBER} .'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Push the Docker Image to DockerHUb') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
withCredentials([string(credentialsId: 'c7783e4f-2f79-482f-885f-dfb39f8c02d3', variable: 'docker_hub')]) {
|
||||||
|
sh 'docker login -u upquark -p ${docker_hub}'
|
||||||
|
}
|
||||||
|
sh 'docker push upquark/swiss-client:latest'
|
||||||
|
sh 'docker push upquark/swiss-client:${BUILD_NUMBER}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Deploy to Kubernetes') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
kubeconfig(credentialsId: 'k3s-kubeconfig') {
|
||||||
|
sh 'cat k8s/deployment.yaml | sed "s/latest/$BUILD_NUMBER/g" | kubectl apply -n swiss -f -'
|
||||||
|
sh 'kubectl apply -f k8s/service.yaml -n swiss'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
README.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# SwissClient
|
||||||
|
|
||||||
|
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.0.5.
|
||||||
|
|
||||||
|
## Development server
|
||||||
|
|
||||||
|
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
|
||||||
|
|
||||||
|
## Code scaffolding
|
||||||
|
|
||||||
|
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||||
|
|
||||||
|
## Running end-to-end tests
|
||||||
|
|
||||||
|
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
|
||||||
|
|
||||||
|
## Further help
|
||||||
|
|
||||||
|
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
||||||
48
TODO.txt
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
Knop Nieuw Toernooi naar Toernooienlijst
|
||||||
|
Geen Flat buttons gebruiken
|
||||||
|
Knop Nieuwe Speler naar Spelerslijst
|
||||||
|
Tabellen stijl gelijktrekken
|
||||||
|
Uitslag verbeteren
|
||||||
|
Gebruik table-hover
|
||||||
|
Ronde afsluiten indien wedstrijden klaar
|
||||||
|
Wedstrijd stoppen
|
||||||
|
Alle toernooi-initialisatie-acties onderbrengen in manage
|
||||||
|
Loting nieuwe ronde
|
||||||
|
Baanbeheer
|
||||||
|
Wedstrijdbriefjes
|
||||||
|
Bij alle lotingen zorgen dat speler maar 1 keer uitgeloot wordt
|
||||||
|
Rondemenu verbergen bij geen opties
|
||||||
|
Animatie uit bij klik op tab
|
||||||
|
Badges in tabs voor aantal actieve wedstrijden
|
||||||
|
Loting los onderdeel wissen
|
||||||
|
Menu per wedstrijd
|
||||||
|
Alles HTTP POST
|
||||||
|
Gemiddelden tussen haakjes in stand
|
||||||
|
Niet-deelnemers per ronde bijhouden
|
||||||
|
Rondestatus DRAWN nodig?
|
||||||
|
Icoontjes langslopen
|
||||||
|
Badge bij actieve wedstrijden voor aantal
|
||||||
|
Max en min voor invoer uitslag?
|
||||||
|
Spelerslijst
|
||||||
|
Presentiemelding
|
||||||
|
Spelerslijst uitbreiden met onderdelen en kosten
|
||||||
|
TournamentPlayers testen met nieuw toernooi
|
||||||
|
Printbare overzichten
|
||||||
|
Stand per ronde
|
||||||
|
Player-registrations: oude toernooien verbergen via knop
|
||||||
|
Onderdeel afsluiten
|
||||||
|
Wedstrijd opgave
|
||||||
|
Blessures
|
||||||
|
Titels pagina's
|
||||||
|
Authenticatie
|
||||||
|
Progress indicator tijdens communicatie backend
|
||||||
|
|
||||||
|
https://blog.shhdharmen.me/browser-storage-in-angular-ssr
|
||||||
|
|
||||||
|
Won't do / later:
|
||||||
|
|
||||||
|
Date-picker
|
||||||
|
DTO's short/full
|
||||||
|
team1 + team2 => team[]
|
||||||
|
Validaties in controllers op juiste status
|
||||||
|
Voorkom benodigde dubbelklik op knoppen bij switchen tab
|
||||||
119
angular.json
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"swiss-client": {
|
||||||
|
"projectType": "application",
|
||||||
|
"schematics": {
|
||||||
|
"@schematics/angular:component": {
|
||||||
|
"style": "scss"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"prefix": "app",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:application",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/swiss-client",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"browser": "src/main.ts",
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js",
|
||||||
|
"@angular/localize/init"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.app.json",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "public"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||||
|
"@angular/material/prebuilt-themes/azure-blue.css",
|
||||||
|
"src/styles.css"
|
||||||
|
],
|
||||||
|
"scripts": [],
|
||||||
|
"server": "src/main.server.ts",
|
||||||
|
"prerender": true,
|
||||||
|
"ssr": {
|
||||||
|
"entry": "server.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "2MB",
|
||||||
|
"maximumError": "3MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "2kB",
|
||||||
|
"maximumError": "4kB"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"optimization": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.development.ts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "swiss-client:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "swiss-client:build:development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js",
|
||||||
|
"zone.js/testing",
|
||||||
|
"@angular/localize/init"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.spec.json",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "public"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"@angular/material/prebuilt-themes/azure-blue.css",
|
||||||
|
"src/styles.css"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"analytics": "95cdfcca-c4c3-4279-80f4-6ccad3383c37"
|
||||||
|
}
|
||||||
|
}
|
||||||
20
k8s/deployment.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: swiss-client
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: swiss-client
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: swiss-client
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: swiss-client
|
||||||
|
image: upquark/swiss-client:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 4000
|
||||||
12
k8s/service.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: swiss-client
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
selector:
|
||||||
|
app: swiss-client
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 4000
|
||||||
|
nodePort: 30042
|
||||||
55
package.json
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"name": "swiss-client",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"start": "ng serve",
|
||||||
|
"build": "ng build",
|
||||||
|
"watch": "ng build --watch --configuration development",
|
||||||
|
"test": "ng test",
|
||||||
|
"serve:ssr:swiss-client": "node dist/swiss-client/server/server.mjs"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "^18.2.5",
|
||||||
|
"@angular/cdk": "^18.2.5",
|
||||||
|
"@angular/common": "^18.2.5",
|
||||||
|
"@angular/compiler": "^18.2.5",
|
||||||
|
"@angular/core": "^18.2.5",
|
||||||
|
"@angular/forms": "^18.2.5",
|
||||||
|
"@angular/material": "^18.2.5",
|
||||||
|
"@angular/material-moment-adapter": "^18.2.5",
|
||||||
|
"@angular/platform-browser": "^18.2.5",
|
||||||
|
"@angular/platform-browser-dynamic": "^18.2.5",
|
||||||
|
"@angular/platform-server": "^18.2.5",
|
||||||
|
"@angular/router": "^18.2.5",
|
||||||
|
"@angular/ssr": "^18.2.5",
|
||||||
|
"@ng-bootstrap/ng-bootstrap": "^17.0.0",
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"bootstrap": "^5.3.2",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"moment": "2.18.1",
|
||||||
|
"ngx-cookie-service-ssr": "^18.0.0",
|
||||||
|
"ngx-mask": "^18.0.0",
|
||||||
|
"rxjs": "~7.8.0",
|
||||||
|
"ts-enums": "^0.0.6",
|
||||||
|
"tslib": "^2.3.0",
|
||||||
|
"zone.js": "~0.14.10"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/build-angular": "^18.2.5",
|
||||||
|
"@angular/cli": "^18.2.5",
|
||||||
|
"@angular/compiler-cli": "^18.2.5",
|
||||||
|
"@angular/localize": "^18.2.5",
|
||||||
|
"@types/express": "^4.17.17",
|
||||||
|
"@types/jasmine": "~5.1.0",
|
||||||
|
"@types/node": "^18.18.0",
|
||||||
|
"jasmine-core": "~5.1.0",
|
||||||
|
"karma": "~6.4.0",
|
||||||
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
|
"karma-coverage": "~2.2.0",
|
||||||
|
"karma-jasmine": "~5.1.0",
|
||||||
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
|
"typescript": "~5.4.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
public/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 262 KiB |
BIN
public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 764 B |
BIN
public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/shuttle.jpg
Executable file
|
After Width: | Height: | Size: 13 KiB |
61
server.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { APP_BASE_HREF } from '@angular/common';
|
||||||
|
import { CommonEngine } from '@angular/ssr';
|
||||||
|
import express from 'express';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { dirname, join, resolve } from 'node:path';
|
||||||
|
import bootstrap from './src/main.server';
|
||||||
|
|
||||||
|
// The Express app is exported so that it can be used by serverless Functions.
|
||||||
|
export function app(): express.Express {
|
||||||
|
const server = express();
|
||||||
|
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const browserDistFolder = resolve(serverDistFolder, '../browser');
|
||||||
|
const indexHtml = join(serverDistFolder, 'index.server.html');
|
||||||
|
|
||||||
|
const commonEngine = new CommonEngine();
|
||||||
|
|
||||||
|
server.set('view engine', 'html');
|
||||||
|
server.set('views', browserDistFolder);
|
||||||
|
|
||||||
|
// Example Express Rest API endpoints
|
||||||
|
// server.get('/api/**', (req, res) => { });
|
||||||
|
// Serve static files from /browser
|
||||||
|
server.get('**', express.static(browserDistFolder, {
|
||||||
|
maxAge: '1y',
|
||||||
|
index: 'index.html',
|
||||||
|
}));
|
||||||
|
|
||||||
|
// All regular routes use the Angular engine
|
||||||
|
server.get('**', (req, res, next) => {
|
||||||
|
const { protocol, originalUrl, baseUrl, headers } = req;
|
||||||
|
|
||||||
|
commonEngine
|
||||||
|
.render({
|
||||||
|
bootstrap,
|
||||||
|
documentFilePath: indexHtml,
|
||||||
|
url: `${protocol}://${headers.host}${originalUrl}`,
|
||||||
|
publicPath: browserDistFolder,
|
||||||
|
providers: [
|
||||||
|
{ provide: APP_BASE_HREF, useValue: baseUrl },
|
||||||
|
{ provide: 'REQUEST', useValue: req },
|
||||||
|
{ provide: 'RESPONSE', useValue: res }
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.then((html) => res.send(html))
|
||||||
|
.catch((err) => next(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(): void {
|
||||||
|
const port = process.env['PORT'] || 4000;
|
||||||
|
|
||||||
|
// Start up the Node server
|
||||||
|
const server = app();
|
||||||
|
server.listen(port, () => {
|
||||||
|
console.log(`Node Express server listening on http://localhost:${port}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
46
src/app/app.component.html
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div class="row toolbar">
|
||||||
|
<mat-toolbar>
|
||||||
|
<img ngSrc="/shuttle.jpg" width="50" height="50" priority alt=""/>
|
||||||
|
<!--
|
||||||
|
<button mat-mini-fab class="menu-button">
|
||||||
|
<mat-icon class="menu-button-icon" style="color: white;">menu</mat-icon>
|
||||||
|
</button>
|
||||||
|
-->
|
||||||
|
<h5 class="m-3">{{ title }}</h5>
|
||||||
|
<span class="spacer"></span>
|
||||||
|
<a routerLink="/tournaments" mat-button>
|
||||||
|
<mat-icon>list</mat-icon>
|
||||||
|
Toernooien
|
||||||
|
</a>
|
||||||
|
<a routerLink="/players" mat-button>
|
||||||
|
<mat-icon>group</mat-icon>
|
||||||
|
Spelers
|
||||||
|
</a>
|
||||||
|
@if (this.userService.isLoggedIn()) {
|
||||||
|
<button mat-flat-button [matMenuTriggerFor]="accountMenu">
|
||||||
|
<mat-icon>person</mat-icon>
|
||||||
|
{{ user }}
|
||||||
|
</button>
|
||||||
|
<mat-menu #accountMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="logOut()">
|
||||||
|
<mat-icon>exit_to_app</mat-icon>
|
||||||
|
Uitloggen
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="addTestData()">
|
||||||
|
<mat-icon>science</mat-icon>
|
||||||
|
Testtoernooi toevoegen
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
} @else {
|
||||||
|
<button mat-flat-button disabled>
|
||||||
|
<mat-icon>person</mat-icon>
|
||||||
|
Niet ingelogd
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</mat-toolbar>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-3">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
30
src/app/app.component.scss
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
a {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
div.toolbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
div.container {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-button {
|
||||||
|
background-color: rgb(0, 92, 187);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-button-icon {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-toolbar {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
56
src/app/app.component.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||||
|
import {ActivatedRoute, NavigationEnd, Router, RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router';
|
||||||
|
import {CommonModule, NgOptimizedImage} from "@angular/common";
|
||||||
|
import {MatAnchor, MatButton, MatIconButton, MatMiniFabButton} from "@angular/material/button";
|
||||||
|
import {MatIcon} from "@angular/material/icon";
|
||||||
|
import {MatToolbar} from "@angular/material/toolbar";
|
||||||
|
import {TitleService} from "./service/title.service";
|
||||||
|
import {Subscription} from "rxjs";
|
||||||
|
import {MatMenu, MatMenuItem, MatMenuTrigger} from "@angular/material/menu";
|
||||||
|
import {UserService} from "./authentication/user.service";
|
||||||
|
import {TournamentService} from "./service/tournament.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
standalone: true,
|
||||||
|
imports: [RouterOutlet, CommonModule, RouterLink, RouterLinkActive, MatAnchor, MatIcon, MatButton, MatToolbar, NgOptimizedImage, MatIconButton, MatMiniFabButton, MatMenuTrigger, MatMenu, MatMenuItem],
|
||||||
|
providers: [TitleService],
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrl: './app.component.scss'
|
||||||
|
})
|
||||||
|
export class AppComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
title: string;
|
||||||
|
titleSubscription: Subscription;
|
||||||
|
user: string;
|
||||||
|
userSubscription: Subscription;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected userService: UserService,
|
||||||
|
private router: Router,
|
||||||
|
protected activatedRoute: ActivatedRoute,
|
||||||
|
private titleService: TitleService,
|
||||||
|
private tournamentService: TournamentService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.titleSubscription = this.titleService.currentTitle.subscribe(newTitle => this.title = newTitle);
|
||||||
|
this.userSubscription = this.userService.currentUser.subscribe(newUser => this.user = newUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.titleSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
logOut() {
|
||||||
|
this.userService.removeUser();
|
||||||
|
this.router.navigate(['/auth/login']);
|
||||||
|
}
|
||||||
|
|
||||||
|
addTestData() {
|
||||||
|
this.tournamentService.addTestData().subscribe(data => {
|
||||||
|
this.router.navigate(['/tournaments']);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/app/app.config.server.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
|
||||||
|
import { provideServerRendering } from '@angular/platform-server';
|
||||||
|
import { appConfig } from './app.config';
|
||||||
|
|
||||||
|
const serverConfig: ApplicationConfig = {
|
||||||
|
providers: [
|
||||||
|
provideServerRendering()
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const config = mergeApplicationConfig(appConfig, serverConfig);
|
||||||
29
src/app/app.config.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import {ApplicationConfig, provideZoneChangeDetection} from '@angular/core';
|
||||||
|
import {provideRouter} from '@angular/router';
|
||||||
|
|
||||||
|
import {routes} from './app.routes';
|
||||||
|
import {provideClientHydration} from '@angular/platform-browser';
|
||||||
|
import {HTTP_INTERCEPTORS, provideHttpClient, withFetch, withInterceptorsFromDi} from "@angular/common/http";
|
||||||
|
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";
|
||||||
|
|
||||||
|
export const appConfig: ApplicationConfig = {
|
||||||
|
providers: [
|
||||||
|
provideZoneChangeDetection({eventCoalescing: true}),
|
||||||
|
provideRouter(routes),
|
||||||
|
provideClientHydration(),
|
||||||
|
provideHttpClient(withFetch(), withInterceptorsFromDi()),
|
||||||
|
provideAnimationsAsync(),
|
||||||
|
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 },
|
||||||
|
provideNgxMask(),
|
||||||
|
]
|
||||||
|
};
|
||||||
35
src/app/app.routes.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Routes } from '@angular/router';
|
||||||
|
import { PlayerListComponent} from "./components/player-list/player-list.component";
|
||||||
|
import {PlayerEditComponent} from "./components/player-edit/player-edit.component";
|
||||||
|
import {TournamentListComponent} from "./components/tournament-list/tournament-list.component";
|
||||||
|
import {TournamentEditComponent} from "./components/tournament-edit/tournament-edit.component";
|
||||||
|
import {PlayerRegistrationsComponent} from "./components/player-registrations/player-registrations.component";
|
||||||
|
import {TournamentRegistrationsComponent} from "./components/tournament-registrations/tournament-registrations.component";
|
||||||
|
import {TournamentValidateComponent} from "./components/tournament-validate/tournament-validate.component";
|
||||||
|
import {TournamentDivideComponent} from "./components/tournament-divide/tournament-divide.component";
|
||||||
|
import {TournamentDrawComponent} from "./components/tournament-draw/tournament-draw.component";
|
||||||
|
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";
|
||||||
|
|
||||||
|
export const routes: Routes = [
|
||||||
|
{ path: '', component: TournamentListComponent, canActivate: [AuthGuard], title: 'Toernooien' },
|
||||||
|
{ path: 'tournaments', component: TournamentListComponent, canActivate: [AuthGuard], title: 'Toernooien' },
|
||||||
|
{ path: 'tournaments/add', component: TournamentEditComponent, canActivate: [AuthGuard], title: 'Nieuw Toernooi' },
|
||||||
|
{ path: 'tournaments/:id/edit', component: TournamentEditComponent, canActivate: [AuthGuard], title: 'Bewerk Toernooi' },
|
||||||
|
{ path: 'tournaments/:id/registrations', component: TournamentRegistrationsComponent, canActivate: [AuthGuard], title: 'Inschrijvingen' },
|
||||||
|
{ path: 'tournaments/:id/validate', component: TournamentValidateComponent, canActivate: [AuthGuard], title: 'Toernooi' },
|
||||||
|
{ path: 'tournaments/:id/divide', component: TournamentDivideComponent, canActivate: [AuthGuard], title: 'Toernooi valideren' },
|
||||||
|
{ path: 'tournaments/:id/draw', component: TournamentDrawComponent, canActivate: [AuthGuard], title: 'Toernooi loten' },
|
||||||
|
{ path: 'tournaments/:id/manage', component: TournamentManageComponent, canActivate: [AuthGuard] },
|
||||||
|
{ path: 'tournaments/:id/manage/:tab', component: TournamentManageComponent, canActivate: [AuthGuard], title: 'Toernooien' },
|
||||||
|
{ path: 'players', component: PlayerListComponent, canActivate: [AuthGuard], title: 'Spelers' },
|
||||||
|
{ path: 'players/add', component: PlayerEditComponent, canActivate: [AuthGuard], title: 'Nieuwe Speler' },
|
||||||
|
{ path: 'players/edit/:id', component: PlayerEditComponent, canActivate: [AuthGuard], title: 'Bewerk Speler' },
|
||||||
|
{ path: 'players/:id/registrations', component: PlayerRegistrationsComponent, canActivate: [AuthGuard], title: 'Inschrijvingen' },
|
||||||
|
{ path: 'tournaments/:id/rounds/:roundId/matchsheets', component: MatchSheetsComponent, canActivate: [AuthGuard], title: 'Wedstrijdbriefjes' },
|
||||||
|
{ path: 'tournaments/:id/rounds/:roundId/overview', component: RoundOverviewComponent, canActivate: [AuthGuard], title: 'Rondeoverzicht' },
|
||||||
|
{ path: 'auth/login', component: LoginComponent }
|
||||||
|
];
|
||||||
35
src/app/authentication/authentication.service.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/app/authentication/authguard.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/app/authentication/errorInterceptor.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/app/authentication/ip.service.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/app/authentication/loginCredentials.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export class LoginCredentials {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
// ipAddress: string;
|
||||||
|
// recaptcha: string;
|
||||||
|
}
|
||||||
27
src/app/authentication/tokenInterceptor.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/app/authentication/tokenModel.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
54
src/app/authentication/user.service.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
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('');
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/app/authentication/user.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export class User {
|
||||||
|
accessToken: string;
|
||||||
|
username: string;
|
||||||
|
// passwordHash: string;
|
||||||
|
// email: string;
|
||||||
|
// token: string;
|
||||||
|
// refreshToken: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<h2 mat-dialog-title>Kies een baan:</h2>
|
||||||
|
<mat-dialog-content>
|
||||||
|
<button type="button" class="btn {{ data.availableCourts.indexOf(i + 1) < 0 ? 'btn-secondary' : 'btn-primary' }} btn-lg m-3"
|
||||||
|
*ngFor="let item of [].constructor(data.totalCourts); let i = index"
|
||||||
|
[disabled]="data.availableCourts.indexOf(i + 1) < 0" [mat-dialog-close]="i + 1">
|
||||||
|
{{ i + 1 }}
|
||||||
|
</button>
|
||||||
|
<br>
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions>
|
||||||
|
<button mat-button (click)="onAnnulerenClick()">Annuleren</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import {Component, inject, Inject} from '@angular/core';
|
||||||
|
import {
|
||||||
|
MAT_DIALOG_DATA,
|
||||||
|
MatDialogActions,
|
||||||
|
MatDialogClose,
|
||||||
|
MatDialogContent, MatDialogRef,
|
||||||
|
MatDialogTitle
|
||||||
|
} from "@angular/material/dialog";
|
||||||
|
import {Match} from "../../model/match";
|
||||||
|
import {NgForOf} from "@angular/common";
|
||||||
|
import {MatButton} from "@angular/material/button";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-court-selection',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
MatDialogTitle,
|
||||||
|
MatDialogContent,
|
||||||
|
NgForOf,
|
||||||
|
MatButton,
|
||||||
|
MatDialogClose,
|
||||||
|
MatDialogActions
|
||||||
|
],
|
||||||
|
templateUrl: './court-selection.component.html',
|
||||||
|
styleUrl: './court-selection.component.scss'
|
||||||
|
})
|
||||||
|
export class CourtSelectionComponent {
|
||||||
|
|
||||||
|
court: number;
|
||||||
|
|
||||||
|
readonly dialogRef = inject(MatDialogRef<CourtSelectionComponent>);
|
||||||
|
|
||||||
|
constructor(@Inject(MAT_DIALOG_DATA) public data: {
|
||||||
|
match: Match,
|
||||||
|
availableCourts: number[],
|
||||||
|
totalCourts: number
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
onAnnulerenClick() {
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/app/components/login/login.component.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<mat-card>
|
||||||
|
<mat-card-content>
|
||||||
|
<form class="form-horizontal" [formGroup]="form">
|
||||||
|
<div class="row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Gebruikersnaam</mat-label>
|
||||||
|
<input matInput placeholder="Gebruikersnaam" formControlName="username" required>
|
||||||
|
</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>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<button class="w-100 mt-2" mat-button mat-flat-button color="primary"
|
||||||
|
(click)="login()" [disabled]="form.invalid">
|
||||||
|
Inloggen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
0
src/app/components/login/login.component.scss
Normal file
87
src/app/components/login/login.component.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} 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 {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 {TitleService} from "../../service/title.service";
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
templateUrl: './login.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
MatCardTitle,
|
||||||
|
MatCardContent,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatFormField,
|
||||||
|
MatButton,
|
||||||
|
RouterLink,
|
||||||
|
MatInput,
|
||||||
|
NgIf,
|
||||||
|
MatLabel,
|
||||||
|
MatError,
|
||||||
|
MatCard,
|
||||||
|
MatCardHeader
|
||||||
|
],
|
||||||
|
styleUrls: ['./login.component.scss']
|
||||||
|
})
|
||||||
|
export class LoginComponent implements OnInit {
|
||||||
|
public form: FormGroup;
|
||||||
|
private returnUrl: string;
|
||||||
|
private ipAddress: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private authenticationService: AuthenticationService,
|
||||||
|
private router: Router,
|
||||||
|
private userPersistenceService: UserService,
|
||||||
|
private titleService: TitleService,
|
||||||
|
) {
|
||||||
|
this.initializeForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeForm() {
|
||||||
|
this.form = this.fb.group({
|
||||||
|
username: ['', [Validators.required]],
|
||||||
|
password: ['', [Validators.required]],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
|
||||||
|
this.titleService.setTitle("Inloggen");
|
||||||
|
}
|
||||||
|
|
||||||
|
login() {
|
||||||
|
if (this.form.invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let loginCredentials = new LoginCredentials();
|
||||||
|
loginCredentials = {
|
||||||
|
...loginCredentials,
|
||||||
|
...this.form.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.authenticationService
|
||||||
|
.login(loginCredentials)
|
||||||
|
.subscribe(
|
||||||
|
{
|
||||||
|
next: (user: User) => {
|
||||||
|
this.userPersistenceService.setUser(user);
|
||||||
|
this.router.navigate([this.returnUrl]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
82
src/app/components/match-result/match-result.component.html
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<h2 mat-dialog-title>Uitslag invoeren</h2>
|
||||||
|
<mat-dialog-content>
|
||||||
|
<mat-grid-list cols="5" rowHeight="12:7">
|
||||||
|
|
||||||
|
<mat-grid-tile colspan="2">
|
||||||
|
<div class="w-100" style="font-size: 16px;">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<mat-grid-tile colspan="2">
|
||||||
|
<div class="w-100" [ngClass]="{'winner': validateResult() == 1}">
|
||||||
|
{{ data.match.team1 | teamText }}
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<mat-grid-tile colspan="2">
|
||||||
|
<div class="w-100" [ngClass]="{'winner': validateResult() == -1}">
|
||||||
|
{{ data.match.team2 | teamText }}
|
||||||
|
</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>
|
||||||
|
|
||||||
|
</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>
|
||||||
|
</mat-dialog-actions>
|
||||||
12
src/app/components/match-result/match-result.component.scss
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
mat-grid-tile {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-form-field {
|
||||||
|
width: 80px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.winner {
|
||||||
|
color: green;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
147
src/app/components/match-result/match-result.component.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import {Component, inject, Inject} from '@angular/core';
|
||||||
|
import {
|
||||||
|
MAT_DIALOG_DATA,
|
||||||
|
MatDialogActions,
|
||||||
|
MatDialogClose,
|
||||||
|
MatDialogContent, MatDialogRef,
|
||||||
|
MatDialogTitle
|
||||||
|
} from "@angular/material/dialog";
|
||||||
|
import {MatButton, MatIconButton} from "@angular/material/button";
|
||||||
|
import {DatePipe, NgClass, NgForOf} from "@angular/common";
|
||||||
|
import {MatIcon} from "@angular/material/icon";
|
||||||
|
import {TeamPipe} from "../../pipes/team-pipe";
|
||||||
|
import {FullNamePipe} from "../../pipes/fullname-pipe";
|
||||||
|
import {Match} from "../../model/match";
|
||||||
|
import {MatFormField, MatInput} from "@angular/material/input";
|
||||||
|
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||||
|
import {Result} from "../../model/result";
|
||||||
|
import {MatGridList, MatGridTile, MatGridTileText} from "@angular/material/grid-list";
|
||||||
|
import {Round} from "../../model/round";
|
||||||
|
import {Group} from "../../model/group";
|
||||||
|
import {Game} from "../../model/game";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-match-result',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
MatDialogContent,
|
||||||
|
MatDialogActions,
|
||||||
|
MatButton,
|
||||||
|
MatDialogClose,
|
||||||
|
MatDialogTitle,
|
||||||
|
DatePipe,
|
||||||
|
MatIcon,
|
||||||
|
NgForOf,
|
||||||
|
TeamPipe,
|
||||||
|
MatInput,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FormsModule,
|
||||||
|
MatFormField,
|
||||||
|
MatGridList,
|
||||||
|
MatGridTile,
|
||||||
|
MatGridTileText,
|
||||||
|
MatIconButton,
|
||||||
|
NgClass
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
FullNamePipe,
|
||||||
|
TeamPipe
|
||||||
|
],
|
||||||
|
templateUrl: './match-result.component.html',
|
||||||
|
styleUrl: './match-result.component.scss'
|
||||||
|
})
|
||||||
|
export class MatchResultComponent {
|
||||||
|
|
||||||
|
result: Result = new Result();
|
||||||
|
|
||||||
|
constructor(@Inject(MAT_DIALOG_DATA) public data: {match: Match, group: Group, round: Round}) {
|
||||||
|
this.result.matchId = this.data.match.id;
|
||||||
|
|
||||||
|
if (data.match.games.length == 0) {
|
||||||
|
this.result.games.push(new Game());
|
||||||
|
this.result.games.push(new Game());
|
||||||
|
this.result.games.push(new Game());
|
||||||
|
} else {
|
||||||
|
for (let game of data.match.games) {
|
||||||
|
this.result.games.push(game);
|
||||||
|
}
|
||||||
|
if (data.match.games.length == 2) {
|
||||||
|
this.result.games.push(new Game());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly dialogRef = inject(MatDialogRef<MatchResultComponent>);
|
||||||
|
|
||||||
|
set21(team: number, game: number) {
|
||||||
|
if (team == 1) {
|
||||||
|
this.result.games[game - 1].score1 = 21;
|
||||||
|
}
|
||||||
|
if (team == 2) {
|
||||||
|
this.result.games[game - 1].score2 = 21;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onAnnulerenClick() {
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
validateResult(): number {
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (this.result.games[2].score1 != undefined && this.result.games[2].score2 != undefined) {
|
||||||
|
valid &&= this.gameValid(this.result.games[2].score1, this.result.games[2].score2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return valid ? this.matchResult(this.result) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameValid(score1: number, score2: number): boolean {
|
||||||
|
if (score1 == undefined) return false;
|
||||||
|
if (score2 == undefined) return false;
|
||||||
|
|
||||||
|
if (score1 < 0 || score1 > 30) return false;
|
||||||
|
if (score2 < 0 || score2 > 30) return false;
|
||||||
|
|
||||||
|
if (score1 == 21 && score2 <= 19) return true;
|
||||||
|
if (score1 <= 19 && score2 == 21) return true;
|
||||||
|
|
||||||
|
if (score1 == 30 && score2 == 29) return true;
|
||||||
|
if (score1 == 29 && score2 == 30) return true;
|
||||||
|
|
||||||
|
if (score1 >= 22 && score1 <= 30 && (score1 - score2) == 2) return true;
|
||||||
|
if (score2 >= 22 && score2 <= 30 && (score2 - score1) == 2) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(gameBalance) == 2 && (result.games[2].score1 != undefined || result.games[2].score2 != undefined)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.games[2].score1 != undefined && result.games[2].score2 != undefined) {
|
||||||
|
if (result.games[2].score1 < result.games[2].score2) {
|
||||||
|
gameBalance--;
|
||||||
|
} else {
|
||||||
|
gameBalance++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Math.sign(gameBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/app/components/match-sheets/match-sheets.component.html
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
@if (round) {
|
||||||
|
<ng-container *ngFor="let match of round.matches">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<input matInput>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<input matInput>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<input matInput>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6">
|
||||||
|
<b>{{ match.team2 | teamText }}</b>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<input matInput>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<input matInput>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<input matInput>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
</ng-container>
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
mat-form-field {
|
||||||
|
width: 80px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
div.nobreak {
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/app/components/match-sheets/match-sheets.component.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
|
||||||
|
import {TournamentService} from "../../service/tournament.service";
|
||||||
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
|
import {Tournament} from "../../model/tournament";
|
||||||
|
import {Round} from "../../model/round";
|
||||||
|
import {TeamPipe} from "../../pipes/team-pipe";
|
||||||
|
import {FullNamePipe} from "../../pipes/fullname-pipe";
|
||||||
|
import {Group} from "../../model/group";
|
||||||
|
import {NgForOf} from "@angular/common";
|
||||||
|
import {MatFormField} from "@angular/material/form-field";
|
||||||
|
import {MatInput} from "@angular/material/input";
|
||||||
|
import {ReactiveFormsModule} from "@angular/forms";
|
||||||
|
import {TitleService} from "../../service/title.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-match-sheets',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
MatCard,
|
||||||
|
MatCardHeader,
|
||||||
|
MatCardContent,
|
||||||
|
TeamPipe,
|
||||||
|
NgForOf,
|
||||||
|
MatFormField,
|
||||||
|
MatInput,
|
||||||
|
ReactiveFormsModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
TeamPipe,
|
||||||
|
FullNamePipe
|
||||||
|
],
|
||||||
|
templateUrl: './match-sheets.component.html',
|
||||||
|
styleUrl: './match-sheets.component.scss'
|
||||||
|
})
|
||||||
|
export class MatchSheetsComponent implements OnInit {
|
||||||
|
|
||||||
|
tournament: Tournament;
|
||||||
|
group: Group;
|
||||||
|
round: Round;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private tournamentService: TournamentService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private titleService: TitleService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const tournamentId = this.route.snapshot.paramMap.get('id');
|
||||||
|
let roundId = Number(this.route.snapshot.paramMap.get('roundId'));
|
||||||
|
|
||||||
|
this.tournamentService.getById(Number(tournamentId)).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
for (let event of this.tournament.events) {
|
||||||
|
for (let group of event.groups) {
|
||||||
|
for (let round of group.rounds) {
|
||||||
|
if (round.id == roundId) {
|
||||||
|
this.group = group;
|
||||||
|
this.round = round;
|
||||||
|
this.titleService.setTitle(`Wedstrijdbriefjes ${this.group.name} ${this.round.name}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
82
src/app/components/player-edit/player-edit.component.html
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<form (ngSubmit)="savePlayer()">
|
||||||
|
<mat-card appearance="outlined">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<mat-form-field appearance="fill">
|
||||||
|
<mat-label>Achternaam</mat-label>
|
||||||
|
<input matInput [(ngModel)]="player.lastName" name="lastName" required>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<mat-form-field appearance="fill">
|
||||||
|
<mat-label>Tussenvoegsel</mat-label>
|
||||||
|
<input matInput [(ngModel)]="player.middleName" name="middleName">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<mat-form-field appearance="fill">
|
||||||
|
<mat-label>Voornaam</mat-label>
|
||||||
|
<input matInput [(ngModel)]="player.firstName" name="firstName" required>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<mat-radio-group [(ngModel)]="player.sex" name="sex" required>
|
||||||
|
<mat-label>Geslacht</mat-label>
|
||||||
|
<mat-radio-button value="M">M</mat-radio-button>
|
||||||
|
<mat-radio-button value="V">V</mat-radio-button>
|
||||||
|
</mat-radio-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<mat-form-field appearance="fill">
|
||||||
|
<mat-label>Telefoon</mat-label>
|
||||||
|
<input matInput [(ngModel)]="player.phoneNumber" name="phoneNumber" required>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<mat-form-field appearance="fill">
|
||||||
|
<mat-label>Emailadres</mat-label>
|
||||||
|
<input matInput [(ngModel)]="player.email" name="email" required>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<mat-form-field appearance="fill">
|
||||||
|
<mat-label>Geboortedatum</mat-label>
|
||||||
|
<input matInput mask="00-00-0000" [showMaskTyped]="true" [dropSpecialCharacters]="false" [(ngModel)]="player.birthday" type="text" name="birthday" required>
|
||||||
|
<mat-hint>dd-mm-jjjj</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<mat-form-field appearance="fill">
|
||||||
|
<mat-label>Club</mat-label>
|
||||||
|
<input matInput [(ngModel)]="player.club" name="club">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<mat-form-field appearance="fill">
|
||||||
|
<mat-label>Speelsterkte</mat-label>
|
||||||
|
<mat-select [(ngModel)]="player.strength" name="strength" required>
|
||||||
|
<mat-option *ngFor="let strengthOption of Strength | keyvalue" [value]="strengthOption.key">
|
||||||
|
{{ strengthOption.value }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button mat-button>
|
||||||
|
<mat-icon>save</mat-icon>
|
||||||
|
{{ isEditMode ? 'Bijwerken' : 'Opslaan' }}
|
||||||
|
</button>
|
||||||
|
<a mat-button routerLink="/players">
|
||||||
|
<mat-icon>cancel</mat-icon>
|
||||||
|
Annuleren
|
||||||
|
</a>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
a, button, mat-radio-button {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
95
src/app/components/player-edit/player-edit.component.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {Player, Strength} from "../../model/player";
|
||||||
|
import {PlayerService} from "../../service/player.service";
|
||||||
|
import {ActivatedRoute, Router, RouterLink} from "@angular/router";
|
||||||
|
import {FormGroup, FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||||
|
import {MatFormField, MatHint, MatLabel} from "@angular/material/form-field";
|
||||||
|
import {MatInput} from "@angular/material/input";
|
||||||
|
import {MatIcon} from "@angular/material/icon";
|
||||||
|
import {MatRadioButton, MatRadioGroup} from "@angular/material/radio";
|
||||||
|
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from "@angular/material/card";
|
||||||
|
import {MatDatepicker, MatDatepickerInput, MatDatepickerToggle} from "@angular/material/datepicker";
|
||||||
|
import {MatOption, MatSelect} from "@angular/material/select";
|
||||||
|
import {KeyValuePipe, NgForOf} from "@angular/common";
|
||||||
|
import {MatAnchor, MatButton} from "@angular/material/button";
|
||||||
|
import {TitleService} from "../../service/title.service";
|
||||||
|
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||||
|
import {NgxMaskDirective, NgxMaskPipe} from "ngx-mask";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-player-edit',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
RouterLink,
|
||||||
|
MatFormField,
|
||||||
|
MatInput,
|
||||||
|
MatIcon,
|
||||||
|
MatHint,
|
||||||
|
MatLabel,
|
||||||
|
MatRadioGroup,
|
||||||
|
MatRadioButton,
|
||||||
|
MatCard,
|
||||||
|
MatCardHeader,
|
||||||
|
MatCardContent,
|
||||||
|
MatCardActions,
|
||||||
|
MatDatepickerInput,
|
||||||
|
MatDatepickerToggle,
|
||||||
|
MatDatepicker,
|
||||||
|
MatSelect,
|
||||||
|
MatOption,
|
||||||
|
KeyValuePipe,
|
||||||
|
NgForOf,
|
||||||
|
MatButton,
|
||||||
|
MatAnchor,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
NgxMaskDirective,
|
||||||
|
NgxMaskPipe
|
||||||
|
],
|
||||||
|
templateUrl: './player-edit.component.html',
|
||||||
|
styleUrl: './player-edit.component.scss'
|
||||||
|
})
|
||||||
|
export class PlayerEditComponent implements OnInit {
|
||||||
|
player: Player;
|
||||||
|
isEditMode: boolean = false;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private playerService: PlayerService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private titleService: TitleService,
|
||||||
|
private _snackBar: MatSnackBar,
|
||||||
|
) {
|
||||||
|
this.player = new Player();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const id = this.route.snapshot.paramMap.get('id');
|
||||||
|
if (id) {
|
||||||
|
this.titleService.setTitle("Bewerk Speler");
|
||||||
|
this.isEditMode = true;
|
||||||
|
this.playerService.getById(Number(id)).subscribe(data => {
|
||||||
|
this.player = data;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.titleService.setTitle("Nieuwe Speler");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
savePlayer() {
|
||||||
|
if (this.isEditMode) {
|
||||||
|
this.playerService.update(this.player.id, this.player).subscribe(() => {
|
||||||
|
this.router.navigate(['/players']);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.playerService.save(this.player).subscribe({
|
||||||
|
next: () => this.router.navigate(['/players']),
|
||||||
|
error: () => this._snackBar.open('Niet alle velden zijn correct gevuld.')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly Object = Object;
|
||||||
|
protected readonly Strength = Strength;
|
||||||
|
}
|
||||||
42
src/app/components/player-list/player-list.component.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<mat-card appearance="outlined">
|
||||||
|
<!--
|
||||||
|
<mat-card-header>
|
||||||
|
<h5>Spelers</h5>
|
||||||
|
</mat-card-header>
|
||||||
|
-->
|
||||||
|
<mat-card-content>
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col">Naam</th>
|
||||||
|
<th scope="col">M/V</th>
|
||||||
|
<th scope="col">Club</th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let player of players">
|
||||||
|
<td class="align-middle">{{ player.id }}</td>
|
||||||
|
<td class="align-middle">{{ player | fullName }}</td>
|
||||||
|
<td class="align-middle">{{ player.sex }}</td>
|
||||||
|
<td class="align-middle">{{ player.club }}</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
<a mat-button [routerLink]="['/players/edit', player.id]">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
Bewerk
|
||||||
|
</a>
|
||||||
|
<a mat-button [routerLink]="['/players', player.id, 'registrations']">
|
||||||
|
<mat-icon>app_registration</mat-icon>
|
||||||
|
Inschrijvingen
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<a mat-button routerLink="/players/add">
|
||||||
|
<mat-icon>person_add</mat-icon>
|
||||||
|
Nieuwe speler
|
||||||
|
</a>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
a {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
36
src/app/components/player-list/player-list.component.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {Player} from "../../model/player";
|
||||||
|
import {PlayerService} from "../../service/player.service";
|
||||||
|
import {NgFor} from "@angular/common";
|
||||||
|
import {RouterLink} from "@angular/router";
|
||||||
|
import {MatAnchor} from "@angular/material/button";
|
||||||
|
import {MatIcon} from "@angular/material/icon";
|
||||||
|
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
|
||||||
|
import {FullNamePipe} from "../../pipes/fullname-pipe";
|
||||||
|
import {TitleService} from "../../service/title.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-player-list',
|
||||||
|
standalone: true,
|
||||||
|
imports: [NgFor, RouterLink, MatAnchor, MatIcon, MatCard, MatCardHeader, MatCardContent, FullNamePipe],
|
||||||
|
templateUrl: './player-list.component.html',
|
||||||
|
styleUrl: './player-list.component.scss'
|
||||||
|
})
|
||||||
|
export class PlayerListComponent implements OnInit {
|
||||||
|
|
||||||
|
players: Player[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private titleService: TitleService,
|
||||||
|
private playerService: PlayerService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.titleService.setTitle("Spelers");
|
||||||
|
this.playerService.getAll().subscribe(data => {
|
||||||
|
this.players = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly Player = Player;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
@if (player && tournamentRegistrations && allPlayers) {
|
||||||
|
<mat-card appearance="outlined">
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-card *ngFor="let tournamentRegistration of getTournamentRegistrations()" appearance="outlined" class="mb-3">
|
||||||
|
<mat-card-header>
|
||||||
|
<h6>{{ tournamentRegistration.name }}</h6>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<ng-container *ngFor="let eventRegistration of tournamentRegistration.events">
|
||||||
|
<div class="row event-row">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<mat-checkbox [disabled]="!tournamentRegistration.editable" [(ngModel)]="eventRegistration.registered" (change)="updateModelWhenEventChecked(eventRegistration, $event)" name="registered">
|
||||||
|
{{ EventRegistration.getType(eventRegistration.type) }}
|
||||||
|
</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<ng-container *ngIf="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>
|
||||||
|
<mat-option *ngFor="let player of getRelevantPlayers(eventRegistration.type)" [value]="player.id">
|
||||||
|
{{ player | fullName }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<div class="col-6"></div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions *ngIf="tournamentRegistration.editable">
|
||||||
|
<button mat-button (click)="saveRegistration(tournamentRegistration, $event)" [disabled]="waitingForBackend">
|
||||||
|
<mat-icon>save</mat-icon>
|
||||||
|
Opslaan
|
||||||
|
</button>
|
||||||
|
<a mat-button routerLink="/players">
|
||||||
|
<mat-icon>cancel</mat-icon>
|
||||||
|
Annuleren
|
||||||
|
</a>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
<button mat-button (click)="this.showAll = true" *ngIf="!this.showAll">
|
||||||
|
<mat-icon>search</mat-icon>
|
||||||
|
Toon oude toernooien
|
||||||
|
</button>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.event-row {
|
||||||
|
height: 5em;
|
||||||
|
}
|
||||||
|
mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
a, button {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {Player} from "../../model/player";
|
||||||
|
import {ActivatedRoute, Router, RouterLink} from "@angular/router";
|
||||||
|
import {PlayerService} from "../../service/player.service";
|
||||||
|
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from "@angular/material/card";
|
||||||
|
import {MatFormField, MatLabel} from "@angular/material/form-field";
|
||||||
|
import {MatInput} from "@angular/material/input";
|
||||||
|
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||||
|
import {RegistrationService} from "../../service/registration.service";
|
||||||
|
import {KeyValuePipe, NgFor, NgIf} from "@angular/common";
|
||||||
|
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, MatFabButton} from "@angular/material/button";
|
||||||
|
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||||
|
import {FullNamePipe} from "../../pipes/fullname-pipe";
|
||||||
|
import {Tournament} from "../../model/tournament";
|
||||||
|
import {TitleService} from "../../service/title.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-player-registrations',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
MatCard,
|
||||||
|
MatCardContent,
|
||||||
|
MatCardHeader,
|
||||||
|
MatFormField,
|
||||||
|
MatInput,
|
||||||
|
MatLabel,
|
||||||
|
NgFor,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FormsModule,
|
||||||
|
MatCheckbox,
|
||||||
|
NgIf,
|
||||||
|
MatCardActions,
|
||||||
|
RouterLink,
|
||||||
|
KeyValuePipe,
|
||||||
|
MatOption,
|
||||||
|
MatSelect,
|
||||||
|
MatIcon,
|
||||||
|
MatFabButton,
|
||||||
|
MatButton,
|
||||||
|
MatAnchor,
|
||||||
|
FullNamePipe
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
FullNamePipe
|
||||||
|
],
|
||||||
|
templateUrl: './player-registrations.component.html',
|
||||||
|
styleUrl: './player-registrations.component.scss'
|
||||||
|
})
|
||||||
|
export class PlayerRegistrationsComponent implements OnInit {
|
||||||
|
|
||||||
|
player: Player;
|
||||||
|
tournamentRegistrations: TournamentRegistration[];
|
||||||
|
allPlayers: Player[];
|
||||||
|
showAll: boolean = false;
|
||||||
|
|
||||||
|
waitingForBackend: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private _snackBar: MatSnackBar,
|
||||||
|
private playerService: PlayerService,
|
||||||
|
private registrationService: RegistrationService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private titleService: TitleService,
|
||||||
|
private fullNamePipe: FullNamePipe
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const id = this.route.snapshot.paramMap.get('id');
|
||||||
|
this.playerService.getById(Number(id)).subscribe(data => {
|
||||||
|
this.player = data;
|
||||||
|
this.titleService.setTitle(`Inschrijvingen van ${this.fullNamePipe.transform(this.player)}`);
|
||||||
|
});
|
||||||
|
this.registrationService.getTournamentRegistrationsByPlayerId(Number(id)).subscribe(data => {
|
||||||
|
this.tournamentRegistrations = data;
|
||||||
|
})
|
||||||
|
this.playerService.getAll().subscribe(data => {
|
||||||
|
this.allPlayers = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
saveRegistration(tournamentRegistration: TournamentRegistration, event: MouseEvent) {
|
||||||
|
this.waitingForBackend = true;
|
||||||
|
this.registrationService.saveTournamentRegistrations(tournamentRegistration, this.player.id).subscribe(data => {
|
||||||
|
this.waitingForBackend = false;
|
||||||
|
this._snackBar.open('Registratie opgeslagen.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateModelWhenEventChecked(eventRegistration: EventRegistration, event: MatCheckboxChange) {
|
||||||
|
if (event.checked) {
|
||||||
|
eventRegistration.player = this.player.id;
|
||||||
|
} else {
|
||||||
|
eventRegistration.player = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRelevantPlayers(type: string): Player[] {
|
||||||
|
if ( (this.player.sex == 'M' && type == 'HD') ||
|
||||||
|
(this.player.sex == 'V' && type == 'GD')
|
||||||
|
) {
|
||||||
|
return this.allPlayers.filter(player => player.sex == 'M' && player.id != this.player.id);
|
||||||
|
} else {
|
||||||
|
return this.allPlayers.filter(player => player.sex == 'V' && player.id != this.player.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getTournamentRegistrations(): TournamentRegistration[] {
|
||||||
|
if (this.showAll) {
|
||||||
|
return this.tournamentRegistrations;
|
||||||
|
} else {
|
||||||
|
return this.tournamentRegistrations.filter(t => (t.status == 'UPCOMING' || t.status == 'ONGOING'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly Player = Player;
|
||||||
|
protected readonly EventRegistration = EventRegistration;
|
||||||
|
protected readonly TournamentRegistration = TournamentRegistration;
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
@if (round) {
|
||||||
|
<h6>{{ group.name }}, {{ round.name }}:</h6>
|
||||||
|
@if (round.status != 'FINISHED') {
|
||||||
|
<table class="table table-sm m-4 wide w-100">
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let match of round.matches">
|
||||||
|
<td class="align-middle" style="width: 45%;">{{ match.team1 | teamText }}</td>
|
||||||
|
<td class="align-middle w-sep">-</td>
|
||||||
|
<td class="align-middle" style="width: 45%;">{{ match.team2 | teamText }}</td>
|
||||||
|
<td class="align-middle w-sep"></td>
|
||||||
|
</tr>
|
||||||
|
@if (round.drawnOut) {
|
||||||
|
<tr>
|
||||||
|
<td class="align-middle w-100" colspan="4"><b>Deze ronde uitgeloot:</b> {{ round.drawnOut | teamText }}</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
} @else {
|
||||||
|
<table class="table table-sm m-4 wide w-100">
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let match of round.matches">
|
||||||
|
<td class="align-middle" style="width: 30%;">
|
||||||
|
@if (event.doublesEvent) {
|
||||||
|
{{ match.team1.player1 | fullName }} /<br>{{ match.team1.player2 | fullName }}
|
||||||
|
} @else {
|
||||||
|
{{ match.team1.player1 | fullName }}
|
||||||
|
}
|
||||||
|
<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 }}
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle" style="width: 35%;">
|
||||||
|
<div class="row result align-items-center">
|
||||||
|
<span *ngFor="let game of match.games" class="col-3">{{ game.score1 }}-{{ game.score2 }}</span>
|
||||||
|
@if (match.games.length == 2) {
|
||||||
|
<span class="col-3"></span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@if (round.drawnOut) {
|
||||||
|
<tr>
|
||||||
|
<td class="align-middle w-100" colspan="4"><b>Deze ronde uitgeloot:</b> {{ round.drawnOut | teamText }}</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (round.status == 'FINISHED' || prevRound) {
|
||||||
|
@if (group.status == 'FINISHED') {
|
||||||
|
<h6>Eindstand:</h6>
|
||||||
|
} @else if (round.status == 'FINISHED') {
|
||||||
|
<h6>Stand na {{ round.name }}:</h6>
|
||||||
|
} @else {
|
||||||
|
<h6>Stand na {{ prevRound?.name }}:</h6>
|
||||||
|
}
|
||||||
|
<table class="table table-sm w-100 m-4">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>
|
||||||
|
@if (event.doublesEvent) {
|
||||||
|
Team
|
||||||
|
} @else {
|
||||||
|
Speler
|
||||||
|
}
|
||||||
|
</th>
|
||||||
|
<th>Gespeeld</th>
|
||||||
|
<th>Punten/W</th>
|
||||||
|
<th>Games/W</th>
|
||||||
|
<th>#/W</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="table-group-divider">
|
||||||
|
<tr *ngFor="let entry of round.standings.entries">
|
||||||
|
<td class="align-middle">{{ entry.position }}</td>
|
||||||
|
<td class="align-middle">{{ entry.team | teamText }}</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>
|
||||||
|
<td class="align-middle">{{ (entry.pointsWon - entry.pointsLost) / entry.played | number: '1.0-2' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
td.w-team {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.w-sep {
|
||||||
|
width: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.w-fill {
|
||||||
|
width: 35%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {Tournament} from "../../model/tournament";
|
||||||
|
import {Event} from "../../model/event";
|
||||||
|
import {Group} from "../../model/group";
|
||||||
|
import {Round} from "../../model/round";
|
||||||
|
import {TournamentService} from "../../service/tournament.service";
|
||||||
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
|
import {DecimalPipe, NgForOf} from "@angular/common";
|
||||||
|
import {TeamPipe} from "../../pipes/team-pipe";
|
||||||
|
import {FullNamePipe} from "../../pipes/fullname-pipe";
|
||||||
|
import {MatButton, MatIconButton} from "@angular/material/button";
|
||||||
|
import {MatIcon} from "@angular/material/icon";
|
||||||
|
import {MatMenu, MatMenuItem} from "@angular/material/menu";
|
||||||
|
import {TitleService} from "../../service/title.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-round-overview',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
NgForOf,
|
||||||
|
TeamPipe,
|
||||||
|
DecimalPipe,
|
||||||
|
MatButton,
|
||||||
|
MatIcon,
|
||||||
|
MatIconButton,
|
||||||
|
MatMenu,
|
||||||
|
MatMenuItem,
|
||||||
|
FullNamePipe
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
TeamPipe,
|
||||||
|
FullNamePipe
|
||||||
|
],
|
||||||
|
templateUrl: './round-overview.component.html',
|
||||||
|
styleUrl: './round-overview.component.scss'
|
||||||
|
})
|
||||||
|
export class RoundOverviewComponent implements OnInit {
|
||||||
|
|
||||||
|
tournament: Tournament;
|
||||||
|
event: Event;
|
||||||
|
group: Group;
|
||||||
|
round: Round;
|
||||||
|
prevRound: Round | undefined;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private tournamentService: TournamentService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private titleService: TitleService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.titleService.setTitle("Rondeoverzicht");
|
||||||
|
const tournamentId = this.route.snapshot.paramMap.get('id');
|
||||||
|
let roundId = Number(this.route.snapshot.paramMap.get('roundId'));
|
||||||
|
|
||||||
|
this.tournamentService.getById(Number(tournamentId)).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
for (let event of this.tournament.events) {
|
||||||
|
for (let group of event.groups) {
|
||||||
|
let roundIndex = 0;
|
||||||
|
this.prevRound = undefined;
|
||||||
|
for (let round of group.rounds) {
|
||||||
|
roundIndex++;
|
||||||
|
if (round.id == roundId) {
|
||||||
|
this.event = event;
|
||||||
|
this.group = group;
|
||||||
|
this.round = round;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.prevRound = round;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<mat-card appearance="outlined" *ngIf="tournament">
|
||||||
|
<mat-card-header>
|
||||||
|
<h5>Indeling voor {{ tournament.name }}</h5>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-card *ngFor="let event of tournament.events" appearance="outlined" class="m-3">
|
||||||
|
<mat-card-header>
|
||||||
|
<h6>Indeling {{ TournamentEvent.getType(event.type) }}</h6>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-accordion multi="true">
|
||||||
|
<mat-expansion-panel *ngFor="let group of event.groups">
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{ group.name }} <span class="badge text-bg-success">{{ group.teams.length }}</span>
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<table class="table {{ event.doublesEvent ? 'w-100' : 'w-50' }}">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="w-20">Naam</th>
|
||||||
|
<th scope="col" class="w-20">Club</th>
|
||||||
|
<th scope="col" class="w-10">Speelsterkte</th>
|
||||||
|
<th *ngIf="event.doublesEvent" scope="col" class="w-20">Partner</th>
|
||||||
|
<th *ngIf="event.doublesEvent" scope="col" class="w-20">Club</th>
|
||||||
|
<th *ngIf="event.doublesEvent" scope="col" class="w-10">Speelsterkte</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let team of group.teams">
|
||||||
|
<td class="align-middle">{{ team.player1 | fullName }}</td>
|
||||||
|
<td class="align-middle">{{ team.player1.club }}</td>
|
||||||
|
<td class="align-middle">{{ getStrength(team.player1.strength.valueOf()) }}</td>
|
||||||
|
<td *ngIf="event.doublesEvent" class="align-middle">{{ team.player2 | fullName }}</td>
|
||||||
|
<td *ngIf="event.doublesEvent" class="align-middle">{{ team.player2.club }}</td>
|
||||||
|
<td *ngIf="event.doublesEvent" class="align-middle">{{ getStrength(team.player2.strength.valueOf()) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
td, th {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {Tournament} from "../../model/tournament";
|
||||||
|
import {TournamentService} from "../../service/tournament.service";
|
||||||
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
|
import {TournamentDivision} from "../../model/tournamentDivision";
|
||||||
|
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
|
||||||
|
import {KeyValuePipe, NgForOf, NgIf} from "@angular/common";
|
||||||
|
import {
|
||||||
|
MatAccordion,
|
||||||
|
MatExpansionPanel,
|
||||||
|
MatExpansionPanelHeader,
|
||||||
|
MatExpansionPanelTitle
|
||||||
|
} from "@angular/material/expansion";
|
||||||
|
import {Event} from "../../model/event";
|
||||||
|
import {Player, Strength} from "../../model/player";
|
||||||
|
import {EventDivision} from "../../model/eventDivision";
|
||||||
|
import {FullNamePipe} from "../../pipes/fullname-pipe";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-tournament-divide',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
MatCard,
|
||||||
|
MatCardHeader,
|
||||||
|
NgIf,
|
||||||
|
MatCardContent,
|
||||||
|
MatExpansionPanel,
|
||||||
|
MatExpansionPanelTitle,
|
||||||
|
MatExpansionPanelHeader,
|
||||||
|
NgForOf,
|
||||||
|
KeyValuePipe,
|
||||||
|
FullNamePipe,
|
||||||
|
MatAccordion
|
||||||
|
],
|
||||||
|
templateUrl: './tournament-divide.component.html',
|
||||||
|
styleUrl: './tournament-divide.component.scss'
|
||||||
|
})
|
||||||
|
export class TournamentDivideComponent implements OnInit {
|
||||||
|
tournament?: Tournament;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private tournamentService: TournamentService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const id = this.route.snapshot.paramMap.get('id');
|
||||||
|
this.tournamentService.getById(Number(id)).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
});
|
||||||
|
// this.tournamentService.getDivision(Number(id)).subscribe(data => {
|
||||||
|
// this.tournamentDivision = data;
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly TournamentEvent = Event;
|
||||||
|
|
||||||
|
getStrength(strength: string) {
|
||||||
|
for (let [key, value] of Object.entries(Strength)) {
|
||||||
|
if (key == strength) return value;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<mat-card appearance="outlined" *ngIf="tournament">
|
||||||
|
<mat-card-header>
|
||||||
|
<h5>Loting voor {{ tournament.name }}</h5>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-card *ngFor="let event of tournament.events" appearance="outlined" class="m-3">
|
||||||
|
<mat-card-header>
|
||||||
|
<h6>Loting {{ TournamentEvent.getType(event.type) }}</h6>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-accordion multi="true">
|
||||||
|
<mat-expansion-panel *ngFor="let group of this.event.groups">
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{ group.name }} <span class="badge text-bg-success">{{ group.teams.length }}</span>
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<table class="table {{ event.doublesEvent ? 'w-100' : 'w-50' }}">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="w-25">Team 1</th>
|
||||||
|
<th scope="col" style="width: 5%">-</th>
|
||||||
|
<th scope="col" class="w-25">Team 2</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let match of group.rounds[0].matches">
|
||||||
|
<td class="align-middle">{{ match.team1 | teamText }}</td>
|
||||||
|
<td class="align-middle">-</td>
|
||||||
|
<td class="align-middle">{{ match.team2 | teamText }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
td, th {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {Tournament} from "../../model/tournament";
|
||||||
|
import {TournamentService} from "../../service/tournament.service";
|
||||||
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
|
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
|
||||||
|
import {NgForOf, NgIf} from "@angular/common";
|
||||||
|
import {
|
||||||
|
MatAccordion,
|
||||||
|
MatExpansionPanel,
|
||||||
|
MatExpansionPanelHeader,
|
||||||
|
MatExpansionPanelTitle
|
||||||
|
} from "@angular/material/expansion";
|
||||||
|
import {Event} from "../../model/event";
|
||||||
|
import {TeamPipe} from "../../pipes/team-pipe";
|
||||||
|
import {FullNamePipe} from "../../pipes/fullname-pipe";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-tournament-draw',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
FullNamePipe,
|
||||||
|
MatCard,
|
||||||
|
NgIf,
|
||||||
|
MatCardContent,
|
||||||
|
MatCardHeader,
|
||||||
|
MatExpansionPanel,
|
||||||
|
MatExpansionPanelHeader,
|
||||||
|
MatExpansionPanelTitle,
|
||||||
|
NgForOf,
|
||||||
|
TeamPipe,
|
||||||
|
MatAccordion
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
FullNamePipe,
|
||||||
|
TeamPipe
|
||||||
|
],
|
||||||
|
templateUrl: './tournament-draw.component.html',
|
||||||
|
styleUrl: './tournament-draw.component.scss'
|
||||||
|
})
|
||||||
|
export class TournamentDrawComponent implements OnInit {
|
||||||
|
|
||||||
|
tournament: Tournament;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private tournamentService: TournamentService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const id = this.route.snapshot.paramMap.get('id');
|
||||||
|
this.tournamentService.draw(Number(id)).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly TournamentEvent = Event;
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
@if (tournament) {
|
||||||
|
<form (ngSubmit)="saveTournament()">
|
||||||
|
<mat-card appearance="outlined">
|
||||||
|
<!--
|
||||||
|
<mat-card-header>
|
||||||
|
<h5>{{ isEditMode ? 'Bewerk toernooi' : 'Toevoegen toernooi' }}</h5>
|
||||||
|
</mat-card-header>
|
||||||
|
-->
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<mat-form-field appearance="fill">
|
||||||
|
<mat-label>Naam</mat-label>
|
||||||
|
<input matInput [(ngModel)]="tournament.name" name="name" required>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<mat-form-field appearance="fill">
|
||||||
|
<mat-label>Datum</mat-label>
|
||||||
|
<input matInput mask="00-00-0000" [showMaskTyped]="true" [dropSpecialCharacters]="false" [(ngModel)]="tournament.date" name="date" required>
|
||||||
|
<mat-hint>dd-mm-jjjj</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label id="max-events-label">Max. aantal onderdelen per inschrijving</label>
|
||||||
|
<mat-radio-group [(ngModel)]="tournament.maxEvents" aria-labelledby="max-events-label" name="maxEvents" required>
|
||||||
|
<mat-radio-button [value]="2">2</mat-radio-button>
|
||||||
|
<mat-radio-button [value]="3">3</mat-radio-button>
|
||||||
|
</mat-radio-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<mat-form-field appearance="fill">
|
||||||
|
<mat-label>Kosten 1 onderdeel</mat-label>
|
||||||
|
<input matInput name="costsPerEvent1" type="number" min="0" step="0.5" [(ngModel)]="tournament.costsPerEvent[0]" required>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<mat-form-field appearance="fill">
|
||||||
|
<mat-label>Kosten 2 onderdelen</mat-label>
|
||||||
|
<input matInput name="costsPerEvent2" type="number" min="0" step="0.5" [(ngModel)]="tournament.costsPerEvent[1]" required>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (tournament.maxEvents == 3) {
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<mat-form-field appearance="fill">
|
||||||
|
<mat-label>Kosten 3 onderdelen</mat-label>
|
||||||
|
<input matInput name="costsPerEvent3" type="number" min="0" step="0.5" [(ngModel)]="tournament.costsPerEvent[2]" required>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<mat-form-field appearance="fill">
|
||||||
|
<mat-label>Aantal banen</mat-label>
|
||||||
|
<input matInput name="courts" type="number" min="1" [(ngModel)]="tournament.courts" required>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button mat-button>
|
||||||
|
<mat-icon>save</mat-icon>
|
||||||
|
{{ isEditMode ? 'Bijwerken' : 'Opslaan' }}
|
||||||
|
</button>
|
||||||
|
<a mat-button routerLink="/tournaments">
|
||||||
|
<mat-icon>cancel</mat-icon>
|
||||||
|
Annuleren
|
||||||
|
</a>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
a, button {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {FormsModule} from "@angular/forms";
|
||||||
|
import {ActivatedRoute, Router, RouterLink} from "@angular/router";
|
||||||
|
import {Tournament} from "../../model/tournament";
|
||||||
|
import {TournamentService} from "../../service/tournament.service";
|
||||||
|
import {MatAnchor, MatButton} from "@angular/material/button";
|
||||||
|
import {MatIcon} from "@angular/material/icon";
|
||||||
|
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from "@angular/material/card";
|
||||||
|
import {MatFormField, MatHint, MatLabel} from "@angular/material/form-field";
|
||||||
|
import {MatInput} from "@angular/material/input";
|
||||||
|
import {MatRadioButton, MatRadioGroup} from "@angular/material/radio";
|
||||||
|
import {CurrencyPipe, NgForOf, registerLocaleData} from "@angular/common";
|
||||||
|
import nl from "@angular/common/locales/nl";
|
||||||
|
import {TitleService} from "../../service/title.service";
|
||||||
|
import {NgxMaskDirective, NgxMaskPipe} from "ngx-mask";
|
||||||
|
|
||||||
|
registerLocaleData(nl);
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-tournament-edit',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
RouterLink,
|
||||||
|
MatAnchor,
|
||||||
|
MatButton,
|
||||||
|
MatIcon,
|
||||||
|
MatCard,
|
||||||
|
MatCardHeader,
|
||||||
|
MatCardContent,
|
||||||
|
MatFormField,
|
||||||
|
MatInput,
|
||||||
|
MatLabel,
|
||||||
|
MatCardActions,
|
||||||
|
MatRadioButton,
|
||||||
|
MatRadioGroup,
|
||||||
|
NgForOf,
|
||||||
|
CurrencyPipe,
|
||||||
|
MatHint,
|
||||||
|
NgxMaskDirective,
|
||||||
|
NgxMaskPipe
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
CurrencyPipe
|
||||||
|
],
|
||||||
|
templateUrl: './tournament-edit.component.html',
|
||||||
|
styleUrl: './tournament-edit.component.scss'
|
||||||
|
})
|
||||||
|
export class TournamentEditComponent implements OnInit {
|
||||||
|
|
||||||
|
tournament: Tournament;
|
||||||
|
isEditMode: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private tournamentService: TournamentService,
|
||||||
|
private currencyPipe: CurrencyPipe,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private titleService: TitleService
|
||||||
|
) {
|
||||||
|
this.tournament = new Tournament();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const id = this.route.snapshot.paramMap.get('id');
|
||||||
|
if (id) {
|
||||||
|
this.titleService.setTitle("Bewerk Toernooi");
|
||||||
|
this.isEditMode = true;
|
||||||
|
this.tournamentService.getById(Number(id)).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.titleService.setTitle("Nieuw Toernooi");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveTournament() {
|
||||||
|
if (this.isEditMode) {
|
||||||
|
this.tournamentService.update(this.tournament.id, this.tournament).subscribe(() => {
|
||||||
|
this.router.navigate(['/tournaments']);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.tournamentService.save(this.tournament).subscribe(() => {
|
||||||
|
this.router.navigate(['/tournaments']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly Array = Array;
|
||||||
|
|
||||||
|
setCostsPerEvent(number: number, $event: any) {
|
||||||
|
let value : string = String($event);
|
||||||
|
value = value.replace( /^\D+/g, ''); // replace all leading non-digits with nothing
|
||||||
|
this.tournament.costsPerEvent[number] = Number(value);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<mat-card appearance="outlined">
|
||||||
|
<!--
|
||||||
|
<mat-card-header>
|
||||||
|
<h5>Toernooien</h5>
|
||||||
|
</mat-card-header>
|
||||||
|
-->
|
||||||
|
<mat-card-content>
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col">Naam</th>
|
||||||
|
<th scope="col">Datum</th>
|
||||||
|
<th scope="col">Status</th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let tournament of tournaments">
|
||||||
|
<td class="align-middle">{{ tournament.id }}</td>
|
||||||
|
<td class="align-middle"><a [routerLink]="['/tournaments', tournament.id, 'manage']">{{ tournament.name }}</a></td>
|
||||||
|
<td class="align-middle">{{ tournament.date }}</td>
|
||||||
|
<td class="align-middle">{{ Tournament.getStatus(tournament) }}</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
<a mat-button [routerLink]="['/tournaments', tournament.id, 'edit']" *ngIf="Tournament.getStatus(tournament) != 'Afgerond'">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
Bewerk
|
||||||
|
</a>
|
||||||
|
<a mat-button [routerLink]="['/tournaments', tournament.id, 'registrations']">
|
||||||
|
<mat-icon>group</mat-icon>
|
||||||
|
Inschrijvingen
|
||||||
|
</a>
|
||||||
|
<a mat-button (click)="clearDraw(tournament)" *ngIf="Tournament.getStatus(tournament) == 'Geloot'">
|
||||||
|
<mat-icon>safety_divider</mat-icon>
|
||||||
|
Loting wissen
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<a mat-button routerLink="/tournaments/add">
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
Nieuw toernooi
|
||||||
|
</a>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
a {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import {AfterContentChecked, AfterContentInit, Component, EventEmitter, OnInit, Output} from '@angular/core';
|
||||||
|
import {NgFor, NgIf} from "@angular/common";
|
||||||
|
import {RouterLink} from "@angular/router";
|
||||||
|
import {Tournament} from "../../model/tournament";
|
||||||
|
import {TournamentService} from "../../service/tournament.service";
|
||||||
|
import {MatAnchor, MatButton, MatIconButton} from "@angular/material/button";
|
||||||
|
import {MatIcon} from "@angular/material/icon";
|
||||||
|
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
|
||||||
|
import {
|
||||||
|
MatCell,
|
||||||
|
MatCellDef,
|
||||||
|
MatColumnDef,
|
||||||
|
MatHeaderCell,
|
||||||
|
MatHeaderCellDef,
|
||||||
|
MatHeaderRow, MatRow,
|
||||||
|
MatTable, MatTableModule
|
||||||
|
} from "@angular/material/table";
|
||||||
|
import {MatMenuTrigger} from "@angular/material/menu";
|
||||||
|
import {TitleService} from "../../service/title.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-tournament-list',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
NgFor, RouterLink, NgIf, MatAnchor, MatIcon, MatCard, MatCardHeader, MatCardContent, MatButton, MatTable, MatColumnDef, MatHeaderCell, MatHeaderCellDef, MatCell, MatCellDef, MatHeaderRow, MatRow, MatTableModule, MatIconButton, MatMenuTrigger
|
||||||
|
],
|
||||||
|
templateUrl: './tournament-list.component.html',
|
||||||
|
styleUrl: './tournament-list.component.scss'
|
||||||
|
})
|
||||||
|
export class TournamentListComponent implements OnInit, AfterContentChecked {
|
||||||
|
|
||||||
|
tournaments: Tournament[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private tournamentService: TournamentService,
|
||||||
|
private titleService: TitleService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.tournamentService.getAll().subscribe(data => {
|
||||||
|
this.tournaments = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterContentChecked() {
|
||||||
|
this.titleService.setTitle("Toernooien");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly Tournament = Tournament;
|
||||||
|
|
||||||
|
clearDraw(tournament: Tournament) {
|
||||||
|
this.tournamentService.clearDraw(tournament.id).subscribe(data => {
|
||||||
|
this.tournamentService.getAll().subscribe(data => {
|
||||||
|
this.tournaments = data;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,429 @@
|
|||||||
|
@if (tournament) {
|
||||||
|
<mat-card appearance="outlined">
|
||||||
|
<!--
|
||||||
|
<mat-card-header>
|
||||||
|
<h5>{{ tournament.name }}</h5>
|
||||||
|
</mat-card-header>
|
||||||
|
-->
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-tab-group animationDuration="0ms" disableRipple="true">
|
||||||
|
|
||||||
|
@if (tournament.status == 'UPCOMING') {
|
||||||
|
<mat-tab>
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<mat-icon>playlist_add_check</mat-icon>
|
||||||
|
Validaties
|
||||||
|
<button mat-icon-button [matMenuTriggerFor]="upcomingTournamentMenu" class="menu-button m-3">
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #upcomingTournamentMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="divideTournament()">
|
||||||
|
<mat-icon>play_arrow</mat-icon>
|
||||||
|
Deel toernooi in
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
</ng-template>
|
||||||
|
<app-tournament-validate></app-tournament-validate>
|
||||||
|
</mat-tab>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (tournament.status == 'DIVIDED') {
|
||||||
|
<mat-tab>
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<mat-icon>safety_divider</mat-icon>
|
||||||
|
Indeling
|
||||||
|
<button mat-icon-button [matMenuTriggerFor]="dividedTournamentMenu" class="menu-button m-3">
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #dividedTournamentMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="clearDivision()">
|
||||||
|
<mat-icon>highlight_remove</mat-icon>
|
||||||
|
Indeling wissen
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="drawTournament()">
|
||||||
|
<mat-icon>safety_divider</mat-icon>
|
||||||
|
1e ronde loten
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
</ng-template>
|
||||||
|
<mat-card *ngFor="let event of tournament.events" appearance="outlined" class="m-3">
|
||||||
|
<mat-card-header>
|
||||||
|
<h6>Indeling {{ TournamentEvent.getType(event.type) }}</h6>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-accordion multi="true">
|
||||||
|
<mat-expansion-panel *ngFor="let group of event.groups">
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
{{ group.name }} <span class="badge text-bg-success">{{ group.teams.length }}</span>
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<table class="table {{ event.doublesEvent ? 'w-100' : 'w-50' }}">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="w-20">Naam</th>
|
||||||
|
<th scope="col" class="w-20">Club</th>
|
||||||
|
<th scope="col" class="w-10">Speelsterkte</th>
|
||||||
|
<th *ngIf="event.doublesEvent" scope="col" class="w-20">Partner</th>
|
||||||
|
<th *ngIf="event.doublesEvent" scope="col" class="w-20">Club</th>
|
||||||
|
<th *ngIf="event.doublesEvent" scope="col" class="w-10">Speelsterkte</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let team of group.teams">
|
||||||
|
<td class="align-middle">{{ team.player1 | fullName }}</td>
|
||||||
|
<td class="align-middle">{{ team.player1.club }}</td>
|
||||||
|
<td class="align-middle">{{ getStrength(team.player1.strength.valueOf()) }}</td>
|
||||||
|
<td *ngIf="event.doublesEvent" class="align-middle">{{ team.player2 | fullName }}</td>
|
||||||
|
<td *ngIf="event.doublesEvent" class="align-middle">{{ team.player2?.club }}</td>
|
||||||
|
<td *ngIf="event.doublesEvent" class="align-middle">{{ getStrength(team.player2?.strength?.valueOf()) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</mat-tab>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (tournament.status == 'ONGOING') {
|
||||||
|
<mat-tab>
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<mat-icon>play_arrow</mat-icon>
|
||||||
|
Actieve wedstrijden
|
||||||
|
|
||||||
|
@if (this.activeMatches().length > 0) {
|
||||||
|
<span class="badge text-bg-success">{{ this.activeMatches().length }}</span>
|
||||||
|
}
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
@if (this.activeMatches().length > 0) {
|
||||||
|
<table class="table table-hover w-100 m-4">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="3">Wedstrijd</th>
|
||||||
|
<th>Onderdeel/Ronde</th>
|
||||||
|
<th>Start</th>
|
||||||
|
<th>Baan</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (activeMatch of this.activeMatches(); track activeMatch.match.id) {
|
||||||
|
<tr>
|
||||||
|
<td class="align-middle">{{ activeMatch.match.team1 | teamText }}</td>
|
||||||
|
<td class="align-middle">-</td>
|
||||||
|
<td class="align-middle">{{ activeMatch.match.team2 | teamText }}</td>
|
||||||
|
<td class="align-middle">{{ activeMatch.group.name }} {{ activeMatch.round.name }}</td>
|
||||||
|
<td class="align-middle">{{ activeMatch.match.startTime | date: 'HH:mm' }}</td>
|
||||||
|
<td class="align-middle">{{ activeMatch.match.court }}</td>
|
||||||
|
<td nowrap class="align-middle">
|
||||||
|
<button class="align-baseline" mat-button (click)="editResult(activeMatch.match, activeMatch.group, activeMatch.round)">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
Uitslag invoeren
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button [matMenuTriggerFor]="activeMatchMenu" [matMenuTriggerData]="{ match: activeMatch.match }" class="menu-button">
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<mat-menu #activeMatchMenu="matMenu">
|
||||||
|
<ng-template matMenuContent let-match="match">
|
||||||
|
<button mat-button (click)="stopMatch(match)">
|
||||||
|
<mat-icon>stop</mat-icon>
|
||||||
|
Wedstrijd stoppen
|
||||||
|
</button>
|
||||||
|
</ng-template>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
} @else {
|
||||||
|
<h6 class="mt-3">Geen actieve wedstrijden</h6>
|
||||||
|
}
|
||||||
|
</mat-tab>
|
||||||
|
}
|
||||||
|
@if (tournament.status == 'ONGOING' || tournament.status == 'DRAWN') {
|
||||||
|
<mat-tab>
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<mat-icon>list</mat-icon>
|
||||||
|
Onderdelen
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<mat-tab-group animationDuration="0ms" disableRipple="true">
|
||||||
|
<ng-container *ngFor="let event of tournament.events">
|
||||||
|
<ng-container *ngFor="let group of event.groups">
|
||||||
|
<mat-tab label="{{group.id}}">
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<!--<mat-icon>list</mat-icon> -->
|
||||||
|
{{ group.name }}
|
||||||
|
|
||||||
|
@if (getActiveMatchCountForGroup(group) > 0) {
|
||||||
|
<span class="badge text-bg-success">{{ getActiveMatchCountForGroup(group) }}</span>
|
||||||
|
}
|
||||||
|
@if (group.status != 'FINISHED' && groupOnlyHasFinishedRounds(group)) {
|
||||||
|
<button mat-icon-button [matMenuTriggerFor]="groupMenu" class="menu-button m-3">
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #groupMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="finishGroup(group)">
|
||||||
|
<mat-icon>check</mat-icon>
|
||||||
|
Onderdeel afsluiten
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
}
|
||||||
|
</ng-template>
|
||||||
|
<mat-tab-group
|
||||||
|
animationDuration="0ms"
|
||||||
|
disableRipple="true"
|
||||||
|
[(selectedIndex)]="activeRoundTab"
|
||||||
|
(selectedTabChange)="onRoundTabChange($event)">
|
||||||
|
<ng-container *ngFor="let round of group.rounds; index as roundIndex">
|
||||||
|
<mat-tab label="{{round.id}}">
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<mat-icon>{{ getRoundIcon(round.status) }}</mat-icon>
|
||||||
|
{{ round.name }}
|
||||||
|
|
||||||
|
<button mat-icon-button [matMenuTriggerFor]="activeRoundMenu" class="menu-button m-3">
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #activeRoundMenu="matMenu">
|
||||||
|
@if (round.status == 'NOT_STARTED') {
|
||||||
|
<button mat-menu-item (click)="startRound(round)">
|
||||||
|
<mat-icon>play_arrow</mat-icon>
|
||||||
|
Ronde starten
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="printMatchSheets(round)">
|
||||||
|
<mat-icon>print</mat-icon>
|
||||||
|
Wedstrijdbriefjes printen
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
<button mat-menu-item (click)="printRoundOverview(round)">
|
||||||
|
<mat-icon>print</mat-icon>
|
||||||
|
Rondeoverzicht printen
|
||||||
|
</button>
|
||||||
|
@if (round.status == 'IN_PROGRESS' && checkRoundComplete(round)) {
|
||||||
|
<button mat-menu-item (click)="finishRound(round)">
|
||||||
|
<mat-icon>check</mat-icon>
|
||||||
|
Ronde afsluiten
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
@if (group.status != 'FINISHED' && round.status == 'FINISHED' && (roundIndex + 1) == group.rounds.length) {
|
||||||
|
<button mat-menu-item (click)="newRound(group)">
|
||||||
|
<mat-icon>playlist_add</mat-icon>
|
||||||
|
Nieuwe ronde
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</mat-menu>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<h6 class="mt-3">Wedstrijden</h6>
|
||||||
|
|
||||||
|
@if (round.status == 'NOT_STARTED') {
|
||||||
|
<table class="table table-hover m-4 wide w-100">
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let match of round.matches">
|
||||||
|
<td class="align-middle w-team">{{ match.team1 | teamText }}</td>
|
||||||
|
<td class="align-middle w-sep">-</td>
|
||||||
|
<td class="align-middle w-team">{{ match.team2 | teamText }}</td>
|
||||||
|
<td class="align-middle w-fill"></td>
|
||||||
|
</tr>
|
||||||
|
@if (round.drawnOut) {
|
||||||
|
<tr>
|
||||||
|
<td class="align-middle w-100" colspan="4"><b>Deze ronde uitgeloot:</b> {{ round.drawnOut | teamText }}</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
} @else if (round.status == 'IN_PROGRESS') {
|
||||||
|
<table class="table table-hover m-4 wide w-100">
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let match of round.matches">
|
||||||
|
<td class="align-middle w-team" [ngClass]="{'winner': checkWinner(match) == 1}">{{ match.team1 | teamText }}</td>
|
||||||
|
<td class="align-middle w-sep">-</td>
|
||||||
|
<td class="align-middle w-team" [ngClass]="{'winner': checkWinner(match) == 2}">{{ match.team2 | teamText }}</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>
|
||||||
|
} @else if (match.status == 'IN_PROGRESS') {
|
||||||
|
<button mat-button (click)="editResult(match, group, round)">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
Uitslag invoeren
|
||||||
|
</button>
|
||||||
|
<button mat-button (click)="stopMatch(match)">
|
||||||
|
<mat-icon>stop</mat-icon>
|
||||||
|
Wedstrijd stoppen
|
||||||
|
</button>
|
||||||
|
} @else if (match.status == 'FINISHED') {
|
||||||
|
<div class="row result align-items-center">
|
||||||
|
<span *ngFor="let game of match.games" class="col-2">{{ game.score1 }}-{{ game.score2 }}</span>
|
||||||
|
@if (match.games.length == 2) {
|
||||||
|
<span class="col-2"></span>
|
||||||
|
}
|
||||||
|
|
||||||
|
<button mat-icon-button [matMenuTriggerFor]="finishedMatchMenu" class="menu-button m-3">
|
||||||
|
<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>
|
||||||
|
Uitslag bewerken
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@if (round.drawnOut) {
|
||||||
|
<tr>
|
||||||
|
<td class="align-middle w-100" colspan="4"><b>Deze ronde uitgeloot:</b> {{ round.drawnOut | teamText }}</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
} @else if (round.status == 'FINISHED') {
|
||||||
|
<table class="table table-hover m-4 wide {{ this.groupIsDoublesType(group) ? 'w-100' : 'w-100' }}">
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let match of round.matches">
|
||||||
|
<td class="align-middle w-team" [ngClass]="{'winner': checkWinner(match) == 1}">{{ match.team1 | teamText }}</td>
|
||||||
|
<td class="align-middle w-sep">-</td>
|
||||||
|
<td class="align-middle w-team" [ngClass]="{'winner': checkWinner(match) == 2}">{{ match.team2 | teamText }}</td>
|
||||||
|
<td class="align-middle w-fill">
|
||||||
|
<div class="row result align-items-center">
|
||||||
|
<span *ngFor="let game of match.games" class="col-2">{{ game.score1 }}-{{ game.score2 }}</span>
|
||||||
|
@if (match.games.length == 2) {
|
||||||
|
<span class="col-2"></span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@if (round.drawnOut) {
|
||||||
|
<tr>
|
||||||
|
<td class="align-middle w-100" colspan="4"><b>Deze ronde uitgeloot:</b> {{ round.drawnOut | teamText }}</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h6 class="mt-3">Stand</h6>
|
||||||
|
|
||||||
|
<table class="table w-75 m-4">
|
||||||
|
<caption>Tussen haakjes de gemiddelden per gespeelde wedstrijd</caption>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>
|
||||||
|
@if (event.doublesEvent) {
|
||||||
|
Team
|
||||||
|
} @else {
|
||||||
|
Speler
|
||||||
|
}
|
||||||
|
</th>
|
||||||
|
<th>Gespeeld</th>
|
||||||
|
<th>Punten</th>
|
||||||
|
<th>Games</th>
|
||||||
|
<th>Wedstrijdpunten</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="table-group-divider">
|
||||||
|
<tr *ngFor="let entry of getStandingsForRound(round, group).entries">
|
||||||
|
<td class="align-middle">{{ entry.position }}</td>
|
||||||
|
<td class="align-middle">{{ entry.team | teamText }}</td>
|
||||||
|
<td class="align-middle">{{ entry.played }}</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
@if (entry.played > 0 ) {
|
||||||
|
{{ entry.points }} ({{ entry.points / entry.played | number: '1.0-2' }})
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
@if (entry.played > 0 ) {
|
||||||
|
{{ entry.gamesWon }}-{{ entry.gamesLost}} ({{ (entry.gamesWon - entry.gamesLost) / entry.played | number: '1.0-2' }})
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
@if (entry.played > 0 ) {
|
||||||
|
{{ entry.pointsWon }}-{{ entry.pointsLost }} ({{ (entry.pointsWon - entry.pointsLost) / entry.played | number: '1.0-2' }})
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</mat-tab>
|
||||||
|
</ng-container>
|
||||||
|
</mat-tab-group>
|
||||||
|
</mat-tab>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</mat-tab-group>
|
||||||
|
</mat-tab>
|
||||||
|
}
|
||||||
|
@if (tournament.status == 'ONGOING' || tournament.status == 'DRAWN') {
|
||||||
|
<mat-tab>
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<mat-icon>settings</mat-icon>
|
||||||
|
Beheer
|
||||||
|
</ng-template>
|
||||||
|
<mat-tab-group animationDuration="0ms" disableRipple="true">
|
||||||
|
<mat-tab>
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<mat-icon>group</mat-icon>
|
||||||
|
Spelerslijst
|
||||||
|
</ng-template>
|
||||||
|
<table class="table table-hover w-75 m-4">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Naam</th>
|
||||||
|
<th>Onderdelen</th>
|
||||||
|
<th>Kosten</th>
|
||||||
|
<th>Betaald</th>
|
||||||
|
<th>Aanwezig</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let tournamentPlayer of tournament.tournamentPlayers">
|
||||||
|
<td>{{ tournamentPlayer.name }}</td>
|
||||||
|
<td>
|
||||||
|
<ng-container *ngFor="let event of tournamentPlayer.events">
|
||||||
|
{{ event }}
|
||||||
|
</ng-container>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ tournament.costsPerEvent[tournamentPlayer.events.length - 1] | currency:'EUR':'symbol':'1.2-2':'nl' }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<mat-slide-toggle [(ngModel)]="tournamentPlayer.paid" (change)="playerPaid($event, tournamentPlayer.playerId)">
|
||||||
|
@if (tournamentPlayer.paid) {
|
||||||
|
Betaald
|
||||||
|
} @else {
|
||||||
|
Nog niet betaald
|
||||||
|
}
|
||||||
|
</mat-slide-toggle>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<mat-slide-toggle [(ngModel)]="tournamentPlayer.present" (change)="playerPresent($event, tournamentPlayer.playerId)">
|
||||||
|
@if (tournamentPlayer.present) {
|
||||||
|
Aanwezig
|
||||||
|
} @else {
|
||||||
|
Nog niet aanwezig
|
||||||
|
}
|
||||||
|
</mat-slide-toggle>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
</mat-tab>
|
||||||
|
}
|
||||||
|
</mat-tab-group>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
td {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
td, th {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.wide td, table.wide th {
|
||||||
|
height: 4em;
|
||||||
|
}
|
||||||
|
.winner {
|
||||||
|
color: green;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.w-team {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.w-sep {
|
||||||
|
width: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.w-fill {
|
||||||
|
width: 35%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,355 @@
|
|||||||
|
import {Component, inject, Input, OnInit} from '@angular/core';
|
||||||
|
import {
|
||||||
|
MatAccordion,
|
||||||
|
MatExpansionPanel,
|
||||||
|
MatExpansionPanelDescription,
|
||||||
|
MatExpansionPanelHeader,
|
||||||
|
MatExpansionPanelTitle
|
||||||
|
} from "@angular/material/expansion";
|
||||||
|
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
|
||||||
|
import {CurrencyPipe, DatePipe, DecimalPipe, NgClass, NgForOf, NgIf} from "@angular/common";
|
||||||
|
import {TeamPipe} from "../../pipes/team-pipe";
|
||||||
|
import {TournamentService} from "../../service/tournament.service";
|
||||||
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
|
import {Tournament} from "../../model/tournament";
|
||||||
|
import {FullNamePipe} from "../../pipes/fullname-pipe";
|
||||||
|
import {MatList, MatListItem} from "@angular/material/list";
|
||||||
|
import {MatDivider} from "@angular/material/divider";
|
||||||
|
import {MatAnchor, MatButton, MatIconAnchor, MatIconButton, MatMiniFabAnchor} from "@angular/material/button";
|
||||||
|
import {MatIcon} from "@angular/material/icon";
|
||||||
|
import {Group} from "../../model/group";
|
||||||
|
import {Round} from "../../model/round";
|
||||||
|
import {MatTable} from "@angular/material/table";
|
||||||
|
import {StatusPipe} from "../../pipes/status-pipe";
|
||||||
|
import {MatMenu, MatMenuContent, MatMenuItem, MatMenuTrigger} from "@angular/material/menu";
|
||||||
|
import {Match} from "../../model/match";
|
||||||
|
import {FormsModule} from "@angular/forms";
|
||||||
|
import {MatTab, MatTabChangeEvent, MatTabGroup, MatTabLabel} from "@angular/material/tabs";
|
||||||
|
import {MatchResultComponent} from "../match-result/match-result.component";
|
||||||
|
import {MatDialog} from "@angular/material/dialog";
|
||||||
|
import {MatchResultPipe} from "../../pipes/match-result-pipe";
|
||||||
|
import {Event} from "../../model/event";
|
||||||
|
import {TournamentValidateComponent} from "../tournament-validate/tournament-validate.component";
|
||||||
|
import {Strength} from "../../model/player";
|
||||||
|
import {MatSlideToggle, MatSlideToggleChange} from "@angular/material/slide-toggle";
|
||||||
|
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||||
|
import {CourtSelectionComponent} from "../court-selection/court-selection.component";
|
||||||
|
import {Standings} from "../../model/standings";
|
||||||
|
import {Title} from '@angular/platform-browser';
|
||||||
|
import {TitleService} from "../../service/title.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-tournament-manage',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
FullNamePipe,
|
||||||
|
MatAccordion,
|
||||||
|
MatCard,
|
||||||
|
MatCardContent,
|
||||||
|
MatCardHeader,
|
||||||
|
MatExpansionPanel,
|
||||||
|
MatExpansionPanelDescription,
|
||||||
|
MatExpansionPanelHeader,
|
||||||
|
MatExpansionPanelTitle,
|
||||||
|
NgForOf,
|
||||||
|
NgIf,
|
||||||
|
TeamPipe,
|
||||||
|
MatListItem,
|
||||||
|
MatDivider,
|
||||||
|
MatList,
|
||||||
|
MatAnchor,
|
||||||
|
MatIcon,
|
||||||
|
MatTable,
|
||||||
|
StatusPipe,
|
||||||
|
NgClass,
|
||||||
|
MatMenu,
|
||||||
|
MatMenuItem,
|
||||||
|
MatMenuTrigger,
|
||||||
|
FormsModule,
|
||||||
|
DatePipe,
|
||||||
|
MatTabGroup,
|
||||||
|
MatTab,
|
||||||
|
MatTabLabel,
|
||||||
|
MatButton,
|
||||||
|
MatchResultPipe,
|
||||||
|
MatIconAnchor,
|
||||||
|
MatMiniFabAnchor,
|
||||||
|
MatIconButton,
|
||||||
|
DecimalPipe,
|
||||||
|
TournamentValidateComponent,
|
||||||
|
MatSlideToggle,
|
||||||
|
CurrencyPipe,
|
||||||
|
MatMenuContent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
FullNamePipe,
|
||||||
|
TeamPipe,
|
||||||
|
MatchResultPipe
|
||||||
|
],
|
||||||
|
templateUrl: './tournament-manage.component.html',
|
||||||
|
styleUrl: './tournament-manage.component.scss'
|
||||||
|
})
|
||||||
|
export class TournamentManageComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() tournament: Tournament;
|
||||||
|
|
||||||
|
activeRoundTab: number = 0;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private tournamentService: TournamentService,
|
||||||
|
private _snackBar: MatSnackBar,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private titleService: TitleService,
|
||||||
|
private title: Title
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const id = this.route.snapshot.paramMap.get('id');
|
||||||
|
this.route.queryParams.subscribe(params => {
|
||||||
|
if (params['tab']) {
|
||||||
|
this.activeRoundTab = params['tab'];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.tournamentService.getById(Number(id)).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
this.titleService.setTitle(this.tournament.name);
|
||||||
|
this.title.setTitle(this.tournament.name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onRoundTabChange(event: MatTabChangeEvent) {
|
||||||
|
const index = event.index;
|
||||||
|
this.router.navigate(
|
||||||
|
['tournaments/' + this.tournament.id + '/manage'],
|
||||||
|
{
|
||||||
|
relativeTo: null, queryParams: {tab: index}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoundIcon(status: String) {
|
||||||
|
if (status == "FINISHED") {
|
||||||
|
return "check";
|
||||||
|
} else if (status == "IN_PROGRESS") {
|
||||||
|
return "play_arrow";
|
||||||
|
} else if (status == "NOT_STARTED") {
|
||||||
|
return "hourglass_top";
|
||||||
|
} else {
|
||||||
|
return "warning";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getStandingsForRound(round: Round, group: Group): Standings {
|
||||||
|
if (round.status == 'FINISHED') {
|
||||||
|
return round.standings;
|
||||||
|
} else {
|
||||||
|
return group.standings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
groupIsDoublesType(group: Group) {
|
||||||
|
return group.type == 'HD' || group.type == 'DD' || group.type == 'GD';
|
||||||
|
}
|
||||||
|
|
||||||
|
startRound(round: Round) {
|
||||||
|
this.tournamentService.startRound(this.tournament.id, round.id).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
finishRound(round: Round) {
|
||||||
|
this.tournamentService.finishRound(this.tournament.id, round.id).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
finishGroup(group: Group) {
|
||||||
|
this.tournamentService.finishGroup(this.tournament.id, group.id).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
divideTournament() {
|
||||||
|
this.tournamentService.divide(this.tournament.id).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearDivision() {
|
||||||
|
this.tournamentService.clearDivision(this.tournament.id).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
drawTournament() {
|
||||||
|
this.tournamentService.draw(this.tournament.id).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startMatch(match: Match) {
|
||||||
|
const availableCourts = this.getAvailableCourts();
|
||||||
|
if (availableCourts.length == 0) {
|
||||||
|
alert('Geen banen beschikbaar!');
|
||||||
|
} else {
|
||||||
|
this.courtSelectionDialog.open(CourtSelectionComponent, {
|
||||||
|
data: {
|
||||||
|
match: match,
|
||||||
|
availableCourts: this.getAvailableCourts(),
|
||||||
|
totalCourts: this.tournament.courts
|
||||||
|
},
|
||||||
|
minWidth: '800px',
|
||||||
|
minHeight: '250px'
|
||||||
|
}).afterClosed().subscribe(result => {
|
||||||
|
if (result != undefined) {
|
||||||
|
this.tournamentService.startMatch(this.tournament.id, match.id, result).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAvailableCourts(): number[] {
|
||||||
|
const maxCourts = this.tournament.courts;
|
||||||
|
const activeCourts = this.activeMatches().map(activeMatch => activeMatch.match.court);
|
||||||
|
|
||||||
|
let i = 0, courts = Array(maxCourts);
|
||||||
|
while (i < maxCourts) courts[i++] = i;
|
||||||
|
|
||||||
|
return courts.filter(court => activeCourts.indexOf(court) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopMatch(match: Match) {
|
||||||
|
this.tournamentService.stopMatch(this.tournament.id, match.id).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
newRound(group: Group) {
|
||||||
|
this.tournamentService.newRound(this.tournament.id, group.id).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
playerPaid($event: MatSlideToggleChange, playerId: number) {
|
||||||
|
this.tournamentService.playerPaid(this.tournament.id, playerId, $event.checked).subscribe(() => {
|
||||||
|
this._snackBar.open('Opgeslagen.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
playerPresent($event: MatSlideToggleChange, playerId: number) {
|
||||||
|
this.tournamentService.playerPresent(this.tournament.id, playerId, $event.checked).subscribe(() => {
|
||||||
|
this._snackBar.open('Opgeslagen.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getStrength(strength: string | undefined) {
|
||||||
|
if (strength == undefined) return "";
|
||||||
|
for (let [key, value] of Object.entries(Strength)) {
|
||||||
|
if (key == strength) return value;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
printMatchSheets(round: Round) {
|
||||||
|
window.open(`tournaments/${this.tournament.id}/rounds/${round.id}/matchsheets`, "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
printRoundOverview(round: Round) {
|
||||||
|
window.open(`tournaments/${this.tournament.id}/rounds/${round.id}/overview`, "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
activeMatches(): ActiveMatch[] {
|
||||||
|
let matches: ActiveMatch[] = [];
|
||||||
|
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) {
|
||||||
|
if (match.status == 'IN_PROGRESS') {
|
||||||
|
matches.push(new ActiveMatch(match, round, group));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveMatchCountForGroup(group: Group): number {
|
||||||
|
let active = 0;
|
||||||
|
for (const round of group.rounds) {
|
||||||
|
for (const match of round.matches) {
|
||||||
|
if (match.status == 'IN_PROGRESS') {
|
||||||
|
active++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
groupOnlyHasFinishedRounds(group: Group): boolean {
|
||||||
|
let allFinished = true;
|
||||||
|
for (const round of group.rounds) {
|
||||||
|
allFinished &&= round.status == 'FINISHED';
|
||||||
|
}
|
||||||
|
return allFinished;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchResultDialog = inject(MatDialog);
|
||||||
|
courtSelectionDialog = inject(MatDialog);
|
||||||
|
|
||||||
|
editResult(match: Match, group: Group, round: Round) {
|
||||||
|
this.matchResultDialog.open(MatchResultComponent, {
|
||||||
|
data: {match: match, 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;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
checkWinner(match: Match): number {
|
||||||
|
if (match.games.length == 0) return 0;
|
||||||
|
if (match.games.length == 3) {
|
||||||
|
if (match.games[2].score1 > match.games[2].score2) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match.games[1].score1 > match.games[1].score2) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRoundComplete(round: Round) {
|
||||||
|
let complete: boolean = true;
|
||||||
|
for (let match of round.matches) {
|
||||||
|
complete &&= match.status == 'FINISHED';
|
||||||
|
}
|
||||||
|
|
||||||
|
return complete;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly TournamentEvent = Event;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ActiveMatch {
|
||||||
|
constructor(match: Match, round: Round, group: Group) {
|
||||||
|
this.match = match;
|
||||||
|
this.round = round;
|
||||||
|
this.group = group;
|
||||||
|
}
|
||||||
|
|
||||||
|
match: Match;
|
||||||
|
round: Round;
|
||||||
|
group: Group;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<mat-card appearance="outlined" *ngIf="tournament">
|
||||||
|
<mat-card-header>
|
||||||
|
<h5>Inschrijvingen voor {{ tournament.name }}ab</h5>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-card *ngFor="let event of tournament.events" appearance="outlined" class="m-3">
|
||||||
|
<mat-card-header>
|
||||||
|
<h6>{{ TournamentEvent.getType(event.type) }} ({{ event.registrations.length}} inschrijvingen)</h6>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<table class="table {{ event.doublesEvent ? 'w-100' : 'w-50' }}">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="w-25">Naam</th>
|
||||||
|
<th scope="col" class="w-25">Club</th>
|
||||||
|
<th *ngIf="event.doublesEvent" scope="col" class="w-25">Partner</th>
|
||||||
|
<th *ngIf="event.doublesEvent" scope="col" class="w-25">Club</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let registration of event.registrations">
|
||||||
|
<td class="align-middle">{{ registration.player | fullName }}</td>
|
||||||
|
<td class="align-middle">{{ registration.player.club }}</td>
|
||||||
|
<td *ngIf="event.doublesEvent" class="align-middle">{{ registration.partner | fullName }}</td>
|
||||||
|
<td *ngIf="event.doublesEvent" class="align-middle">{{ registration.partner?.club }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
td, th {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
|
||||||
|
import {Tournament} from "../../model/tournament";
|
||||||
|
import {TournamentService} from "../../service/tournament.service";
|
||||||
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
|
import {NgForOf, NgIf} from "@angular/common";
|
||||||
|
import {Player} from "../../model/player";
|
||||||
|
import {Event} from "../../model/event";
|
||||||
|
import {FullNamePipe} from "../../pipes/fullname-pipe";
|
||||||
|
import {TitleService} from "../../service/title.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-tournament-registrations',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
MatCard,
|
||||||
|
MatCardHeader,
|
||||||
|
MatCardContent,
|
||||||
|
NgForOf,
|
||||||
|
NgIf,
|
||||||
|
FullNamePipe
|
||||||
|
],
|
||||||
|
templateUrl: './tournament-registrations.component.html',
|
||||||
|
styleUrl: './tournament-registrations.component.scss'
|
||||||
|
})
|
||||||
|
export class TournamentRegistrationsComponent implements OnInit {
|
||||||
|
|
||||||
|
tournament?: Tournament;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private tournamentService: TournamentService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private titleService: TitleService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.titleService.setTitle("Inschrijvingen");
|
||||||
|
const id = this.route.snapshot.paramMap.get('id');
|
||||||
|
this.tournamentService.getById(Number(id)).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly TournamentEvent = Event;
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
@if (tournamentValidation && tournament) {
|
||||||
|
<mat-card appearance="outlined" class="m-3">
|
||||||
|
<mat-card-header>
|
||||||
|
<h6>Toernooi</h6>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-expansion-panel [disabled]="tournamentValidation.validations.length == 0">
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
Validaties <span class="badge {{ Validation.hasErrors(tournamentValidation.validations) ? 'text-bg-danger' : 'text-bg-success'}}">{{ tournamentValidation.validations.length }}</span>
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let validation of tournamentValidation.validations">
|
||||||
|
<mat-icon class="text-{{ getColorForSeverity(validation.severity) }}">{{ getIconForSeverity(validation.severity) }}</mat-icon>
|
||||||
|
{{ validation.message }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
<mat-card *ngFor="let event of tournament.events" appearance="outlined" class="m-3">
|
||||||
|
<mat-card-header>
|
||||||
|
<h6>{{ TournamentEvent.getType(event.type) }}</h6>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-accordion multi="true">
|
||||||
|
<mat-expansion-panel [disabled]="event.registrations.length == 0">
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
Inschrijvingen <span class="badge {{ event.registrations.length == 0 ? 'text-bg-danger' : 'text-bg-success'}}">{{ event.registrations.length }}</span>
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<table class="table {{ event.doublesEvent ? 'w-100' : 'w-50' }}">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="w-25">Naam</th>
|
||||||
|
<th scope="col" class="w-25">Club</th>
|
||||||
|
<th *ngIf="event.doublesEvent" scope="col" class="w-25">Partner</th>
|
||||||
|
<th *ngIf="event.doublesEvent" scope="col" class="w-25">Club</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let registration of event.registrations">
|
||||||
|
<td class="align-middle">{{ registration.player | fullName }}</td>
|
||||||
|
<td class="align-middle">{{ registration.player.club }}</td>
|
||||||
|
<td *ngIf="event.doublesEvent" class="align-middle">{{ registration.partner | fullName }}</td>
|
||||||
|
<td *ngIf="event.doublesEvent" class="align-middle">{{ registration.partner?.club }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
<mat-expansion-panel [disabled]="getEventValidation(event.id)?.validations?.length == 0">
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
Validaties <span class="badge {{ Validation.hasErrors(getEventValidation(event.id)?.validations) ? 'text-bg-danger' : 'text-bg-success'}}">{{ getEventValidation(event.id)?.validations?.length }}</span>
|
||||||
|
</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let validation of getEventValidation(event.id)?.validations">
|
||||||
|
<mat-icon class="text-{{ getColorForSeverity(validation.severity) }}">{{ getIconForSeverity(validation.severity) }}</mat-icon>
|
||||||
|
{{ validation.message }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
td, th {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {MatCard, MatCardContent, MatCardHeader} from "@angular/material/card";
|
||||||
|
import {Tournament} from "../../model/tournament";
|
||||||
|
import {AsyncPipe, NgForOf, NgIf} from "@angular/common";
|
||||||
|
import {Event} from "../../model/event";
|
||||||
|
import {TournamentService} from "../../service/tournament.service";
|
||||||
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
|
import {
|
||||||
|
MatAccordion,
|
||||||
|
MatExpansionPanel,
|
||||||
|
MatExpansionPanelHeader,
|
||||||
|
MatExpansionPanelTitle
|
||||||
|
} from "@angular/material/expansion";
|
||||||
|
import {Player} from "../../model/player";
|
||||||
|
import {EventValidation, TournamentValidation, Validation} from "../../model/tournamentValidation";
|
||||||
|
import {MatIcon} from "@angular/material/icon";
|
||||||
|
import {FullNamePipe} from "../../pipes/fullname-pipe";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-tournament-validate',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
MatCard,
|
||||||
|
MatCardHeader,
|
||||||
|
MatCardContent,
|
||||||
|
NgForOf,
|
||||||
|
MatExpansionPanel,
|
||||||
|
MatExpansionPanelTitle,
|
||||||
|
MatExpansionPanelHeader,
|
||||||
|
NgIf,
|
||||||
|
MatAccordion,
|
||||||
|
AsyncPipe,
|
||||||
|
MatIcon,
|
||||||
|
FullNamePipe
|
||||||
|
],
|
||||||
|
templateUrl: './tournament-validate.component.html',
|
||||||
|
styleUrl: './tournament-validate.component.scss'
|
||||||
|
})
|
||||||
|
export class TournamentValidateComponent implements OnInit {
|
||||||
|
tournament: Tournament;
|
||||||
|
tournamentValidation: TournamentValidation;
|
||||||
|
|
||||||
|
protected readonly TournamentEvent = Event;
|
||||||
|
protected readonly Player = Player;
|
||||||
|
protected readonly Validation = Validation;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private tournamentService: TournamentService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const id = this.route.snapshot.paramMap.get('id');
|
||||||
|
this.tournamentService.getById(Number(id)).subscribe(data => {
|
||||||
|
this.tournament = data;
|
||||||
|
});
|
||||||
|
this.tournamentService.getValidation(Number(id)).subscribe(data => {
|
||||||
|
this.tournamentValidation = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getEventValidation(id: number): EventValidation | null {
|
||||||
|
if (!this.tournamentValidation) {
|
||||||
|
return null;
|
||||||
|
} else {}
|
||||||
|
return this.tournamentValidation.eventValidations.filter(eventValidation => eventValidation.eventId == id)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
getIconForSeverity(severity: string) {
|
||||||
|
if (severity == 'INFO') {
|
||||||
|
return "info";
|
||||||
|
} else if (severity == 'WARN') {
|
||||||
|
return "warning";
|
||||||
|
} else {
|
||||||
|
return "dangerous";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getColorForSeverity(severity: string) {
|
||||||
|
if (severity == 'INFO') {
|
||||||
|
return "success";
|
||||||
|
} else if (severity == 'WARN') {
|
||||||
|
return "warning";
|
||||||
|
} else {
|
||||||
|
return "danger";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tournamentHasErrors(): boolean {
|
||||||
|
let hasErrors: boolean = Validation.hasErrors(this.tournamentValidation.validations);
|
||||||
|
for (let eventValidation of this.tournamentValidation.eventValidations) {
|
||||||
|
hasErrors &&= Validation.hasErrors(eventValidation.validations);
|
||||||
|
}
|
||||||
|
return hasErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
19
src/app/model/event.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import {Registration} from "./registration";
|
||||||
|
import {Group} from "./group";
|
||||||
|
|
||||||
|
export class Event {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
status: string;
|
||||||
|
doublesEvent: boolean;
|
||||||
|
registrations: Registration[];
|
||||||
|
groups: Group[];
|
||||||
|
|
||||||
|
static getType(type: string): string {
|
||||||
|
if (type == "HE") return "Herenenkel";
|
||||||
|
if (type == "HD") return "Herendubbel";
|
||||||
|
if (type == "DD") return "Damesdubbel";
|
||||||
|
if (type == "DE") return "Damesenkel";
|
||||||
|
return "Gemengd dubbel";
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/app/model/eventDivision.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import {Group} from "./group";
|
||||||
|
|
||||||
|
export class EventDivision {
|
||||||
|
eventId: number;
|
||||||
|
groups: Group[];
|
||||||
|
}
|
||||||
4
src/app/model/game.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export class Game {
|
||||||
|
score1: number;
|
||||||
|
score2: number;
|
||||||
|
}
|
||||||
13
src/app/model/group.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import {Team} from "./team";
|
||||||
|
import {Round} from "./round";
|
||||||
|
import {Standings} from "./standings";
|
||||||
|
|
||||||
|
export class Group {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
status: string;
|
||||||
|
rounds: Round[];
|
||||||
|
teams: Team[];
|
||||||
|
standings: Standings;
|
||||||
|
}
|
||||||
14
src/app/model/match.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Team } from "./team";
|
||||||
|
import {Game} from "./game";
|
||||||
|
|
||||||
|
export class Match {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
status: string;
|
||||||
|
team1: Team;
|
||||||
|
team2: Team;
|
||||||
|
startTime: Date;
|
||||||
|
endTime: Date;
|
||||||
|
games: Game[];
|
||||||
|
court: number;
|
||||||
|
}
|
||||||
24
src/app/model/player.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export enum Strength {
|
||||||
|
D5 = "5e divisie",
|
||||||
|
D6 = "6e divisie",
|
||||||
|
D7 = "7e divisie",
|
||||||
|
D8 = "8e divisie",
|
||||||
|
D9 = "9e divisie",
|
||||||
|
DR = "recreatief"
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Player {
|
||||||
|
id: number;
|
||||||
|
firstName: string;
|
||||||
|
middleName: string;
|
||||||
|
lastName: string;
|
||||||
|
sex: string;
|
||||||
|
birthday: string;
|
||||||
|
phoneNumber: string
|
||||||
|
email: string;
|
||||||
|
club: string;
|
||||||
|
strength: Strength;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
9
src/app/model/registration.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import {Player} from "./player";
|
||||||
|
|
||||||
|
export class Registration {
|
||||||
|
id: number;
|
||||||
|
tournament: number;
|
||||||
|
event: number;
|
||||||
|
player: Player;
|
||||||
|
partner?: Player;
|
||||||
|
}
|
||||||
6
src/app/model/result.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import {Game} from "./game";
|
||||||
|
|
||||||
|
export class Result {
|
||||||
|
matchId: number;
|
||||||
|
games: Game[] = [];
|
||||||
|
}
|
||||||
13
src/app/model/round.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import {Match} from "./match";
|
||||||
|
import {Team} from "./team";
|
||||||
|
import {Standings} from "./standings";
|
||||||
|
|
||||||
|
export class Round {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
matches: Match[];
|
||||||
|
status: string;
|
||||||
|
quit: Team[];
|
||||||
|
drawnOut: Team;
|
||||||
|
standings: Standings;
|
||||||
|
}
|
||||||
18
src/app/model/standings.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import {Team} from "./team";
|
||||||
|
|
||||||
|
export class Standings {
|
||||||
|
entries: StandingEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StandingEntry {
|
||||||
|
position: number;
|
||||||
|
team: Team;
|
||||||
|
played: number;
|
||||||
|
won: number;
|
||||||
|
lost: number;
|
||||||
|
points: number;
|
||||||
|
gamesWon: number;
|
||||||
|
gamesLost: number;
|
||||||
|
pointsWon: number;
|
||||||
|
pointsLost: number;
|
||||||
|
}
|
||||||
7
src/app/model/team.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import {Player} from "./player";
|
||||||
|
import {FullNamePipe} from "../pipes/fullname-pipe";
|
||||||
|
|
||||||
|
export class Team {
|
||||||
|
player1: Player;
|
||||||
|
player2: Player;
|
||||||
|
}
|
||||||
23
src/app/model/tournament.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import {Event} from "./event";
|
||||||
|
import {TournamentPlayer} from "./tournamentPlayer";
|
||||||
|
|
||||||
|
export class Tournament {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
date: string;
|
||||||
|
status: string;
|
||||||
|
events: Event[];
|
||||||
|
tournamentPlayers: TournamentPlayer[];
|
||||||
|
maxEvents: number;
|
||||||
|
costsPerEvent: number[] = [0, 0, 0];
|
||||||
|
courts: number;
|
||||||
|
|
||||||
|
static getStatus(tournament: Tournament): string {
|
||||||
|
if (tournament.status == "CLOSED") return "Afgerond";
|
||||||
|
if (tournament.status == "DIVIDED") return "Ingedeeld";
|
||||||
|
if (tournament.status == "DRAWN") return "Geloot";
|
||||||
|
if (tournament.status == "ONGOING") return "Bezig";
|
||||||
|
return "Nieuw";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
9
src/app/model/tournamentDivision.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import {EventDivision} from "./eventDivision";
|
||||||
|
|
||||||
|
export class TournamentDivision {
|
||||||
|
tournamentId: number;
|
||||||
|
divided: boolean;
|
||||||
|
eventDivisions: EventDivision[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
8
src/app/model/tournamentPlayer.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
export class TournamentPlayer {
|
||||||
|
playerId: number;
|
||||||
|
name: string;
|
||||||
|
events: string[];
|
||||||
|
paid: boolean;
|
||||||
|
present: boolean;
|
||||||
|
}
|
||||||
34
src/app/model/tournamentRegistration.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
export class TournamentRegistration {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
editable: boolean;
|
||||||
|
date: string;
|
||||||
|
status: string;
|
||||||
|
events: EventRegistration[];
|
||||||
|
|
||||||
|
static getStatus(tournamentRegistration: TournamentRegistration): string {
|
||||||
|
if (tournamentRegistration.status == "CLOSED") return "Afgerond";
|
||||||
|
if (tournamentRegistration.status == "DIVIDED") return "Ingedeeld";
|
||||||
|
if (tournamentRegistration.status == "DRAWN") return "Geloot";
|
||||||
|
if (tournamentRegistration.status == "IN_PROGRESS") return "Bezig";
|
||||||
|
return "Nieuw";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventRegistration {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
doublesEvent: boolean;
|
||||||
|
registered: boolean;
|
||||||
|
player?: number;
|
||||||
|
partner?: number;
|
||||||
|
|
||||||
|
static getType(type: string): string {
|
||||||
|
if (type == "HE") return "Herenenkel";
|
||||||
|
if (type == "HD") return "Herendubbel";
|
||||||
|
if (type == "DD") return "Damesdubbel";
|
||||||
|
if (type == "DE") return "Damesenkel";
|
||||||
|
return "Gemengd dubbel";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
24
src/app/model/tournamentValidation.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export class TournamentValidation {
|
||||||
|
id: number;
|
||||||
|
validations : Validation[];
|
||||||
|
eventValidations: EventValidation[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventValidation {
|
||||||
|
eventId: number;
|
||||||
|
validations: Validation[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Validation {
|
||||||
|
severity: string;
|
||||||
|
message: string;
|
||||||
|
|
||||||
|
static hasErrors(validations: Validation[] | undefined): boolean {
|
||||||
|
if (validations == null) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return validations.filter(v => v.severity == 'ERROR').length > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
14
src/app/pipes/fullname-pipe.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
@Pipe({
|
||||||
|
name: 'fullName',
|
||||||
|
standalone: true
|
||||||
|
})
|
||||||
|
export class FullNamePipe implements PipeTransform {
|
||||||
|
transform(person: any, args?: any): any {
|
||||||
|
if (person) {
|
||||||
|
return [person.firstName, person.middleName, person.lastName].join(" ");
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/app/pipes/match-result-pipe.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Pipe, PipeTransform } from "@angular/core";
|
||||||
|
@Pipe({
|
||||||
|
name: 'matchResult',
|
||||||
|
standalone: true
|
||||||
|
})
|
||||||
|
export class MatchResultPipe implements PipeTransform {
|
||||||
|
transform(match: any, args?: any): any {
|
||||||
|
let result = `${match.games[0].score1} – ${match.games[0].score2}`;
|
||||||
|
result += ` ${match.games[1].score1} – ${match.games[1].score2}`;
|
||||||
|
|
||||||
|
if (match.games[2] != null) {
|
||||||
|
result += ` ${match.games[2].score1} - ${match.games[2].score2}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/app/pipes/status-pipe.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Pipe, PipeTransform } from "@angular/core";
|
||||||
|
@Pipe({
|
||||||
|
name: 'status',
|
||||||
|
standalone: true
|
||||||
|
})
|
||||||
|
export class StatusPipe implements PipeTransform {
|
||||||
|
transform(objectWithStatus: any, args?: any): any {
|
||||||
|
if (objectWithStatus.status == 'NOT_STARTED') return 'Nog niet gestart';
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/app/pipes/team-pipe.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import {FullNamePipe} from "../pipes/fullname-pipe";
|
||||||
|
@Pipe({
|
||||||
|
name: 'teamText',
|
||||||
|
standalone: true
|
||||||
|
})
|
||||||
|
export class TeamPipe implements PipeTransform {
|
||||||
|
|
||||||
|
constructor(private fullNamePipe: FullNamePipe) {}
|
||||||
|
|
||||||
|
transform(team: any, args?: any): any {
|
||||||
|
if (team.player2 != null) {
|
||||||
|
// return this.player1.getFullName() + " / " + this.player2.getFullName();
|
||||||
|
return this.fullNamePipe.transform(team.player1) + " / " + this.fullNamePipe.transform(team.player2);
|
||||||
|
}
|
||||||
|
// return this.player1.getFullName();
|
||||||
|
return this.fullNamePipe.transform(team.player1);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/app/service/player.service.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Observable} from "rxjs";
|
||||||
|
import { Player } from "../model/player";
|
||||||
|
import {environment} from "../../environments/environment";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class PlayerService {
|
||||||
|
|
||||||
|
private readonly playersUrl: string;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {
|
||||||
|
this.playersUrl = `${environment.backendUrl}/players`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAll(): Observable<Player[]> {
|
||||||
|
return this.http.get<Player[]>(this.playersUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getById(id: number): Observable<Player> {
|
||||||
|
return this.http.get<Player>(`${this.playersUrl}/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public save(player: Player) {
|
||||||
|
return this.http.post<Player>(this.playersUrl, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(id: number, player: Player) {
|
||||||
|
return this.http.put<Player>(`${this.playersUrl}/${id}`, player);
|
||||||
|
}
|
||||||
|
}
|
||||||