Compare commits
	
		
			107 commits
		
	
	
		
			
				loeschen-l
			
			...
			
				main
			
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 84b855b8aa | |||
| 1e42614b22 | |||
| 2582c5b568 | |||
| f51619e172 | |||
| 6efe952831 | |||
| 2e9b48be7c | |||
| 60c5d41161 | |||
| 951ab76128 | |||
| f941fff8d6 | |||
| e5b2d7789f | |||
| 300520093d | |||
| fb90c770f6 | |||
| 9b90c735f5 | |||
| ebd4508743 | |||
| 5cc396f552 | |||
| f3738ff631 | |||
| fbec66debc | |||
| 4dc253b508 | |||
| 5f18aa207d | |||
| 2a337f3586 | |||
| 471a675117 | |||
| 9b80777fc3 | |||
| 08adf21da9 | |||
| 158c7acb55 | |||
| 11da909e3f | |||
| d920f8d774 | |||
| 421740910e | |||
| d6f68afd64 | |||
| d3b9362023 | |||
| e91dc49804 | |||
| b61e00e647 | |||
| bac1f3cfac | |||
| a6047e2720 | |||
| 3882cde8ca | |||
| 56a2ef77fa | |||
| d4cdfba01a | |||
| c1de527057 | |||
| 007a2f041b | |||
| c29ae4ef18 | |||
| 9e70e2a090 | |||
| b70d7c8e0e | |||
| f21d03dda3 | |||
| f3a61c10ad | |||
| 3a25307a36 | |||
| 00fefab60b | |||
| 2ee45c699a | |||
| b693ea7706 | |||
| 0c475e51a9 | |||
| 17f20461f8 | |||
| aceb2e953c | |||
| 370b1f54f5 | |||
| 5a4e14c2dd | |||
| db0966fdd9 | |||
| c292380fcd | |||
| c76749f3fd | |||
| a058720a6b | |||
| a598a68d34 | |||
| 8a3fb2aaf0 | |||
| 03a8586c38 | |||
| ab1c9ac76f | |||
| 182c014564 | |||
| b8f1dab9f4 | |||
| 408644ddad | |||
| c0ccc736bc | |||
| 1daed7ec65 | |||
| 2d062da74f | |||
| 433001001a | |||
| ae78af5440 | |||
| 769c775be4 | |||
| 59b210ff46 | |||
| 0cecd1cd2b | |||
| 6c1ec524ec | |||
| b30a40fe07 | |||
| 27eca287e2 | |||
| 3d2f049312 | |||
| 8a4a617c33 | |||
| b7e1ec2d57 | |||
| 303dadfc68 | |||
| 703d644799 | |||
| 6119621040 | |||
| 8d778ae773 | |||
| c9a4b5063f | |||
| c11772f08b | |||
| 61597627c6 | |||
| 4de394acb0 | |||
| ca26b84cc6 | |||
| 582e115285 | |||
| b6919107da | |||
| 2dc8956142 | |||
| a0672a03a1 | |||
| d80c334e29 | |||
| c7f3bf5c54 | |||
| 4c0371efb3 | |||
| d80f98f2a0 | |||
| 9219b92049 | |||
| 5d90b71ba5 | |||
|  | 9807f73f1a | ||
|  | 67179669c7 | ||
|  | e18436a57c | ||
| d736143ad6 | |||
| f10fe2455d | |||
| 3b8ed21b27 | |||
| feaf8f54d2 | |||
| 8abc5855dd | |||
| d86af94ac8 | |||
| 0bcc7d4684 | |||
| 5ff00d7510 | 
					 57 changed files with 2964 additions and 4178 deletions
				
			
		
							
								
								
									
										71
									
								
								.github/workflows/playwright.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								.github/workflows/playwright.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | ||||||
|  | name: Playwright Tests | ||||||
|  | 
 | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     branches: [ main, master ] | ||||||
|  |   pull_request: | ||||||
|  |     branches: [ main, master ] | ||||||
|  | 
 | ||||||
|  | jobs: | ||||||
|  |   test: | ||||||
|  |     timeout-minutes: 60 | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     services: | ||||||
|  |       postgres-employee: | ||||||
|  |         image: postgres:13.3 | ||||||
|  |         env: | ||||||
|  |           POSTGRES_DB: employee_db | ||||||
|  |           POSTGRES_USER: employee | ||||||
|  |           POSTGRES_PASSWORD: secret | ||||||
|  | 
 | ||||||
|  |       employee: | ||||||
|  |         image: berndheidemann/employee-management-service:1.1.3 | ||||||
|  |         # image: berndheidemann/employee-management-service_without_keycloak:1.1 | ||||||
|  |         env: | ||||||
|  |           spring.datasource.url: jdbc:postgresql://postgres-employee:5432/employee_db | ||||||
|  |           spring.datasource.username: employee | ||||||
|  |           spring.datasource.password: secret | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     steps: | ||||||
|  |       # Checkout the repository | ||||||
|  |       - uses: actions/checkout@v4 | ||||||
|  | 
 | ||||||
|  |       # Set up Node.js | ||||||
|  |       - uses: actions/setup-node@v4 | ||||||
|  |         with: | ||||||
|  |           node-version: lts/* | ||||||
|  | 
 | ||||||
|  |       # Install project dependencies | ||||||
|  |       - name: Install dependencies | ||||||
|  |         run: npm ci | ||||||
|  | 
 | ||||||
|  |       # Build the Angular project | ||||||
|  |       - name: Build Angular Project | ||||||
|  |         run: | | ||||||
|  |           npm install -g @angular/cli | ||||||
|  |           ng build --configuration=pipeline | ||||||
|  | 
 | ||||||
|  |       # Start Angular development server | ||||||
|  |       - name: Start Angular Development Server in Background | ||||||
|  |         run: ng serve --configuration=pipeline > angular.log 2>&1 & | ||||||
|  |         env: | ||||||
|  |           PORT: 4200 # Ensure the server runs on a predictable port | ||||||
|  | 
 | ||||||
|  |       # Install Playwright and dependencies | ||||||
|  |       - name: Install Playwright Browsers | ||||||
|  |         run: npx playwright install --with-deps | ||||||
|  | 
 | ||||||
|  |       # Run Playwright tests | ||||||
|  |       - name: Run Playwright Tests | ||||||
|  |         run: npx playwright test | ||||||
|  |         env: | ||||||
|  |           CI: true # Ensures Playwright runs in CI mode | ||||||
|  | 
 | ||||||
|  |       # Upload Playwright report | ||||||
|  |       - uses: actions/upload-artifact@v3 | ||||||
|  |         if: ${{ !cancelled() }} | ||||||
|  |         with: | ||||||
|  |           name: playwright-report | ||||||
|  |           path: playwright-report/ | ||||||
|  |           retention-days: 30 | ||||||
							
								
								
									
										5
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -40,3 +40,8 @@ testem.log | ||||||
| # System files | # System files | ||||||
| .DS_Store | .DS_Store | ||||||
| Thumbs.db | Thumbs.db | ||||||
|  | node_modules/ | ||||||
|  | /test-results/ | ||||||
|  | /playwright-report/ | ||||||
|  | /blob-report/ | ||||||
|  | /playwright/.cache/ | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								angular.json
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								angular.json
									
										
									
									
									
								
							|  | @ -52,6 +52,14 @@ | ||||||
|               "optimization": false, |               "optimization": false, | ||||||
|               "extractLicenses": false, |               "extractLicenses": false, | ||||||
|               "sourceMap": true |               "sourceMap": true | ||||||
|  |             }, | ||||||
|  |             "pipeline": { | ||||||
|  |               "fileReplacements": [ | ||||||
|  |                 { | ||||||
|  |                   "replace": "src/app/environments/environment.ts", | ||||||
|  |                   "with": "src/app/environments/environment.ci.ts" | ||||||
|  |                 } | ||||||
|  |               ] | ||||||
|             } |             } | ||||||
|           }, |           }, | ||||||
|           "defaultConfiguration": "production" |           "defaultConfiguration": "production" | ||||||
|  | @ -64,6 +72,9 @@ | ||||||
|             }, |             }, | ||||||
|             "development": { |             "development": { | ||||||
|               "buildTarget": "employeeService:build:development" |               "buildTarget": "employeeService:build:development" | ||||||
|  |             }, | ||||||
|  |             "pipeline": { | ||||||
|  |               "buildTarget": "employeeService:build:pipeline" | ||||||
|             } |             } | ||||||
|           }, |           }, | ||||||
|           "defaultConfiguration": "development" |           "defaultConfiguration": "development" | ||||||
|  | @ -93,5 +104,8 @@ | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |   }, | ||||||
|  |   "cli": { | ||||||
|  |     "analytics": false | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -6,14 +6,10 @@ services: | ||||||
|   postgres-employee: |   postgres-employee: | ||||||
|     container_name: postgres_employee |     container_name: postgres_employee | ||||||
|     image: postgres:13.3 |     image: postgres:13.3 | ||||||
|     volumes: |  | ||||||
|       - employee_postgres_data:/var/lib/postgresql/data |  | ||||||
|     environment: |     environment: | ||||||
|       POSTGRES_DB: employee_db |       POSTGRES_DB: employee_db | ||||||
|       POSTGRES_USER: employee |       POSTGRES_USER: employee | ||||||
|       POSTGRES_PASSWORD: secret |       POSTGRES_PASSWORD: secret | ||||||
|     ports: |  | ||||||
|       - "5432:5432" |  | ||||||
| 
 | 
 | ||||||
|   employee: |   employee: | ||||||
|     container_name: employee |     container_name: employee | ||||||
|  |  | ||||||
							
								
								
									
										4704
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										4704
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -28,7 +28,9 @@ | ||||||
|     "@angular-devkit/build-angular": "^18.2.0", |     "@angular-devkit/build-angular": "^18.2.0", | ||||||
|     "@angular/cli": "^18.2.0", |     "@angular/cli": "^18.2.0", | ||||||
|     "@angular/compiler-cli": "^18.2.0", |     "@angular/compiler-cli": "^18.2.0", | ||||||
|  |     "@playwright/test": "^1.49.1", | ||||||
|     "@types/jasmine": "~5.1.0", |     "@types/jasmine": "~5.1.0", | ||||||
|  |     "@types/node": "^22.10.7", | ||||||
|     "jasmine-core": "~5.2.0", |     "jasmine-core": "~5.2.0", | ||||||
|     "karma": "~6.4.0", |     "karma": "~6.4.0", | ||||||
|     "karma-chrome-launcher": "~3.2.0", |     "karma-chrome-launcher": "~3.2.0", | ||||||
|  |  | ||||||
							
								
								
									
										69
									
								
								playwright.config.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								playwright.config.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,69 @@ | ||||||
|  | import { defineConfig, devices } from '@playwright/test'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Read environment variables from file. | ||||||
|  |  * https://github.com/motdotla/dotenv
 | ||||||
|  |  */ | ||||||
|  | // import dotenv from 'dotenv';
 | ||||||
|  | // import path from 'path';
 | ||||||
|  | // dotenv.config({ path: path.resolve(__dirname, '.env') });
 | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * See https://playwright.dev/docs/test-configuration.
 | ||||||
|  |  */ | ||||||
|  | export default defineConfig({ | ||||||
|  |   testDir: './tests', | ||||||
|  |   /* Run tests in files in parallel */ | ||||||
|  |   fullyParallel: true, | ||||||
|  |   /* Fail the build on CI if you accidentally left test.only in the source code. */ | ||||||
|  |   forbidOnly: !!process.env.CI, | ||||||
|  |   /* Retry on CI only */ | ||||||
|  |   retries: process.env.CI ? 2 : 0, | ||||||
|  |   /* Opt out of parallel tests on CI. */ | ||||||
|  |   workers: process.env.CI ? 8 : undefined, | ||||||
|  |   /* Reporter to use. See https://playwright.dev/docs/test-reporters */ | ||||||
|  |   reporter: 'html', | ||||||
|  |   /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ | ||||||
|  |   use: { | ||||||
|  |     /* Base URL to use in actions like `await page.goto('/')`. */ | ||||||
|  |     // baseURL: 'http://127.0.0.1:3000',
 | ||||||
|  | 
 | ||||||
|  |     /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ | ||||||
|  |     trace: 'on-first-retry', | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   /* Configure projects for major browsers */ | ||||||
|  |   projects: [ | ||||||
|  |     { | ||||||
|  |       name: 'chromium', | ||||||
|  |       use: { ...devices['Desktop Chrome'] }, | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     /* Test against mobile viewports. */ | ||||||
|  |     // {
 | ||||||
|  |     //   name: 'Mobile Chrome',
 | ||||||
|  |     //   use: { ...devices['Pixel 5'] },
 | ||||||
|  |     // },
 | ||||||
|  |     // {
 | ||||||
|  |     //   name: 'Mobile Safari',
 | ||||||
|  |     //   use: { ...devices['iPhone 12'] },
 | ||||||
|  |     // },
 | ||||||
|  | 
 | ||||||
|  |     /* Test against branded browsers. */ | ||||||
|  |     // {
 | ||||||
|  |     //   name: 'Microsoft Edge',
 | ||||||
|  |     //   use: { ...devices['Desktop Edge'], channel: 'msedge' },
 | ||||||
|  |     // },
 | ||||||
|  |     // {
 | ||||||
|  |     //   name: 'Google Chrome',
 | ||||||
|  |     //   use: { ...devices['Desktop Chrome'], channel: 'chrome' },
 | ||||||
|  |     // },
 | ||||||
|  |   ], | ||||||
|  | 
 | ||||||
|  |   /* Run your local dev server before starting the tests */ | ||||||
|  |   // webServer: {
 | ||||||
|  |   //   command: 'npm run start',
 | ||||||
|  |   //   url: 'http://127.0.0.1:3000',
 | ||||||
|  |   //   reuseExistingServer: !process.env.CI,
 | ||||||
|  |   // },
 | ||||||
|  | }); | ||||||
|  | @ -1,25 +1,10 @@ | ||||||
| import {Component} from '@angular/core'; | import { Component } from '@angular/core'; | ||||||
| import {RouterOutlet} from '@angular/router'; | import { RouterOutlet } from '@angular/router'; | ||||||
| import {LoginViewComponent} from "./components/login-view/login-view.component"; |  | ||||||
| import { |  | ||||||
|   MitarbeiterverwaltungViewComponent |  | ||||||
| } from "./components/mitarbeiterverwaltung-view/mitarbeiterverwaltung-view.component"; |  | ||||||
| 
 |  | ||||||
| import {NavigationBarComponent} from './components/navigation-bar/navigation-bar.component'; |  | ||||||
| import {EmployeeDetailComponent} from './components/employee-detail/employee-detail.component'; |  | ||||||
| import { |  | ||||||
|   MitarbeiterBearbeitenViewComponent |  | ||||||
| } from "./components/mitarbeiter-bearbeiten-view/mitarbeiter-bearbeiten-view.component"; |  | ||||||
| import { |  | ||||||
|   QualifikatonBearbeitenViewComponent |  | ||||||
| } from "./components/qualifikaton-bearbeiten-view/qualifikaton-bearbeiten-view.component"; |  | ||||||
| import {QualifikatonDetailComponent} from "./components/qualifikaton-detail/qualifikaton-detail.component"; |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-root', |   selector: 'app-root', | ||||||
|   standalone: true, |   standalone: true, | ||||||
|   imports: [RouterOutlet, NavigationBarComponent, EmployeeDetailComponent, LoginViewComponent, MitarbeiterverwaltungViewComponent, MitarbeiterBearbeitenViewComponent, QualifikatonBearbeitenViewComponent, QualifikatonDetailComponent], |   imports: [RouterOutlet], | ||||||
|   templateUrl: './app.component.html', |   templateUrl: './app.component.html', | ||||||
|   styleUrl: './app.component.css' |   styleUrl: './app.component.css' | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import { APP_INITIALIZER, ApplicationConfig, provideZoneChangeDetection } from ' | ||||||
| import { provideRouter } from '@angular/router'; | import { provideRouter } from '@angular/router'; | ||||||
| 
 | 
 | ||||||
| import { routes } from './app.routes'; | import { routes } from './app.routes'; | ||||||
| import {KeycloakAngularModule, KeycloakBearerInterceptor, KeycloakService} from "keycloak-angular"; | import { KeycloakAngularModule, KeycloakBearerInterceptor, KeycloakService } from "keycloak-angular"; | ||||||
| import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; | import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; | ||||||
| 
 | 
 | ||||||
| export const initializeKeycloak = (keycloak: KeycloakService) => async () => | export const initializeKeycloak = (keycloak: KeycloakService) => async () => | ||||||
|  | @ -28,20 +28,20 @@ function initializeApp(keycloak: KeycloakService): () => Promise<boolean> { | ||||||
| 
 | 
 | ||||||
| export const appConfig: ApplicationConfig = { | export const appConfig: ApplicationConfig = { | ||||||
|   providers: [ |   providers: [ | ||||||
|         provideRouter(routes), |     provideRouter(routes), | ||||||
|         KeycloakAngularModule, |     KeycloakAngularModule, | ||||||
|         { |     { | ||||||
|           provide: APP_INITIALIZER, |       provide: APP_INITIALIZER, | ||||||
|           useFactory: initializeApp, |       useFactory: initializeApp, | ||||||
|           multi: true, |       multi: true, | ||||||
|           deps: [KeycloakService] |       deps: [KeycloakService] | ||||||
|         }, |     }, | ||||||
|         KeycloakService, |     KeycloakService, | ||||||
|         provideHttpClient(withInterceptorsFromDi()), |     provideHttpClient(withInterceptorsFromDi()), | ||||||
|         { |     { | ||||||
|           provide: HTTP_INTERCEPTORS, |       provide: HTTP_INTERCEPTORS, | ||||||
|           useClass: KeycloakBearerInterceptor, |       useClass: KeycloakBearerInterceptor, | ||||||
|           multi: true |       multi: true | ||||||
|         } |     } | ||||||
|       ] |   ] | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -3,8 +3,11 @@ import { LoginViewComponent } from "./components/login-view/login-view.component | ||||||
| import { MitarbeiterverwaltungViewComponent } from "./components/mitarbeiterverwaltung-view/mitarbeiterverwaltung-view.component"; | import { MitarbeiterverwaltungViewComponent } from "./components/mitarbeiterverwaltung-view/mitarbeiterverwaltung-view.component"; | ||||||
| import { EmployeeDetailComponent } from "./components/employee-detail/employee-detail.component"; | import { EmployeeDetailComponent } from "./components/employee-detail/employee-detail.component"; | ||||||
| import { QualifikatonBearbeitenViewComponent } from "./components/qualifikaton-bearbeiten-view/qualifikaton-bearbeiten-view.component"; | import { QualifikatonBearbeitenViewComponent } from "./components/qualifikaton-bearbeiten-view/qualifikaton-bearbeiten-view.component"; | ||||||
| import { KeycloakAuthGuard } from "keycloak-angular"; | import { MitarbeiterBearbeitenViewComponent } from "./components/mitarbeiter-bearbeiten-view/mitarbeiter-bearbeiten-view.component"; | ||||||
| import { AuthGuard } from "./service/auth.service"; | import { AuthGuard } from "./service/auth.service"; | ||||||
|  | import { MitarbeiterErstellenComponent } from "./components/mitarbeiter-erstellen/mitarbeiter-erstellen.component"; | ||||||
|  | import { QualifikationsverwaltungComponent } from "./components/qualifikationsverwaltung/qualifikationsverwaltung.component"; | ||||||
|  | import { QulifikationErstellenComponent } from "./components/qualifikation-erstellen/qualifikation-erstellen.component"; | ||||||
| 
 | 
 | ||||||
| export const routes: Routes = [ | export const routes: Routes = [ | ||||||
|   { |   { | ||||||
|  | @ -17,12 +20,34 @@ export const routes: Routes = [ | ||||||
|     canActivate: [AuthGuard], |     canActivate: [AuthGuard], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     path: "mitarbeiterdetails", |     path: "qualifikationerstellen", | ||||||
|     component: EmployeeDetailComponent, |     component: QulifikationErstellenComponent, | ||||||
|  |     canActivate: [AuthGuard], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     path: "qualifikationbearbeiten", |     path: "mitarbeitererstellen", | ||||||
|  |     component: MitarbeiterErstellenComponent, | ||||||
|  |     canActivate: [AuthGuard], | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     path: "mitarbeiterbearbeiten/:id", | ||||||
|  |     component: MitarbeiterBearbeitenViewComponent, | ||||||
|  |     canActivate: [AuthGuard], | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     path: "qualifikationsverwaltung", | ||||||
|  |     component: QualifikationsverwaltungComponent, | ||||||
|  |     canActivate: [AuthGuard], | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     path: "mitarbeiterdetails/:id", | ||||||
|  |     component: EmployeeDetailComponent, | ||||||
|  |     canActivate: [AuthGuard], | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     path: "qualifikationbearbeiten/:id", | ||||||
|     component: QualifikatonBearbeitenViewComponent, |     component: QualifikatonBearbeitenViewComponent, | ||||||
|  |     canActivate: [AuthGuard], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     path: "**", |     path: "**", | ||||||
|  |  | ||||||
|  | @ -1,28 +1,29 @@ | ||||||
| <div class="d-flex flex-col" style="height: 100%;"> | <div class="container"> | ||||||
|   <app-navigation-bar></app-navigation-bar> |   <div class="d-flex flex-col" style="height: 100%;"> | ||||||
|   <div class="p-3" style="width: 100%;"> |     <div class="p-3" style="width: 100%;"> | ||||||
|     <a href="" class="btn btn-primary">Zurück</a> |       <button class="btn btn-primary" (click)="goToEmployeePage()">Back</button> | ||||||
|     <div class="row align-items-start pt-3"> |       <div class="row align-items-start pt-3"> | ||||||
|       <div class="col"> |         <div class="col"> | ||||||
|         <h1>Name des Mitarbeiters</h1> |           <h1>{{ employee.firstName }} {{ employee.lastName }} </h1> | ||||||
|         <p><strong>Straße: </strong>Straße des Benutzers</p> |           <p><strong>Street: </strong>{{ employee.street }}</p> | ||||||
|         <p><strong>Postleitzahl: </strong>Postleitzahl des Benutzers</p> |           <p><strong>Postal Code: </strong>{{ employee.postcode }}</p> | ||||||
|         <p><strong>Stadt: </strong>Stadt des Benutzers</p> |           <p><strong>City: </strong>{{ employee.city }}</p> | ||||||
|         <p><strong>Telefonnummer: </strong>Telefonnummer des Benutzers</p> |           <p><strong>Phone number: </strong>{{ employee.phone }}s</p> | ||||||
|         <button class="btn btn-danger">Löschen</button> |           <button class="btn btn-danger" (click)="deleteEmployee(currentId)">Delete</button> | ||||||
|         <button class="ms-3 btn btn-primary">Bearbeiten</button> |           <button class="ms-3 btn btn-primary" (click)="editEmployee(currentId)">Edit</button> | ||||||
|       </div> |  | ||||||
|       <div class="col"> |  | ||||||
|         <h2>Qualifikationen</h2> |  | ||||||
|         <ul class="list-group" style="width: fit-content;"> |  | ||||||
|           <li class="list-group-item">Qualifikation 1</li> |  | ||||||
|           <li class="list-group-item">Qualifikation 2</li> |  | ||||||
|           <li class="list-group-item">Qualifikation 3</li> |  | ||||||
|           <li class="list-group-item">Qualifikation 4</li> |  | ||||||
|           <li class="list-group-item">Qualifikation 5</li> |  | ||||||
|         </ul> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
| 
 | 
 | ||||||
|  |         </div> | ||||||
|  |         <div class="col"> | ||||||
|  |           <h2>Qualifikationen</h2> | ||||||
|  |           <ul class="list-group" style="width: fit-content;"> | ||||||
|  |             @for(skill of skillSet; track skill) { | ||||||
|  |             <li class="list-group-item">{{ skill }}</li> | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |           </ul> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |     </div> | ||||||
|   </div> |   </div> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,11 @@ | ||||||
| import { Component } from '@angular/core'; | import {Component} from '@angular/core'; | ||||||
| import { NavigationBarComponent } from '../navigation-bar/navigation-bar.component'; | import {NavigationBarComponent} from '../navigation-bar/navigation-bar.component'; | ||||||
|  | import {EmployeeNameAndSkillDataDTO, EmployeeResponseDTO} from "../../models/mitarbeiter"; | ||||||
|  | import {EmployeeService} from "../../service/employee.service"; | ||||||
|  | import {ActivatedRoute, Router} from "@angular/router"; | ||||||
|  | import {Observable} from "rxjs"; | ||||||
|  | import {QualificationGetDTO} from "../../models/skill"; | ||||||
|  | import {SkillService} from "../../service/skill.service"; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-employee-detail', |   selector: 'app-employee-detail', | ||||||
|  | @ -9,5 +15,31 @@ import { NavigationBarComponent } from '../navigation-bar/navigation-bar.compone | ||||||
|   styleUrl: './employee-detail.component.css' |   styleUrl: './employee-detail.component.css' | ||||||
| }) | }) | ||||||
| export class EmployeeDetailComponent { | export class EmployeeDetailComponent { | ||||||
|  |   employee!: EmployeeResponseDTO; | ||||||
|  |   skillSet: string[] = []; | ||||||
|  |   currentId: number = 0; | ||||||
| 
 | 
 | ||||||
|  |   constructor(private employeeService: EmployeeService, private router: Router, private route: ActivatedRoute, private skillService: SkillService) { | ||||||
|  |     this.currentId = this.route.snapshot.params['id']; | ||||||
|  |     this.employeeService.getEmployeeById(this.route.snapshot.params['id']).subscribe(employee => { | ||||||
|  |       this.employee = employee; | ||||||
|  |       this.skillSet = this.employee.skillSet?.map(skill => skill.skill) || []; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   editEmployee(id: number) { | ||||||
|  |     this.router.navigate([`/mitarbeiterbearbeiten/${id}`]); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   goToEmployeePage(){ | ||||||
|  |     this.router.navigate(['mitarbeiter']); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   deleteEmployee(id: number) { | ||||||
|  |     this.employeeService.deleteEmployee(id); | ||||||
|  |     this.router.navigate([`mitarbeiter`]); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   protected readonly EmployeeService = EmployeeService; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,6 +3,6 @@ | ||||||
|   <img src="img.png" alt="Logout Icon" class="logo-image" /> |   <img src="img.png" alt="Logout Icon" class="logo-image" /> | ||||||
| 
 | 
 | ||||||
|   <form> |   <form> | ||||||
|     <button (click)="login()" type="submit">Mit KeyCLoak anmelden</button> |     <button (click)="login()" type="submit">Login with Keycloak</button> | ||||||
|   </form> |   </form> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  | @ -1,120 +0,0 @@ | ||||||
| body { |  | ||||||
|   font-family: sans-serif; |  | ||||||
|   margin: 0; |  | ||||||
|   padding: 20px; |  | ||||||
|   background-color: #f0f0f0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .container { |  | ||||||
|   width: 100%; |  | ||||||
|   max-width: 800px; |  | ||||||
|   margin: 0 auto; |  | ||||||
|   padding: 20px; |  | ||||||
|   background-color: #fff; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| h1, h2 { |  | ||||||
|   font-size: 2rem; |  | ||||||
|   margin-bottom: 20px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .back-button { |  | ||||||
|   background-color: #ccc; |  | ||||||
|   color: #333; |  | ||||||
|   border: none; |  | ||||||
|   padding: 8px 12px; |  | ||||||
|   border-radius: 3px; |  | ||||||
|   margin-bottom: 15px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .user-info { |  | ||||||
|   display: grid; |  | ||||||
|   grid-template-columns: 1fr 1fr; |  | ||||||
|   grid-gap: 20px; |  | ||||||
|   margin-bottom: 20px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .form-group { |  | ||||||
|   display: flex; |  | ||||||
|   flex-direction: column; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| label { |  | ||||||
|   margin-bottom: 5px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| input[type="text"] { |  | ||||||
|   padding: 10px; |  | ||||||
|   border: 1px solid #ddd; |  | ||||||
|   border-radius: 3px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| input[type="text"]::placeholder { |  | ||||||
|   color: #999; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .save-button { |  | ||||||
|   background-color: #007bff; |  | ||||||
|   color: #fff; |  | ||||||
|   padding: 10px 15px; |  | ||||||
|   border: none; |  | ||||||
|   border-radius: 3px; |  | ||||||
|   cursor: pointer; |  | ||||||
|   margin-top: 10px; |  | ||||||
|   width: 100%; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .skills-container { |  | ||||||
|   border: 1px solid #ccc; |  | ||||||
|   padding: 20px; |  | ||||||
|   border-radius: 3px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .skill-controls select { |  | ||||||
|   padding: 10px 10px; |  | ||||||
|   border: none; |  | ||||||
|   border-radius: 3px; |  | ||||||
|   background-color: #007bff; |  | ||||||
|   color: #fff; |  | ||||||
|   cursor: pointer; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .skill-list { |  | ||||||
|   list-style: none; |  | ||||||
|   padding: 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .skill-list li { |  | ||||||
|   display: flex; |  | ||||||
|   align-items: center; |  | ||||||
|   margin-bottom: 5px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .skill-icon { |  | ||||||
|   margin-right: 5px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .delete-skill-button { |  | ||||||
|   color: #fff; |  | ||||||
|   padding: 5px 8px; |  | ||||||
|   border: none; |  | ||||||
|   cursor: pointer; |  | ||||||
|   margin-left: 10px; |  | ||||||
|   border-radius: 3px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .delete-skill-button img { |  | ||||||
|   width: 15px; |  | ||||||
|   height: 15px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .add-skill-button{ |  | ||||||
|   background-color: #06a63b; |  | ||||||
|   color: #fff; |  | ||||||
|   padding: 8px 10px; |  | ||||||
|   border: none; |  | ||||||
|   border-radius: 3px; |  | ||||||
|   cursor: pointer; |  | ||||||
|   margin-left: 10px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
|  | @ -1,49 +1 @@ | ||||||
| <div class="container"> | <app-mitarbeiter-form [(mitarbeiter)]="mitarbeiter" (mitarbeiterChange)="submitted($event)"></app-mitarbeiter-form> | ||||||
|   <button class="back-button">Back</button> |  | ||||||
|   <div class="user-info"> |  | ||||||
|     <div class="form-group"> |  | ||||||
|       <label for="firstName">First Name</label> |  | ||||||
|       <input type="text" id="firstName" placeholder="First Name"> |  | ||||||
|     </div> |  | ||||||
|     <div class="form-group"> |  | ||||||
|       <label for="lastName">Last Name</label> |  | ||||||
|       <input type="text" id="lastName" placeholder="Last Name"> |  | ||||||
|     </div> |  | ||||||
|     <div class="form-group"> |  | ||||||
|       <label for="street">Street</label> |  | ||||||
|       <input type="text" id="street" placeholder="Street"> |  | ||||||
|     </div> |  | ||||||
|     <div class="form-group"> |  | ||||||
|       <label for="postcode">Postcode</label> |  | ||||||
|       <input type="text" id="postcode" placeholder="Postcode"> |  | ||||||
|     </div> |  | ||||||
|     <div class="form-group"> |  | ||||||
|       <label for="city">City</label> |  | ||||||
|       <input type="text" id="city" placeholder="City"> |  | ||||||
|     </div> |  | ||||||
|     <div class="form-group"> |  | ||||||
|       <label for="phone">Phone Number</label> |  | ||||||
|       <input type="text" id="phone" placeholder="Phone Number"> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| 
 |  | ||||||
|   <div class="skills-container"> |  | ||||||
|     <h2>Skills</h2> |  | ||||||
|     <ul class="skill-list"> |  | ||||||
|       <li> |  | ||||||
|         <span class="skill-name">Skill 1</span> |  | ||||||
|           <button class="delete-skill-button"> |  | ||||||
|             <img src="Delete-button.svg" alt="Delete"> |  | ||||||
|           </button> |  | ||||||
|       </li> |  | ||||||
|     </ul> |  | ||||||
|     <div class="skill-controls"> |  | ||||||
|       <select> |  | ||||||
|         <option value="">Option 1</option> |  | ||||||
|         <option value="">Option 2</option> |  | ||||||
|       </select> |  | ||||||
|       <button class="add-skill-button">Add qualification</button> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
|   <button class="save-button">Save</button> |  | ||||||
| </div> |  | ||||||
|  |  | ||||||
|  | @ -1,12 +1,43 @@ | ||||||
| import { Component } from '@angular/core'; | import { Component } from '@angular/core'; | ||||||
|  | import { MitarbeiterFormComponent } from '../mitarbeiter-form/mitarbeiter-form.component'; | ||||||
|  | import { EmployeeResponseDTO } from '../../models/mitarbeiter'; | ||||||
|  | import { ActivatedRoute, Router } from '@angular/router'; | ||||||
|  | import { EmployeeService } from '../../service/employee.service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-mitarbeiter-bearbeiten-view', |   selector: 'app-mitarbeiter-bearbeiten-view', | ||||||
|   standalone: true, |   standalone: true, | ||||||
|   imports: [], |   imports: [MitarbeiterFormComponent], | ||||||
|   templateUrl: './mitarbeiter-bearbeiten-view.component.html', |   templateUrl: './mitarbeiter-bearbeiten-view.component.html', | ||||||
|   styleUrl: './mitarbeiter-bearbeiten-view.component.css' |   styleUrl: './mitarbeiter-bearbeiten-view.component.css' | ||||||
| }) | }) | ||||||
| export class MitarbeiterBearbeitenViewComponent { | export class MitarbeiterBearbeitenViewComponent { | ||||||
|  |   public mitarbeiter!: EmployeeResponseDTO; | ||||||
| 
 | 
 | ||||||
|  |   constructor(private employeeService: EmployeeService, private route: ActivatedRoute, private router: Router) { } | ||||||
|  | 
 | ||||||
|  |   submitted(mitarbeiter: EmployeeResponseDTO) { | ||||||
|  |     this.employeeService.updateEmployee(mitarbeiter); | ||||||
|  |     this.returnToEmployeeOverview(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   returnToEmployeeOverview() { | ||||||
|  |     this.router.navigate(["mitarbeiter"]); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ngOnInit(): void { | ||||||
|  |     this.mitarbeiter = { | ||||||
|  |       id: 0, | ||||||
|  |       firstName: '', | ||||||
|  |       lastName: '', | ||||||
|  |       street: '', | ||||||
|  |       phone: '', | ||||||
|  |       skillSet: [], | ||||||
|  |       postcode: '', | ||||||
|  |       city: '', | ||||||
|  |     } | ||||||
|  |     this.employeeService.getEmployeeById(this.route.snapshot.params['id']).subscribe(employee => { | ||||||
|  |       this.mitarbeiter = employee; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | <app-mitarbeiter-form [(mitarbeiter)]="mitarbeiter" (mitarbeiterChange)="submitted($event)"></app-mitarbeiter-form> | ||||||
|  | @ -0,0 +1,23 @@ | ||||||
|  | import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||||
|  | 
 | ||||||
|  | import { MitarbeiterErstellenComponent } from './mitarbeiter-erstellen.component'; | ||||||
|  | 
 | ||||||
|  | describe('MitarbeiterErstellenComponent', () => { | ||||||
|  |   let component: MitarbeiterErstellenComponent; | ||||||
|  |   let fixture: ComponentFixture<MitarbeiterErstellenComponent>; | ||||||
|  | 
 | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     await TestBed.configureTestingModule({ | ||||||
|  |       imports: [MitarbeiterErstellenComponent] | ||||||
|  |     }) | ||||||
|  |     .compileComponents(); | ||||||
|  | 
 | ||||||
|  |     fixture = TestBed.createComponent(MitarbeiterErstellenComponent); | ||||||
|  |     component = fixture.componentInstance; | ||||||
|  |     fixture.detectChanges(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('should create', () => { | ||||||
|  |     expect(component).toBeTruthy(); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,44 @@ | ||||||
|  | import { Component } from '@angular/core'; | ||||||
|  | import { ActivatedRoute, Router } from '@angular/router'; | ||||||
|  | import { EmployeeResponseDTO } from '../../models/mitarbeiter'; | ||||||
|  | import { EmployeeService } from '../../service/employee.service'; | ||||||
|  | import { MitarbeiterFormComponent } from '../mitarbeiter-form/mitarbeiter-form.component'; | ||||||
|  | 
 | ||||||
|  | @Component({ | ||||||
|  |   selector: 'app-mitarbeiter-erstellen', | ||||||
|  |   standalone: true, | ||||||
|  |   imports: [MitarbeiterFormComponent], | ||||||
|  |   templateUrl: './mitarbeiter-erstellen.component.html', | ||||||
|  |   styleUrl: './mitarbeiter-erstellen.component.css' | ||||||
|  | }) | ||||||
|  | export class MitarbeiterErstellenComponent { | ||||||
|  |   public mitarbeiter!: EmployeeResponseDTO; | ||||||
|  | 
 | ||||||
|  |   constructor(private employeeService: EmployeeService, private route: ActivatedRoute, private router: Router) { } | ||||||
|  | 
 | ||||||
|  |   submitted(mitarbeiter: EmployeeResponseDTO) { | ||||||
|  |     console.log(mitarbeiter) | ||||||
|  |     this.employeeService.createEmployee(mitarbeiter); | ||||||
|  |     this.returnToEmployeeOverview(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   returnToEmployeeOverview() { | ||||||
|  |     this.router.navigate(["mitarbeiter"]); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ngOnInit(): void { | ||||||
|  |     this.mitarbeiter = { | ||||||
|  |       id: 0, | ||||||
|  |       firstName: '', | ||||||
|  |       lastName: '', | ||||||
|  |       street: '', | ||||||
|  |       phone: '', | ||||||
|  |       skillSet: [], | ||||||
|  |       postcode: '', | ||||||
|  |       city: '', | ||||||
|  |     } | ||||||
|  |     this.employeeService.getEmployeeById(this.route.snapshot.params['id']).subscribe(employee => { | ||||||
|  |       this.mitarbeiter = employee; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,120 @@ | ||||||
|  | body { | ||||||
|  |   font-family: sans-serif; | ||||||
|  |   margin: 0; | ||||||
|  |   padding: 20px; | ||||||
|  |   background-color: #f0f0f0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .container { | ||||||
|  |   width: 100%; | ||||||
|  |   max-width: 800px; | ||||||
|  |   margin: 0 auto; | ||||||
|  |   padding: 20px; | ||||||
|  |   background-color: #fff; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h1, h2 { | ||||||
|  |   font-size: 2rem; | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .back-button { | ||||||
|  |   background-color: #ccc; | ||||||
|  |   color: #333; | ||||||
|  |   border: none; | ||||||
|  |   padding: 8px 12px; | ||||||
|  |   border-radius: 3px; | ||||||
|  |   margin-bottom: 15px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .user-info { | ||||||
|  |   display: grid; | ||||||
|  |   grid-template-columns: 1fr 1fr; | ||||||
|  |   grid-gap: 20px; | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .form-group { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label { | ||||||
|  |   margin-bottom: 5px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input[type="text"] { | ||||||
|  |   padding: 10px; | ||||||
|  |   border: 1px solid #ddd; | ||||||
|  |   border-radius: 3px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input[type="text"]::placeholder { | ||||||
|  |   color: #999; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .save-button { | ||||||
|  |   background-color: #007bff; | ||||||
|  |   color: #fff; | ||||||
|  |   padding: 10px 15px; | ||||||
|  |   border: none; | ||||||
|  |   border-radius: 3px; | ||||||
|  |   cursor: pointer; | ||||||
|  |   margin-top: 10px; | ||||||
|  |   width: 100%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .skills-container { | ||||||
|  |   border: 1px solid #ccc; | ||||||
|  |   padding: 20px; | ||||||
|  |   border-radius: 3px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .skill-controls select { | ||||||
|  |   padding: 10px 10px; | ||||||
|  |   border: none; | ||||||
|  |   border-radius: 3px; | ||||||
|  |   background-color: #007bff; | ||||||
|  |   color: #fff; | ||||||
|  |   cursor: pointer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .skill-list { | ||||||
|  |   list-style: none; | ||||||
|  |   padding: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .skill-list li { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   margin-bottom: 5px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .skill-icon { | ||||||
|  |   margin-right: 5px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .delete-skill-button { | ||||||
|  |   color: #fff; | ||||||
|  |   padding: 5px 8px; | ||||||
|  |   border: none; | ||||||
|  |   cursor: pointer; | ||||||
|  |   margin-left: 10px; | ||||||
|  |   border-radius: 3px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .delete-skill-button img { | ||||||
|  |   width: 15px; | ||||||
|  |   height: 15px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .add-skill-button{ | ||||||
|  |   background-color: #06a63b; | ||||||
|  |   color: #fff; | ||||||
|  |   padding: 8px 10px; | ||||||
|  |   border: none; | ||||||
|  |   border-radius: 3px; | ||||||
|  |   cursor: pointer; | ||||||
|  |   margin-left: 10px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,78 @@ | ||||||
|  | <form [formGroup]="mitarbeiterForm"> | ||||||
|  | 
 | ||||||
|  |   <div class="container"> | ||||||
|  |     <button (click)="returnToEmployeeOverview()" class="back-button">Back</button> | ||||||
|  |     <div class="user-info"> | ||||||
|  |       <div class="form-group"> | ||||||
|  |         @if (errorMessages['firstName']) { | ||||||
|  |         <div class="alert alert-danger">{{errorMessages['firstName']}}</div> | ||||||
|  |         } | ||||||
|  |         <label for="firstName">First Name</label> | ||||||
|  |         <input type="text" id="firstName" placeholder="First Name" formControlName="firstName"> | ||||||
|  |       </div> | ||||||
|  |       <div class="form-group"> | ||||||
|  |         @if (errorMessages['lastName']) { | ||||||
|  |         <div class="alert alert-danger">{{errorMessages['lastName']}}</div> | ||||||
|  |         } | ||||||
|  |         <label for="lastName">Last Name</label> | ||||||
|  |         <input type="text" id="lastName" placeholder="Last Name" formControlName="lastName"> | ||||||
|  |       </div> | ||||||
|  |       <div class="form-group"> | ||||||
|  |         @if (errorMessages['street']) { | ||||||
|  |         <div class="alert alert-danger">{{errorMessages['street']}}</div> | ||||||
|  |         } | ||||||
|  |         <label for="street">Street</label> | ||||||
|  |         <input type="text" id="street" placeholder="Street" formControlName="street"> | ||||||
|  |       </div> | ||||||
|  |       <div class="form-group"> | ||||||
|  |         @if (errorMessages['postcode']) { | ||||||
|  |         <div class="alert alert-danger">{{errorMessages['postcode']}}</div> | ||||||
|  |         } | ||||||
|  |         <label for="postcode">Postcode</label> | ||||||
|  |         <input type="text" id="postcode" placeholder="Postcode" formControlName="postcode"> | ||||||
|  |       </div> | ||||||
|  |       <div class="form-group"> | ||||||
|  |         @if (errorMessages['city']) { | ||||||
|  |         <div class="alert alert-danger">{{errorMessages['city']}}</div> | ||||||
|  |         } | ||||||
|  |         <label for="city">City</label> | ||||||
|  |         <input type="text" id="city" placeholder="City" formControlName="city"> | ||||||
|  |       </div> | ||||||
|  |       <div class="form-group"> | ||||||
|  |         @if (errorMessages['phone']) { | ||||||
|  |         <div class="alert alert-danger">{{errorMessages['phone']}}</div> | ||||||
|  |         } | ||||||
|  |         <label for="phone">Phone Number</label> | ||||||
|  |         <input type="text" id="phone" placeholder="Phone Number" formControlName="phone"> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="skills-container"> | ||||||
|  |       <h2>Skills</h2> | ||||||
|  |       <ul class="skill-list"> | ||||||
|  |         @for (skill of mitarbeiter.skillSet; track skill) { | ||||||
|  |         <li> | ||||||
|  |           <span class="skill-name">{{skill.skill}}</span> | ||||||
|  |           <button (click)="removeSkill(skill.id)" class="delete-skill-button"> | ||||||
|  |             <img src="Delete-button.svg" alt="Delete"> | ||||||
|  |           </button> | ||||||
|  |         </li> | ||||||
|  |         } | ||||||
|  |       </ul> | ||||||
|  |       @if (!hasAllSkills) { | ||||||
|  |       <div class="skill-controls"> | ||||||
|  |         <select formControlName="newSkill"> | ||||||
|  |           @for (skill of allSkills | async; track skill) { | ||||||
|  |           @if (!hasSkill(skill.id)) { | ||||||
|  |           <option value="{{skill.id}}">{{skill.skill}}</option> | ||||||
|  |           } | ||||||
|  |           } | ||||||
|  |         </select> | ||||||
|  |         <button (click)="addSkill()" class="add-skill-button">Add qualification</button> | ||||||
|  |       </div> | ||||||
|  |       } | ||||||
|  |     </div> | ||||||
|  |     <button (click)="submit()" class="save-button">Save</button> | ||||||
|  |   </div> | ||||||
|  | 
 | ||||||
|  | </form> | ||||||
|  | @ -0,0 +1,23 @@ | ||||||
|  | import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||||
|  | 
 | ||||||
|  | import { MitarbeiterFormComponent } from './mitarbeiter-form.component'; | ||||||
|  | 
 | ||||||
|  | describe('MitarbeiterFormComponent', () => { | ||||||
|  |   let component: MitarbeiterFormComponent; | ||||||
|  |   let fixture: ComponentFixture<MitarbeiterFormComponent>; | ||||||
|  | 
 | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     await TestBed.configureTestingModule({ | ||||||
|  |       imports: [MitarbeiterFormComponent] | ||||||
|  |     }) | ||||||
|  |     .compileComponents(); | ||||||
|  | 
 | ||||||
|  |     fixture = TestBed.createComponent(MitarbeiterFormComponent); | ||||||
|  |     component = fixture.componentInstance; | ||||||
|  |     fixture.detectChanges(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('should create', () => { | ||||||
|  |     expect(component).toBeTruthy(); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,133 @@ | ||||||
|  | import { Component, EventEmitter, Input, Output } from '@angular/core'; | ||||||
|  | import { EmployeeNameAndSkillDataDTO, EmployeeRequestPutDTO, EmployeeResponseDTO } from '../../models/mitarbeiter'; | ||||||
|  | import { AbstractControl, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; | ||||||
|  | import { HttpClient } from '@angular/common/http'; | ||||||
|  | import { Router } from '@angular/router'; | ||||||
|  | import { AsyncPipe, NgFor, Location } from '@angular/common'; | ||||||
|  | import { SkillService } from '../../service/skill.service'; | ||||||
|  | import { QualificationGetDTO } from '../../models/skill'; | ||||||
|  | import { Observable, of } from 'rxjs'; | ||||||
|  | 
 | ||||||
|  | @Component({ | ||||||
|  |   selector: 'app-mitarbeiter-form', | ||||||
|  |   standalone: true, | ||||||
|  |   imports: [ReactiveFormsModule, NgFor, AsyncPipe], | ||||||
|  |   templateUrl: './mitarbeiter-form.component.html', | ||||||
|  |   styleUrl: './mitarbeiter-form.component.css' | ||||||
|  | }) | ||||||
|  | export class MitarbeiterFormComponent { | ||||||
|  |   @Input() mitarbeiter!: EmployeeResponseDTO; | ||||||
|  |   @Output() mitarbeiterChange = new EventEmitter<EmployeeResponseDTO>(); | ||||||
|  | 
 | ||||||
|  |   public mitarbeiterForm!: FormGroup; | ||||||
|  |   public allSkills: Observable<Array<QualificationGetDTO>> = of([]); | ||||||
|  |   public hasAllSkills: boolean = false; | ||||||
|  |   errorMessages: Record<string, string> = {}; | ||||||
|  | 
 | ||||||
|  |   constructor(public http: HttpClient, public router: Router, private skillService: SkillService, private location: Location) {} | ||||||
|  | 
 | ||||||
|  |   returnToEmployeeOverview() { | ||||||
|  |     this.location.back(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private setupForm() { | ||||||
|  |     this.mitarbeiterForm = new FormGroup({ | ||||||
|  |       lastName: new FormControl(this.mitarbeiter.lastName, Validators.required), | ||||||
|  |       firstName: new FormControl(this.mitarbeiter.firstName, Validators.required), | ||||||
|  |       street: new FormControl(this.mitarbeiter.street, Validators.required), | ||||||
|  |       postcode: new FormControl(this.mitarbeiter.postcode, [Validators.required, Validators.minLength(5), Validators.maxLength(5)]), | ||||||
|  |       city: new FormControl(this.mitarbeiter.city, Validators.required), | ||||||
|  |       phone: new FormControl(this.mitarbeiter.phone, [Validators.required, Validators.pattern('^[- +()0-9]+$')]), | ||||||
|  |       newSkill: new FormControl(null) | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ngOnChanges(): void { | ||||||
|  |     this.setupForm(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   skillsChanged() { | ||||||
|  |     this.allSkills.subscribe(skills => { | ||||||
|  |       this.hasAllSkills = this.checkAllSkills(skills); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ngOnInit(): void { | ||||||
|  |     this.allSkills = this.skillService.getAllSkills(); | ||||||
|  |     this.skillsChanged(); | ||||||
|  |     this.setupForm(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   removeSkill(id?: number) { | ||||||
|  |     this.mitarbeiter.skillSet = this.mitarbeiter.skillSet?.filter(skill => skill.id !== id); | ||||||
|  |     this.skillsChanged(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   checkAllSkills(skills: Array<QualificationGetDTO>): boolean { | ||||||
|  |     const skillSet = this.mitarbeiter.skillSet || []; | ||||||
|  | 
 | ||||||
|  |     return skills.every(skill => | ||||||
|  |       skillSet.some(givenSkill => skill.id === givenSkill.id) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   hasSkill(id: number): boolean { | ||||||
|  |     for (const skill of this.mitarbeiter.skillSet || []) { | ||||||
|  |       if (skill.id == id) { | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   addSkill() { | ||||||
|  |     const id = Number(this.mitarbeiterForm.get("newSkill")?.value); | ||||||
|  |     if (!id) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     this.allSkills.subscribe(skills => { | ||||||
|  |       const newSkill = skills.filter(skill => skill.id == id)[0]; | ||||||
|  |       this.mitarbeiter.skillSet?.push(newSkill); | ||||||
|  |       this.skillsChanged(); | ||||||
|  |       this.mitarbeiterForm.get("newSkill")?.reset(null); // Added
 | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private validationErrorMessages: Record<string, string> = { | ||||||
|  |     required: "This field is required", | ||||||
|  |     minlength: "The value is too short", | ||||||
|  |     maxlength: "The value is too long", | ||||||
|  |     pattern: "This field must be a valid phone number", | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   updateErrorMessages(): void { | ||||||
|  |     this.errorMessages = {}; | ||||||
|  | 
 | ||||||
|  |     Object.keys(this.mitarbeiterForm.controls).forEach(field => { | ||||||
|  |       const control = this.mitarbeiterForm.get(field); | ||||||
|  | 
 | ||||||
|  |       if (control && control.errors) { | ||||||
|  |         this.errorMessages[field] = Object.keys(control.errors) | ||||||
|  |           .map(errorKey => this.validationErrorMessages[errorKey] || `Unknown error: ${errorKey}`) | ||||||
|  |           .join(' '); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   submit() { | ||||||
|  |     this.updateErrorMessages(); | ||||||
|  |     if (!this.mitarbeiterForm.valid) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this.mitarbeiter.firstName = this.mitarbeiterForm.get("firstName")?.value; | ||||||
|  |     this.mitarbeiter.lastName = this.mitarbeiterForm.get("lastName")?.value; | ||||||
|  |     this.mitarbeiter.street = this.mitarbeiterForm.get("street")?.value; | ||||||
|  |     this.mitarbeiter.postcode = this.mitarbeiterForm.get("postcode")?.value; | ||||||
|  |     this.mitarbeiter.city = this.mitarbeiterForm.get("city")?.value; | ||||||
|  |     this.mitarbeiter.phone = this.mitarbeiterForm.get("phone")?.value; | ||||||
|  | 
 | ||||||
|  |     this.mitarbeiterChange.emit(this.mitarbeiter); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -19,7 +19,7 @@ h1 { | ||||||
| 
 | 
 | ||||||
| .search-bar { | .search-bar { | ||||||
|   display: flex; |   display: flex; | ||||||
|   margin-bottom: 20px; |   margin-bottom: 10px; | ||||||
|   align-items: center; |   align-items: center; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,49 +1,52 @@ | ||||||
| <div class="container"> | <div class="d-flex flex-row"> | ||||||
|   <div class="header"> |   <app-navigation-bar [route]="'employee'" class="row" style="height: 100vh;"></app-navigation-bar> | ||||||
|     <div class="dropdown position-absolute top-0 end-0 m-3"> |   <div class="container"> | ||||||
|       <button |     <div class="header"> | ||||||
|         class="btn align-items-center d-flex" |       <div class="dropdown position-absolute top-0 end-0 m-3"> | ||||||
|         type="button" |         <button class="btn align-items-center d-flex" type="button" id="userDropdown" data-bs-toggle="dropdown" | ||||||
|         id="userDropdown" |           aria-expanded="false"> | ||||||
|         data-bs-toggle="dropdown" |           <img src="user.svg" alt="User Icon" class="rounded-circle" style="width: 30px; height: 30px;"> | ||||||
|         aria-expanded="false" |         </button> | ||||||
|       > |         <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown"> | ||||||
|         <img |           <li><a class="dropdown-item" href="/logout">Log out</a></li> | ||||||
|           src="user.svg" |         </ul> | ||||||
|           alt="User Icon" |       </div> | ||||||
|           class="rounded-circle" |       <h1>Employees</h1> | ||||||
|           style="width: 30px; height: 30px;" |  | ||||||
|         > |  | ||||||
|       </button> |  | ||||||
|       <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown"> |  | ||||||
|         <li> |  | ||||||
|           <a class="dropdown-item" href="/logout">Log out</a> |  | ||||||
|         </li> |  | ||||||
|       </ul> |  | ||||||
|     </div> |     </div> | ||||||
|     <h1>Employees</h1> |  | ||||||
|   </div> |  | ||||||
|   <div class="header-actions"> |  | ||||||
|     <div class="search-bar"> |  | ||||||
|       <input type="text" placeholder="Search employee"> |  | ||||||
|       <button>Search</button> |  | ||||||
|     </div> |  | ||||||
|     <button class="add-button">Add employee</button> |  | ||||||
|   </div> |  | ||||||
| 
 | 
 | ||||||
|   <table class="employee-table"> |     <div class="header-actions"> | ||||||
|     <thead> |       <form [formGroup]="searchForm"> | ||||||
|     <tr> |         <div class="search-bar"> | ||||||
|       <th><span class="sortable">First Name</span></th> |           <input type="text" placeholder="Search employee" formControlName="search"> | ||||||
|       <th><span class="sortable">Last Name</span></th> |           <button (click)="submit()">Search</button> | ||||||
|       <th>Street</th> |         </div> | ||||||
|       <th>Postcode</th> |         <p class="text-body-tertiary">Search for a propertiy of an Employee. eg. First Name</p> | ||||||
|       <th>City</th> |       </form> | ||||||
|       <th>Phone</th> |       <button (click)="createEmployee()" class="add-button">Add employee</button> | ||||||
|       <th>Actions</th> |     </div> | ||||||
|     </tr> | 
 | ||||||
|     </thead> |     <table class="employee-table"> | ||||||
|     <tbody> |       <thead> | ||||||
|     </tbody> |         <tr> | ||||||
|   </table> |           <th><span class="sortable">First Name</span></th> | ||||||
|  |           <th><span class="sortable">Last Name</span></th> | ||||||
|  |           <th>Actions</th> | ||||||
|  |         </tr> | ||||||
|  |       </thead> | ||||||
|  |       <tbody> | ||||||
|  |         @if (employees) { | ||||||
|  |         @for (employee of employees; track employee) { | ||||||
|  |         <tr> | ||||||
|  |           <td>{{ employee.firstName }}</td> | ||||||
|  |           <td>{{ employee.lastName }}</td> | ||||||
|  |           <td> | ||||||
|  |           <button class="btn btn-primary me-2" (click)="goToEmployeeDetailPage(employee.id)">Details</button> | ||||||
|  |             <button class="btn btn-danger" (click)="deleteEmployee(employee.id)">Delete</button> | ||||||
|  |           </td> | ||||||
|  |         </tr> | ||||||
|  |         } | ||||||
|  |         } | ||||||
|  |       </tbody> | ||||||
|  |     </table> | ||||||
|  |   </div> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  | @ -1,12 +1,75 @@ | ||||||
| import { Component } from '@angular/core'; | import { Component, OnInit } from '@angular/core'; | ||||||
|  | import { CommonModule } from '@angular/common'; | ||||||
|  | import { Observable, of } from 'rxjs'; | ||||||
|  | import { EmployeeResponseDTO } from '../../models/mitarbeiter'; | ||||||
|  | import { EmployeeService } from '../../service/employee.service'; | ||||||
|  | import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; | ||||||
|  | import { Router } from '@angular/router'; | ||||||
|  | import { NavigationBarComponent } from '../navigation-bar/navigation-bar.component'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-mitarbeiterverwaltung-view', |   selector: 'app-mitarbeiterverwaltung-view', | ||||||
|   standalone: true, |  | ||||||
|   imports: [], |  | ||||||
|   templateUrl: './mitarbeiterverwaltung-view.component.html', |   templateUrl: './mitarbeiterverwaltung-view.component.html', | ||||||
|   styleUrl: './mitarbeiterverwaltung-view.component.css' |   styleUrls: ['./mitarbeiterverwaltung-view.component.css'], | ||||||
|  |   standalone: true, | ||||||
|  |   imports: [CommonModule, ReactiveFormsModule, NavigationBarComponent] | ||||||
| }) | }) | ||||||
| export class MitarbeiterverwaltungViewComponent { | export class MitarbeiterverwaltungViewComponent implements OnInit { | ||||||
|  |   employees: Array<EmployeeResponseDTO> = []; | ||||||
|  |   searchForm!: FormGroup; | ||||||
| 
 | 
 | ||||||
|  |   constructor(private employeeService: EmployeeService, private router: Router) { | ||||||
|  |     this.updateEmployees(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   submit() { | ||||||
|  |     const searchTerm = this.searchForm.get("search")?.value || ''; | ||||||
|  | 
 | ||||||
|  |     this.employeeService.getAllEmployees().subscribe(employees => { | ||||||
|  |       let foundEmployees: Array<EmployeeResponseDTO> = []; | ||||||
|  |       for (const employee of employees) { | ||||||
|  |         if ( | ||||||
|  |           employee.firstName.toLowerCase().includes(searchTerm.toLowerCase()) || | ||||||
|  |           employee.lastName.toLowerCase().includes(searchTerm.toLowerCase()) || | ||||||
|  |           employee.street.toLowerCase().includes(searchTerm.toLowerCase()) || | ||||||
|  |           employee.postcode.toLowerCase().includes(searchTerm.toLowerCase()) || | ||||||
|  |           employee.city.toLowerCase().includes(searchTerm.toLowerCase()) || | ||||||
|  |           employee.phone.toLowerCase().includes(searchTerm.toLowerCase()) || | ||||||
|  |           searchTerm == '' | ||||||
|  |         ) { | ||||||
|  |           foundEmployees.push(employee); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       this.employees = foundEmployees; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   editEmployee(id: number) { | ||||||
|  |     this.router.navigate([`/mitarbeiterbearbeiten/${id}`]); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   createEmployee() { | ||||||
|  |     this.router.navigate(['/mitarbeitererstellen']); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   deleteEmployee(id: number) { | ||||||
|  |     this.employeeService.deleteEmployee(id); | ||||||
|  |     this.employees = this.employees.filter(employee => employee.id != id); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   goToEmployeeDetailPage(id: number){ | ||||||
|  |     this.router.navigate([`/mitarbeiterdetails/${id}`]); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   updateEmployees() { | ||||||
|  |     this.employeeService.getAllEmployees().subscribe(employees => this.employees = employees); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ngOnInit(): void { | ||||||
|  |     this.searchForm = new FormGroup({ | ||||||
|  |       search: new FormControl(''), | ||||||
|  |     }); | ||||||
|  |     this.updateEmployees(); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -5,18 +5,13 @@ | ||||||
|     </a> |     </a> | ||||||
|     <hr> |     <hr> | ||||||
|     <ul class="nav nav-pills flex-column mb-auto"> |     <ul class="nav nav-pills flex-column mb-auto"> | ||||||
|       <li class="nav-item"> |  | ||||||
|         <a href="#" class="nav-link active text-black" aria-current="page"> |  | ||||||
|           Dashboard |  | ||||||
|         </a> |  | ||||||
|       </li> |  | ||||||
|       <li> |       <li> | ||||||
|         <a href="#" class="nav-link text-black"> |         <a [routerLink]="['/mitarbeiter']" (click)="employees()" [class.active]="route == 'employee'" class="nav-link text-black" aria-current="page"> | ||||||
|           Mitarbeiterverwaltung |           Mitarbeiterverwaltung | ||||||
|         </a> |         </a> | ||||||
|       </li> |       </li> | ||||||
|       <li> |       <li> | ||||||
|         <a href="#" class="nav-link text-black"> |         <a [routerLink]="['/qualifikationsverwaltung']" [class.active]="route == 'skill'" class="nav-link text-black"> | ||||||
|           Qualifikationsverwaltung |           Qualifikationsverwaltung | ||||||
|         </a> |         </a> | ||||||
|       </li> |       </li> | ||||||
|  |  | ||||||
|  | @ -1,12 +1,18 @@ | ||||||
| import { Component } from '@angular/core'; | import { Component, Input } from '@angular/core'; | ||||||
|  | import { ActivatedRoute, Router, RouterLink } from '@angular/router'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-navigation-bar', |   selector: 'app-navigation-bar', | ||||||
|   standalone: true, |   standalone: true, | ||||||
|   imports: [], |   imports: [RouterLink], | ||||||
|   templateUrl: './navigation-bar.component.html', |   templateUrl: './navigation-bar.component.html', | ||||||
|   styleUrl: './navigation-bar.component.css' |   styleUrl: './navigation-bar.component.css' | ||||||
| }) | }) | ||||||
| export class NavigationBarComponent { | export class NavigationBarComponent { | ||||||
|  |   constructor(private router: Router) { } | ||||||
|  |   @Input() route: string = ""; | ||||||
| 
 | 
 | ||||||
|  |   employees() { | ||||||
|  |     this.router.navigate(['mitarbeiter']); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | <app-qualifikation-form [(skill)]="skill" (skillChange)="submitted($event)"></app-qualifikation-form> | ||||||
|  | @ -0,0 +1,23 @@ | ||||||
|  | import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||||
|  | 
 | ||||||
|  | import { QulifikationErstellenComponent } from './qulifikation-erstellen.component'; | ||||||
|  | 
 | ||||||
|  | describe('QulifikationErstellenComponent', () => { | ||||||
|  |   let component: QulifikationErstellenComponent; | ||||||
|  |   let fixture: ComponentFixture<QulifikationErstellenComponent>; | ||||||
|  | 
 | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     await TestBed.configureTestingModule({ | ||||||
|  |       imports: [QulifikationErstellenComponent] | ||||||
|  |     }) | ||||||
|  |     .compileComponents(); | ||||||
|  | 
 | ||||||
|  |     fixture = TestBed.createComponent(QulifikationErstellenComponent); | ||||||
|  |     component = fixture.componentInstance; | ||||||
|  |     fixture.detectChanges(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('should create', () => { | ||||||
|  |     expect(component).toBeTruthy(); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,29 @@ | ||||||
|  | import { Component } from '@angular/core'; | ||||||
|  | import { QualifikationFormComponent } from '../qualifikation-form/qualifikation-form.component'; | ||||||
|  | import { QualificationGetDTO } from '../../models/skill'; | ||||||
|  | import { SkillService } from '../../service/skill.service'; | ||||||
|  | 
 | ||||||
|  | @Component({ | ||||||
|  |   selector: 'app-qulifikation-erstellen', | ||||||
|  |   standalone: true, | ||||||
|  |   imports: [QualifikationFormComponent], | ||||||
|  |   templateUrl: './qualifikation-erstellen.component.html', | ||||||
|  |   styleUrl: './qualifikation-erstellen.component.css' | ||||||
|  | }) | ||||||
|  | export class QulifikationErstellenComponent { | ||||||
|  |   public skill!: QualificationGetDTO; | ||||||
|  | 
 | ||||||
|  |   constructor(private skillService: SkillService) { } | ||||||
|  | 
 | ||||||
|  |   submitted(skill: QualificationGetDTO) { | ||||||
|  |     console.log(skill); | ||||||
|  |     this.skillService.createSkill(skill); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ngOnInit(): void { | ||||||
|  |     this.skill = { | ||||||
|  |       id: 0, | ||||||
|  |       skill: '', | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,131 @@ | ||||||
|  | body { | ||||||
|  |   font-family: sans-serif; | ||||||
|  |   margin: 0; | ||||||
|  |   padding: 20px; | ||||||
|  |   background-color: #f0f0f0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .container { | ||||||
|  |   width: 100%; | ||||||
|  |   max-width: 800px; | ||||||
|  |   margin: 0 auto; | ||||||
|  |   padding: 20px; | ||||||
|  |   background-color: #fff; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h1, h2 { | ||||||
|  |   font-size: 1.8rem; | ||||||
|  |   margin-bottom: 15px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .back-button { | ||||||
|  |   background-color: #ccc; | ||||||
|  |   color: #333; | ||||||
|  |   border: none; | ||||||
|  |   padding: 8px 12px; | ||||||
|  |   border-radius: 3px; | ||||||
|  |   margin-bottom: 15px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .form-group { | ||||||
|  |   margin-bottom: 15px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label { | ||||||
|  |   display: block; | ||||||
|  |   margin-bottom: 5px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input[type="text"] { | ||||||
|  |   width: 100%; | ||||||
|  |   padding: 10px; | ||||||
|  |   border: 1px solid #ccc; | ||||||
|  |   border-radius: 1px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .employee-list { | ||||||
|  |   list-style: none; | ||||||
|  |   padding: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .employee-list li { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   margin-bottom: 5px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .employee-icon { | ||||||
|  |   margin-right: 5px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .add-employee-section { | ||||||
|  |   margin-top: 15px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .add-employee-section select { | ||||||
|  |   padding: 10px; | ||||||
|  |   border: 1px solid #ddd; | ||||||
|  |   border-radius: 3px; | ||||||
|  |   margin-right: 10px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .add-employee-button { | ||||||
|  |   background-color: #06a63b; | ||||||
|  |   color: #fff; | ||||||
|  |   padding: 10px 15px; | ||||||
|  |   border: none; | ||||||
|  |   border-radius: 3px; | ||||||
|  |   cursor: pointer; | ||||||
|  |   margin-top: 10px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .save-button { | ||||||
|  |   background-color: #007bff; | ||||||
|  |   color: #fff; | ||||||
|  |   padding: 10px 15px; | ||||||
|  |   border: none; | ||||||
|  |   border-radius: 3px; | ||||||
|  |   cursor: pointer; | ||||||
|  |   margin-top: 20px; | ||||||
|  |   display: block; | ||||||
|  |   width: 100%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .delete-skill-button { | ||||||
|  |   color: #fff; | ||||||
|  |   padding: 5px 8px; | ||||||
|  |   border: none; | ||||||
|  |   cursor: pointer; | ||||||
|  |   margin-left: 4px; | ||||||
|  |   margin-right: 4px; | ||||||
|  |   border-radius: 3px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .delete-skill-button img { | ||||||
|  |   width: 15px; | ||||||
|  |   height: 15px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .add-employee-section { | ||||||
|  |   margin-top: 15px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .add-employee-section label { | ||||||
|  |   display: block; | ||||||
|  |   margin-bottom: 5px; | ||||||
|  |   font-weight: bold; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .add-employee-section input[type="text"] { | ||||||
|  |   width: 100%; | ||||||
|  |   padding: 10px; | ||||||
|  |   border: 1px solid #ddd; | ||||||
|  |   border-radius: 3px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .employee-container { | ||||||
|  |   border: 1px solid #ccc; | ||||||
|  |   padding: 20px; | ||||||
|  |   border-radius: 1px; | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,41 @@ | ||||||
|  | <div class="container"> | ||||||
|  |   <form [formGroup]="skillForm"> | ||||||
|  |     <button (click)="returnToSkillList()" class="back-button">Back</button> | ||||||
|  | 
 | ||||||
|  |     <div class="form-group"> | ||||||
|  |       @if (errorMessages['name']) { | ||||||
|  |       <div class="alert alert-danger">{{errorMessages['name']}}</div> | ||||||
|  |       } | ||||||
|  |       <label for="name">Name</label> | ||||||
|  |       <input type="text" id="name" formControlName="name"> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="employee-container"> | ||||||
|  |       <h2>Employees possessing the qualification</h2> | ||||||
|  | 
 | ||||||
|  |       <ul class="employee-list"> | ||||||
|  |         @for (employee of addedEmployees; track employee) { | ||||||
|  |         <li> | ||||||
|  |           <button (click)="removeEmployee(employee.id)" class="delete-skill-button"> | ||||||
|  |             <img src="Delete-button.svg" alt="Delete"> | ||||||
|  |           </button> | ||||||
|  |           <span class="employee-name">{{employee.firstName}} {{employee.lastName}}</span> | ||||||
|  |         </li> | ||||||
|  |         } | ||||||
|  |       </ul> | ||||||
|  | 
 | ||||||
|  |       @if (addableEmployees.length > 0) { | ||||||
|  |       <div class="add-employee-section"> | ||||||
|  |         <select formControlName="newEmployee"> | ||||||
|  |           @for (employee of addableEmployees; track employee) { | ||||||
|  |           <option value="{{employee.id}}">{{employee.firstName}} {{employee.lastName}}</option> | ||||||
|  |           } | ||||||
|  |         </select> | ||||||
|  | 
 | ||||||
|  |         <button (click)="addEmployee()" class="add-employee-button">Add employee</button> | ||||||
|  |       </div> | ||||||
|  |       } | ||||||
|  |     </div> | ||||||
|  |     <button (click)="submit()" class="save-button">Save</button> | ||||||
|  |   </form> | ||||||
|  | </div> | ||||||
|  | @ -0,0 +1,23 @@ | ||||||
|  | import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||||
|  | 
 | ||||||
|  | import { QualifikationFormComponent } from './qualifikation-form.component'; | ||||||
|  | 
 | ||||||
|  | describe('QualifikationFormComponent', () => { | ||||||
|  |   let component: QualifikationFormComponent; | ||||||
|  |   let fixture: ComponentFixture<QualifikationFormComponent>; | ||||||
|  | 
 | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     await TestBed.configureTestingModule({ | ||||||
|  |       imports: [QualifikationFormComponent] | ||||||
|  |     }) | ||||||
|  |     .compileComponents(); | ||||||
|  | 
 | ||||||
|  |     fixture = TestBed.createComponent(QualifikationFormComponent); | ||||||
|  |     component = fixture.componentInstance; | ||||||
|  |     fixture.detectChanges(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('should create', () => { | ||||||
|  |     expect(component).toBeTruthy(); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,133 @@ | ||||||
|  | import { Component, EventEmitter, Input, Output } from '@angular/core'; | ||||||
|  | import { EmployeesForAQualificationDTO, QualificationGetDTO } from '../../models/skill'; | ||||||
|  | import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; | ||||||
|  | import { SkillService } from '../../service/skill.service'; | ||||||
|  | import { EmployeeService } from '../../service/employee.service'; | ||||||
|  | import { concatMap, every, from, lastValueFrom, Observable, of, switchMap, take, tap } from 'rxjs'; | ||||||
|  | import { EmployeeNameDataDTO, EmployeeResponseDTO } from '../../models/mitarbeiter'; | ||||||
|  | import { AsyncPipe } from '@angular/common'; | ||||||
|  | import { Router } from '@angular/router'; | ||||||
|  | 
 | ||||||
|  | @Component({ | ||||||
|  |   selector: 'app-qualifikation-form', | ||||||
|  |   standalone: true, | ||||||
|  |   imports: [ReactiveFormsModule, AsyncPipe], | ||||||
|  |   templateUrl: './qualifikation-form.component.html', | ||||||
|  |   styleUrl: './qualifikation-form.component.css' | ||||||
|  | }) | ||||||
|  | export class QualifikationFormComponent { | ||||||
|  |   @Input() skill!: QualificationGetDTO; | ||||||
|  |   @Output() skillChange = new EventEmitter<QualificationGetDTO>(); | ||||||
|  | 
 | ||||||
|  |   public skillForm!: FormGroup; | ||||||
|  |   public addableEmployees: Array<EmployeeResponseDTO> = []; | ||||||
|  |   public addedEmployees: Array<EmployeeNameDataDTO> = []; | ||||||
|  |   errorMessages: Record<string, string> = {}; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   constructor(private skillService: SkillService, private employeeService: EmployeeService, private router: Router) { } | ||||||
|  | 
 | ||||||
|  |   private validationErrorMessages: Record<string, string> = { | ||||||
|  |     required: "This field is required", | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   updateErrorMessages(): void { | ||||||
|  |     this.errorMessages = {}; | ||||||
|  | 
 | ||||||
|  |     Object.keys(this.skillForm.controls).forEach(field => { | ||||||
|  |       const control = this.skillForm.get(field); | ||||||
|  | 
 | ||||||
|  |       if (control && control.errors) { | ||||||
|  |         this.errorMessages[field] = Object.keys(control.errors) | ||||||
|  |           .map(errorKey => this.validationErrorMessages[errorKey] || `Unknown error: ${errorKey}`) | ||||||
|  |           .join(' '); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setUpForm() { | ||||||
|  |     this.skillForm = new FormGroup({ | ||||||
|  |       name: new FormControl(this.skill.skill, Validators.required), | ||||||
|  |       newEmployee: new FormControl(), | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   addEmployee() { | ||||||
|  |     const employeeId = Number(this.skillForm.get("newEmployee")?.value); | ||||||
|  |     if (!employeeId) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     const employee = this.addableEmployees.find(emp => emp.id === employeeId); | ||||||
|  | 
 | ||||||
|  |     if (employee) { | ||||||
|  |       this.addableEmployees = this.addableEmployees.filter(emp => emp.id !== employeeId); | ||||||
|  | 
 | ||||||
|  |       this.addedEmployees.push(employee); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   removeEmployee(employeeId: number) { | ||||||
|  |     const employee = this.addedEmployees.find(emp => emp.id === employeeId); | ||||||
|  | 
 | ||||||
|  |     if (employee) { | ||||||
|  |       this.addedEmployees = this.addedEmployees.filter(emp => emp.id !== employeeId); | ||||||
|  | 
 | ||||||
|  |       this.employeeService.getEmployeeById(employee.id).subscribe(employeeDto => { | ||||||
|  |         this.addableEmployees.push(employeeDto); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   returnToSkillList() { | ||||||
|  |     this.router.navigate(["qualifikationsverwaltung"]); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   updateEmployeeLists() { | ||||||
|  |     if (this.skill.id != -1) { | ||||||
|  |       this.employeeService.getAllEmployees().subscribe(employees => { | ||||||
|  |         this.addableEmployees = employees; | ||||||
|  | 
 | ||||||
|  |         this.skillService.getEmployeesBySkill(this.skill.id).subscribe(addedEmployeesResponse => { | ||||||
|  |           this.addedEmployees = addedEmployeesResponse.employees; | ||||||
|  | 
 | ||||||
|  |           this.addableEmployees = this.addableEmployees.filter(employee => { | ||||||
|  |             return !this.addedEmployees.some(added => added.id === employee.id); | ||||||
|  |           }); | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   submit() { | ||||||
|  |     this.updateErrorMessages(); | ||||||
|  |     if (!this.skillForm.valid) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for (const employee of this.addedEmployees) { | ||||||
|  |       this.employeeService.getEmployeeById(employee.id).subscribe(employeeResponse => { | ||||||
|  |         this.employeeService.addSkillToEmployee(this.skill.id, employeeResponse); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for (const employee of this.addableEmployees) { | ||||||
|  |       this.employeeService.removeSkillFromEmployee(this.skill.id, employee.id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this.skill.skill = this.skillForm.get("name")?.value; | ||||||
|  |     this.skillChange.emit(this.skill); | ||||||
|  | 
 | ||||||
|  |     this.router.navigate(["/qualifikationsverwaltung"]); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ngOnChanges(): void { | ||||||
|  |     this.setUpForm(); | ||||||
|  |     this.updateEmployeeLists(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ngOnInit() { | ||||||
|  |     this.setUpForm(); | ||||||
|  |     this.updateEmployeeLists(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,90 @@ | ||||||
|  | body { | ||||||
|  |   font-family: sans-serif; | ||||||
|  |   margin: 0; | ||||||
|  |   padding: 20px; | ||||||
|  |   background-color: #f0f0f0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .container { | ||||||
|  |   width: 100%; | ||||||
|  |   margin: 0 auto; | ||||||
|  |   background-color: #fff; | ||||||
|  |   padding: 20px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h1 { | ||||||
|  |   font-size: 2rem; | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .search-bar { | ||||||
|  |   display: flex; | ||||||
|  |   margin-bottom: 10px; | ||||||
|  |   align-items: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .search-bar input[type="text"] { | ||||||
|  |   padding: 8px; | ||||||
|  |   border: 1px solid #ccc; | ||||||
|  |   border-radius: 3px; | ||||||
|  |   flex-grow: 1; | ||||||
|  |   margin-right: 10px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .search-bar button { | ||||||
|  |   padding: 10px 15px; | ||||||
|  |   background-color: #007bff; | ||||||
|  |   color: #fff; | ||||||
|  |   border: none; | ||||||
|  |   border-radius: 3px; | ||||||
|  |   cursor: pointer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .add-button { | ||||||
|  |   background-color: #07af16; | ||||||
|  |   color: #fff; | ||||||
|  |   padding: 10px 15px; | ||||||
|  |   border: none; | ||||||
|  |   border-radius: 3px; | ||||||
|  |   cursor: pointer; | ||||||
|  |   float: left; | ||||||
|  |   margin-bottom: 10px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .employee-table { | ||||||
|  |   width: 100%; | ||||||
|  |   border-collapse: collapse; | ||||||
|  |   margin-top: 20px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .employee-table th, | ||||||
|  | .employee-table td { | ||||||
|  |   padding: 12px 15px; | ||||||
|  |   border: 1px solid #ddd; | ||||||
|  |   text-align: left; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .employee-table th { | ||||||
|  |   background-color: #f0f0f0; | ||||||
|  |   font-weight: bold; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .sortable { | ||||||
|  |   cursor: pointer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .logout-button { | ||||||
|  |   background-color: transparent; | ||||||
|  |   border: none; | ||||||
|  |   cursor: pointer; | ||||||
|  |   border-radius: 50%; | ||||||
|  |   width: 30px; | ||||||
|  |   height: 30px; | ||||||
|  |   display: flex; | ||||||
|  |   float: right; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .logout-button img { | ||||||
|  |   width: 20px; | ||||||
|  |   height: 20px; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,60 @@ | ||||||
|  | <div class="d-flex flex-row"> | ||||||
|  |   <app-navigation-bar [route]="'skill'" class="row" style="height: 100vh;"></app-navigation-bar> | ||||||
|  |   <div class="container"> | ||||||
|  |     <div class="header"> | ||||||
|  |       <div class="dropdown position-absolute top-0 end-0 m-3"> | ||||||
|  |         <button class="btn align-items-center d-flex" type="button" id="userDropdown" data-bs-toggle="dropdown" | ||||||
|  |           aria-expanded="false"> | ||||||
|  |           <img src="user.svg" alt="User Icon" class="rounded-circle" style="width: 30px; height: 30px;"> | ||||||
|  |         </button> | ||||||
|  |         <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown"> | ||||||
|  |           <li><a class="dropdown-item" href="/logout">Log out</a></li> | ||||||
|  |         </ul> | ||||||
|  |       </div> | ||||||
|  |       <h1>Qualifications</h1> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="d-flex flex-column"> | ||||||
|  |       <div class="row header-actions"> | ||||||
|  |         <form [formGroup]="searchForm"> | ||||||
|  |           <div class="search-bar"> | ||||||
|  |             <input type="text" placeholder="Search employee" formControlName="search"> | ||||||
|  |             <button (click)="submit()">Search</button> | ||||||
|  |           </div> | ||||||
|  |           <p class="text-body-tertiary">Search for a propertiy of a Qualification. eg. Name</p> | ||||||
|  |         </form> | ||||||
|  |         <div class="d-flex"> | ||||||
|  |           <button (click)="createSkill()" class="add-button me-auto">Add qualification</button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |       @if (errorDeletingSkill) { | ||||||
|  |       <div class="row"> | ||||||
|  |         <p class="alert alert-danger">This Qualification can not be deleted because it still has employees.</p> | ||||||
|  |       </div> | ||||||
|  |       } | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <table class="employee-table"> | ||||||
|  |       <thead> | ||||||
|  |         <tr> | ||||||
|  |           <th><span class="sortable">Name</span></th> | ||||||
|  |           <th>Actions</th> | ||||||
|  |         </tr> | ||||||
|  |       </thead> | ||||||
|  |       <tbody> | ||||||
|  |         @if (skills) { | ||||||
|  |         @for (skill of skills; track skill) { | ||||||
|  |         <tr> | ||||||
|  |           <td>{{ skill.skill }}</td> | ||||||
|  |           <td> | ||||||
|  |             <button class="btn btn-primary me-2" (click)="editSkill(skill.id)">Edit</button> | ||||||
|  |             <button class="btn btn-danger" (click)="deleteSkill(skill.id)">Delete</button> | ||||||
|  |           </td> | ||||||
|  |         </tr> | ||||||
|  |         } | ||||||
|  |         } | ||||||
|  |       </tbody> | ||||||
|  |     </table> | ||||||
|  |   </div> | ||||||
|  | </div> | ||||||
|  | @ -0,0 +1,23 @@ | ||||||
|  | import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||||
|  | 
 | ||||||
|  | import { QualifikationsverwaltungComponent } from './qualifikationsverwaltung.component'; | ||||||
|  | 
 | ||||||
|  | describe('QualifikationsverwaltungComponent', () => { | ||||||
|  |   let component: QualifikationsverwaltungComponent; | ||||||
|  |   let fixture: ComponentFixture<QualifikationsverwaltungComponent>; | ||||||
|  | 
 | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     await TestBed.configureTestingModule({ | ||||||
|  |       imports: [QualifikationsverwaltungComponent] | ||||||
|  |     }) | ||||||
|  |     .compileComponents(); | ||||||
|  | 
 | ||||||
|  |     fixture = TestBed.createComponent(QualifikationsverwaltungComponent); | ||||||
|  |     component = fixture.componentInstance; | ||||||
|  |     fixture.detectChanges(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('should create', () => { | ||||||
|  |     expect(component).toBeTruthy(); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,66 @@ | ||||||
|  | import { Component } from '@angular/core'; | ||||||
|  | import { QualificationGetDTO } from '../../models/skill'; | ||||||
|  | import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; | ||||||
|  | import { SkillService } from '../../service/skill.service'; | ||||||
|  | import { Router } from '@angular/router'; | ||||||
|  | import { NavigationBarComponent } from '../navigation-bar/navigation-bar.component'; | ||||||
|  | 
 | ||||||
|  | @Component({ | ||||||
|  |   selector: 'app-qualifikationsverwaltung', | ||||||
|  |   standalone: true, | ||||||
|  |   imports: [ReactiveFormsModule, NavigationBarComponent], | ||||||
|  |   templateUrl: './qualifikationsverwaltung.component.html', | ||||||
|  |   styleUrl: './qualifikationsverwaltung.component.css' | ||||||
|  | }) | ||||||
|  | export class QualifikationsverwaltungComponent { | ||||||
|  |   skills: Array<QualificationGetDTO> = []; | ||||||
|  |   public searchForm!: FormGroup; | ||||||
|  |   public errorDeletingSkill = false; | ||||||
|  | 
 | ||||||
|  |   constructor(private skillService: SkillService, private router: Router) { } | ||||||
|  | 
 | ||||||
|  |   submit() { | ||||||
|  |     const searchTerm = this.searchForm.get("search")?.value || ''; | ||||||
|  | 
 | ||||||
|  |     this.skillService.getAllSkills().subscribe(skills => { | ||||||
|  |       let foundSkills: Array<QualificationGetDTO> = []; | ||||||
|  |       for (const skill of skills) { | ||||||
|  |         if ( | ||||||
|  |           skill.skill.toLowerCase().includes(searchTerm.toLowerCase()) || | ||||||
|  |           searchTerm == '' | ||||||
|  |         ) { | ||||||
|  |           foundSkills.push(skill); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       this.skills = foundSkills; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   editSkill(id: number) { | ||||||
|  |     this.router.navigate([`/qualifikationbearbeiten/${id}`]); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   createSkill() { | ||||||
|  |     this.router.navigate(['/qualifikationerstellen']); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   deleteSkill(id: number) { | ||||||
|  |     this.skillService.getEmployeesBySkill(id).subscribe(employees => { | ||||||
|  |       this.errorDeletingSkill = false; | ||||||
|  |       if (employees.employees.length == 0) { | ||||||
|  |         this.skillService.deleteSkill(id); | ||||||
|  |         this.skills = this.skills.filter(skill => skill.id != id); | ||||||
|  |       } else { | ||||||
|  |         this.errorDeletingSkill = true; | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ngOnInit(): void { | ||||||
|  |     this.searchForm = new FormGroup({ | ||||||
|  |       search: new FormControl(''), | ||||||
|  |     }); | ||||||
|  |     this.skillService.getAllSkills().subscribe(skills => this.skills = skills); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -1,131 +0,0 @@ | ||||||
| body { |  | ||||||
|   font-family: sans-serif; |  | ||||||
|   margin: 0; |  | ||||||
|   padding: 20px; |  | ||||||
|   background-color: #f0f0f0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .container { |  | ||||||
|   width: 100%; |  | ||||||
|   max-width: 800px; |  | ||||||
|   margin: 0 auto; |  | ||||||
|   padding: 20px; |  | ||||||
|   background-color: #fff; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| h1, h2 { |  | ||||||
|   font-size: 1.8rem; |  | ||||||
|   margin-bottom: 15px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .back-button { |  | ||||||
|   background-color: #ccc; |  | ||||||
|   color: #333; |  | ||||||
|   border: none; |  | ||||||
|   padding: 8px 12px; |  | ||||||
|   border-radius: 3px; |  | ||||||
|   margin-bottom: 15px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .form-group { |  | ||||||
|   margin-bottom: 15px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| label { |  | ||||||
|   display: block; |  | ||||||
|   margin-bottom: 5px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| input[type="text"] { |  | ||||||
|   width: 100%; |  | ||||||
|   padding: 10px; |  | ||||||
|   border: 1px solid #ccc; |  | ||||||
|   border-radius: 1px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .employee-list { |  | ||||||
|   list-style: none; |  | ||||||
|   padding: 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .employee-list li { |  | ||||||
|   display: flex; |  | ||||||
|   align-items: center; |  | ||||||
|   margin-bottom: 5px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .employee-icon { |  | ||||||
|   margin-right: 5px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .add-employee-section { |  | ||||||
|   margin-top: 15px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .add-employee-section select { |  | ||||||
|   padding: 10px; |  | ||||||
|   border: 1px solid #ddd; |  | ||||||
|   border-radius: 3px; |  | ||||||
|   margin-right: 10px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .add-employee-button { |  | ||||||
|   background-color: #06a63b; |  | ||||||
|   color: #fff; |  | ||||||
|   padding: 10px 15px; |  | ||||||
|   border: none; |  | ||||||
|   border-radius: 3px; |  | ||||||
|   cursor: pointer; |  | ||||||
|   margin-top: 10px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .save-button { |  | ||||||
|   background-color: #007bff; |  | ||||||
|   color: #fff; |  | ||||||
|   padding: 10px 15px; |  | ||||||
|   border: none; |  | ||||||
|   border-radius: 3px; |  | ||||||
|   cursor: pointer; |  | ||||||
|   margin-top: 20px; |  | ||||||
|   display: block; |  | ||||||
|   width: 100%; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .delete-skill-button { |  | ||||||
|   color: #fff; |  | ||||||
|   padding: 5px 8px; |  | ||||||
|   border: none; |  | ||||||
|   cursor: pointer; |  | ||||||
|   margin-left: 4px; |  | ||||||
|   margin-right: 4px; |  | ||||||
|   border-radius: 3px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .delete-skill-button img { |  | ||||||
|   width: 15px; |  | ||||||
|   height: 15px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .add-employee-section { |  | ||||||
|   margin-top: 15px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .add-employee-section label { |  | ||||||
|   display: block; |  | ||||||
|   margin-bottom: 5px; |  | ||||||
|   font-weight: bold; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .add-employee-section input[type="text"] { |  | ||||||
|   width: 100%; |  | ||||||
|   padding: 10px; |  | ||||||
|   border: 1px solid #ddd; |  | ||||||
|   border-radius: 3px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .employee-container { |  | ||||||
|   border: 1px solid #ccc; |  | ||||||
|   padding: 20px; |  | ||||||
|   border-radius: 1px; |  | ||||||
|   margin-bottom: 20px; |  | ||||||
| } |  | ||||||
|  | @ -1,36 +1 @@ | ||||||
| <div class="container"> | <app-qualifikation-form [(skill)]="skill" (skillChange)="submitted($event)"></app-qualifikation-form> | ||||||
|   <button class="back-button">Back</button> |  | ||||||
| 
 |  | ||||||
|   <div class="form-group"> |  | ||||||
|     <label for="name">Name</label> |  | ||||||
|     <input type="text" id="name" value="(Hier kommt name der gewählten qualification hin)"> |  | ||||||
|   </div> |  | ||||||
| 
 |  | ||||||
|   <div class="employee-container"> |  | ||||||
|     <h2>Employees possessing the qualification</h2> |  | ||||||
| 
 |  | ||||||
|     <ul class="employee-list"> |  | ||||||
|       <li> |  | ||||||
|         <button class="delete-skill-button"> |  | ||||||
|           <img src="Delete-button.svg" alt="Delete"> |  | ||||||
|         </button> |  | ||||||
|         <span class="employee-name">Max Mustermann</span> |  | ||||||
|       </li> |  | ||||||
|       <li> |  | ||||||
|         <button class="delete-skill-button"> |  | ||||||
|           <img src="Delete-button.svg" alt="Delete"> |  | ||||||
|         </button> |  | ||||||
|         <span class="employee-name">Mehdi Boudjoudi</span> |  | ||||||
|       </li> |  | ||||||
|     </ul> |  | ||||||
| 
 |  | ||||||
|     <div class="add-employee-section"> |  | ||||||
|       <label for="employeeSearch">Search for employee</label> |  | ||||||
|       <input type="text" id="employeeSearch" placeholder="Last name of employee"> |  | ||||||
|       <button class="add-employee-button">Add employee</button> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| 
 |  | ||||||
|   <button class="save-button">Save</button> |  | ||||||
| </div> |  | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -1,12 +1,38 @@ | ||||||
| import { Component } from '@angular/core'; | import { Component } from '@angular/core'; | ||||||
|  | import { QualifikationFormComponent } from '../qualifikation-form/qualifikation-form.component'; | ||||||
|  | import { SkillService } from '../../service/skill.service'; | ||||||
|  | import { FormGroup } from '@angular/forms'; | ||||||
|  | import { QualificationGetDTO } from '../../models/skill'; | ||||||
|  | import { ActivatedRoute } from '@angular/router'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-qualifikaton-bearbeiten-view', |   selector: 'app-qualifikaton-bearbeiten-view', | ||||||
|   standalone: true, |   standalone: true, | ||||||
|   imports: [], |   imports: [QualifikationFormComponent], | ||||||
|   templateUrl: './qualifikaton-bearbeiten-view.component.html', |   templateUrl: './qualifikaton-bearbeiten-view.component.html', | ||||||
|   styleUrl: './qualifikaton-bearbeiten-view.component.css' |   styleUrl: './qualifikaton-bearbeiten-view.component.css' | ||||||
| }) | }) | ||||||
| export class QualifikatonBearbeitenViewComponent { | export class QualifikatonBearbeitenViewComponent { | ||||||
|  |   public skill!: QualificationGetDTO; | ||||||
| 
 | 
 | ||||||
|  |   constructor(private skillService: SkillService, private route: ActivatedRoute) { } | ||||||
|  | 
 | ||||||
|  |   submitted(skill: QualificationGetDTO) { | ||||||
|  |     this.skillService.updateSkill(skill); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ngOnInit(): void { | ||||||
|  |     this.skill = { | ||||||
|  |       id: -1, | ||||||
|  |       skill: '', | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     this.skillService.getAllSkills().subscribe(skills => { | ||||||
|  |       for (const skill of skills) { | ||||||
|  |         if (skill.id == Number(this.route.snapshot.params['id'])) { | ||||||
|  |           this.skill = skill; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								src/app/environments/environment.ci.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/app/environments/environment.ci.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | ||||||
|  | 
 | ||||||
|  | export class Environment { | ||||||
|  |   public static readonly BASE_URL = "http://employee:8089"; | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								src/app/environments/environment.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/app/environments/environment.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | ||||||
|  | 
 | ||||||
|  | export class Environment { | ||||||
|  |   public static readonly BASE_URL = "http://127.0.0.1:8089"; | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								src/app/models/mitarbeiter.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/app/models/mitarbeiter.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | ||||||
|  | import { QualificationGetDTO, QualificationPostDTO } from "./skill"; | ||||||
|  | 
 | ||||||
|  | export interface EmployeeRequestPutDTO { | ||||||
|  |   lastName: string, | ||||||
|  |   firstName: string, | ||||||
|  |   street: string, | ||||||
|  |   postcode: string, | ||||||
|  |   city: string, | ||||||
|  |   phone: string, | ||||||
|  |   skillSet: Array<number>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface EmployeeResponseDTO { | ||||||
|  |   id: number, | ||||||
|  |   lastName: string, | ||||||
|  |   firstName: string, | ||||||
|  |   street: string, | ||||||
|  |   postcode: string, | ||||||
|  |   city: string, | ||||||
|  |   phone: string, | ||||||
|  |   skillSet?: Array<QualificationGetDTO>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface EmployeeRequestDTO { | ||||||
|  |   lastName: string, | ||||||
|  |   firstName: string, | ||||||
|  |   street: string, | ||||||
|  |   postcode: string, | ||||||
|  |   city: string, | ||||||
|  |   phone: string, | ||||||
|  |   skillSet?: Array<number>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface EmployeeNameAndSkillDataDTO { | ||||||
|  |   id: number, | ||||||
|  |   lastName: string, | ||||||
|  |   firstName: string, | ||||||
|  |   skillSet: Array<QualificationPostDTO>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface EmployeeNameDataDTO { | ||||||
|  |   id: number, | ||||||
|  |   lastName: string, | ||||||
|  |   firstName: string, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
							
								
								
									
										15
									
								
								src/app/models/skill.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/app/models/skill.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | ||||||
|  | import { EmployeeNameDataDTO } from "./mitarbeiter"; | ||||||
|  | 
 | ||||||
|  | export interface QualificationGetDTO { | ||||||
|  |   id: number, | ||||||
|  |   skill: string, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface QualificationPostDTO { | ||||||
|  |   skill: string, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface EmployeesForAQualificationDTO { | ||||||
|  |   qualification: QualificationGetDTO, | ||||||
|  |   employees: Array<EmployeeNameDataDTO>, | ||||||
|  | } | ||||||
							
								
								
									
										76
									
								
								src/app/service/employee.service.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/app/service/employee.service.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,76 @@ | ||||||
|  | import { Injectable } from "@angular/core"; | ||||||
|  | import { EmployeeRequestDTO, EmployeeRequestPutDTO, EmployeeResponseDTO } from "../models/mitarbeiter"; | ||||||
|  | import { HttpClient } from "@angular/common/http"; | ||||||
|  | import { Observable } from "rxjs"; | ||||||
|  | import { SkillService } from "./skill.service"; | ||||||
|  | 
 | ||||||
|  | @Injectable({ | ||||||
|  |   providedIn: 'root' | ||||||
|  | }) | ||||||
|  | export class EmployeeService { | ||||||
|  |   constructor(private http: HttpClient) { } | ||||||
|  | 
 | ||||||
|  |   responseToRequestDto(employee: EmployeeResponseDTO): EmployeeRequestDTO { | ||||||
|  |     return { | ||||||
|  |       firstName: employee.firstName, | ||||||
|  |       lastName: employee.lastName, | ||||||
|  |       street: employee.street, | ||||||
|  |       postcode: employee.postcode, | ||||||
|  |       city: employee.city, | ||||||
|  |       phone: employee.phone, | ||||||
|  |       skillSet: employee.skillSet?.map(skill => skill.id) || [], | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   responseDtoToPutDto(employee: EmployeeResponseDTO): EmployeeRequestPutDTO { | ||||||
|  |     return { | ||||||
|  |       firstName: employee.firstName, | ||||||
|  |       lastName: employee.lastName, | ||||||
|  |       street: employee.street, | ||||||
|  |       postcode: employee.postcode, | ||||||
|  |       city: employee.city, | ||||||
|  |       phone: employee.phone, | ||||||
|  |       skillSet: employee.skillSet?.map(skill => skill.id) || [], | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   createEmployee(employee: EmployeeResponseDTO) { | ||||||
|  |     this.http.post(`${SkillService.BASE_URL}/employees`, this.responseToRequestDto(employee)).subscribe(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   updateEmployee(employee: EmployeeResponseDTO) { | ||||||
|  |     this.http.put(`${SkillService.BASE_URL}/employees/${employee.id}`, this.responseDtoToPutDto(employee)).subscribe(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getAllEmployees(): Observable<Array<EmployeeResponseDTO>> { | ||||||
|  |     return this.http.get<Array<EmployeeResponseDTO>>(`${SkillService.BASE_URL}/employees`); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   deleteEmployee(id: number) { | ||||||
|  |     this.http.delete(`${SkillService.BASE_URL}/employees/${id}`).subscribe(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getEmployeeById(id: number): Observable<EmployeeResponseDTO> { | ||||||
|  |     return this.http.get<EmployeeResponseDTO>(`${SkillService.BASE_URL}/employees/${id}`); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   removeSkillFromEmployee(skillId: number, employeeId: number) { | ||||||
|  |     this.getEmployeeById(employeeId).subscribe(employee => { | ||||||
|  |       let employeePut = this.responseDtoToPutDto(employee); | ||||||
|  |       if (employeePut.skillSet.indexOf(skillId) != 1) { | ||||||
|  |         employeePut.skillSet = employeePut.skillSet.filter(skill => skill != skillId); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       this.http.put(`${SkillService.BASE_URL}/employees/${employee.id}`, employeePut).subscribe(); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   addSkillToEmployee(skillId: number, employee: EmployeeResponseDTO) { | ||||||
|  |     let employeePut = this.responseDtoToPutDto(employee); | ||||||
|  |     if (employeePut.skillSet.indexOf(skillId) == -1) { | ||||||
|  |       employeePut.skillSet.push(skillId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this.http.put(`${SkillService.BASE_URL}/employees/${employee.id}`, employeePut).subscribe(); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										55
									
								
								src/app/service/skill.service.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/app/service/skill.service.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | ||||||
|  | import {HttpClient} from "@angular/common/http"; | ||||||
|  | import {Injectable} from "@angular/core"; | ||||||
|  | import {EmployeesForAQualificationDTO, QualificationGetDTO, QualificationPostDTO} from "../models/skill"; | ||||||
|  | import {Observable} from "rxjs"; | ||||||
|  | import {EmployeeNameAndSkillDataDTO, EmployeeNameDataDTO} from "../models/mitarbeiter"; | ||||||
|  | import {EmployeeService} from "./employee.service"; | ||||||
|  | import {Environment} from "../environments/environment"; | ||||||
|  | 
 | ||||||
|  | @Injectable({ | ||||||
|  |   providedIn: 'root' | ||||||
|  | }) | ||||||
|  | export class SkillService { | ||||||
|  |   deleteSkill(id: number) { | ||||||
|  |     this.getEmployeesBySkill(id).subscribe(employees => { | ||||||
|  |       for (let employee of employees.employees) { | ||||||
|  |         this.employeeService.removeSkillFromEmployee(id, employee.id); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     this.http.delete(`${SkillService.BASE_URL}/qualifications/${id}`).subscribe(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public static readonly BASE_URL = Environment.BASE_URL; | ||||||
|  | 
 | ||||||
|  |   getToPutDto(skill: QualificationGetDTO): QualificationPostDTO { | ||||||
|  |     return { | ||||||
|  |       skill: skill.skill, | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   createSkill(skill: QualificationGetDTO) { | ||||||
|  |     this.http.post(`${SkillService.BASE_URL}/qualifications`, this.getToPutDto(skill)).subscribe(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   constructor(private http: HttpClient, private employeeService: EmployeeService) { | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   updateSkill(skill: QualificationGetDTO) { | ||||||
|  |     this.http.put(`${SkillService.BASE_URL}/qualifications/${skill.id}`, this.getToPutDto(skill)).subscribe(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getAllSkills(): Observable<Array<QualificationGetDTO>> { | ||||||
|  |     return this.http.get<Array<QualificationGetDTO>>(`${SkillService.BASE_URL}/qualifications`); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getEmployeesBySkill(id: number): Observable<EmployeesForAQualificationDTO> { | ||||||
|  |     return this.http.get<EmployeesForAQualificationDTO>(`${SkillService.BASE_URL}/qualifications/${id}/employees`); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getSkillsOfEmployee(id: number): Observable<EmployeeNameAndSkillDataDTO> { | ||||||
|  |     return this.http.get<EmployeeNameAndSkillDataDTO>(`${SkillService.BASE_URL}/employees/${id}/qualifications`); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
|  | import { HttpClientModule } from '@angular/common/http'; | ||||||
| import { bootstrapApplication } from '@angular/platform-browser'; | import { bootstrapApplication } from '@angular/platform-browser'; | ||||||
| import { appConfig } from './app/app.config'; | import { appConfig } from './app/app.config'; | ||||||
| import { AppComponent } from './app/app.component'; | import { AppComponent } from './app/app.component'; | ||||||
| 
 | 
 | ||||||
| bootstrapApplication(AppComponent, appConfig) | bootstrapApplication(AppComponent , appConfig,) | ||||||
|   .catch((err) => console.error(err)); |   .catch((err) => console.error(err)); | ||||||
|  |  | ||||||
							
								
								
									
										46
									
								
								tests/login.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								tests/login.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | ||||||
|  | import { test, expect } from '@playwright/test'; | ||||||
|  | import { async } from 'rxjs'; | ||||||
|  | 
 | ||||||
|  | test('LoginPageShouldRender', async ({ page }) => { | ||||||
|  |   await page.goto('http://localhost:4200'); | ||||||
|  | 
 | ||||||
|  |   // Expect a title "to contain" a substring.
 | ||||||
|  |   await expect(page).toHaveTitle(/EmployeeService/); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('LoginPageShouldHaveCorrectHeading', async ({ page }) => { | ||||||
|  |   await page.goto('http://localhost:4200'); | ||||||
|  | 
 | ||||||
|  |   const heading = page.getByRole('heading'); | ||||||
|  |   await expect(heading).toHaveText('Hi-Tec GmbH'); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('LoginPageShouldHaveLoginButton', async ({ page }) => { | ||||||
|  |   await page.goto('http://localhost:4200'); | ||||||
|  | 
 | ||||||
|  |   const button = page.getByRole('button'); | ||||||
|  |   await expect(button).toHaveText('Login with Keycloak'); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('LoginPageButtonShouldRedirectToKeycloak', async ({ page }) => { | ||||||
|  |   await page.goto('http://localhost:4200'); | ||||||
|  | 
 | ||||||
|  |   const button = page.getByText("Login with Keycloak"); | ||||||
|  |   await button.click(); | ||||||
|  |   await page.waitForFunction(() => window.location.href.includes('keycloak')); | ||||||
|  |   expect(page.url()).toContain("keycloak.szut.dev"); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('AfterLoginUserShouldBeRedirectedToEmployees', async ({ page }) => { | ||||||
|  |   await page.goto('http://localhost:4200'); | ||||||
|  | 
 | ||||||
|  |   await page.getByRole('button').click(); | ||||||
|  |   await page.waitForFunction(() => window.location.href.includes('keycloak')); | ||||||
|  |   await page.getByLabel('Username or email').fill('user'); | ||||||
|  |   await page.getByLabel('Password').fill('test'); | ||||||
|  |   await page.click('#kc-login'); | ||||||
|  | 
 | ||||||
|  |   expect(page.url()).toContain('localhost'); | ||||||
|  |   expect(page.url()).toContain('mitarbeiter'); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
							
								
								
									
										50
									
								
								tests/mitarbeiter.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								tests/mitarbeiter.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,50 @@ | ||||||
|  | import { test, expect } from "@playwright/test"; | ||||||
|  | 
 | ||||||
|  | test.describe('mitarbeiter', () => { | ||||||
|  |   test.beforeEach(async ({ page }) => { | ||||||
|  |     await page.goto('http://localhost:4200'); | ||||||
|  | 
 | ||||||
|  |     await page.getByRole('button').click(); | ||||||
|  |     await page.waitForFunction(() => window.location.href.includes('keycloak')); | ||||||
|  |     await page.getByLabel('Username or email').fill('user'); | ||||||
|  |     await page.getByLabel('Password').fill('test'); | ||||||
|  |     await page.click('#kc-login'); | ||||||
|  | 
 | ||||||
|  |     await page.goto('http://localhost:4200/mitarbeiter'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('ShouldLoad', async ({ page }) => { | ||||||
|  |     await expect(page.getByRole('heading')).toHaveText("Employees"); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('ShouldLoadEmployees', async ({ page }) => { | ||||||
|  |     expect(page.getByText('Max')).toBeTruthy(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('AddEmployeeShouldRedirect', async ({ page }) => { | ||||||
|  |     await page.getByText('Add employee').click(); | ||||||
|  | 
 | ||||||
|  |     expect(page.url()).toContain('mitarbeitererstellen'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('DeleteShouldBeThere', async ({ page }) => { | ||||||
|  |     const button = page.getByText('Delete').first(); | ||||||
|  | 
 | ||||||
|  |     const users = page.getByText('Delete'); | ||||||
|  |     await users.first().waitFor({ state: "visible" }); | ||||||
|  |     expect(await users.count()).toBeGreaterThan(1); | ||||||
|  |     expect(button).toBeTruthy(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('SearchShouldWork', async ({ page }) => { | ||||||
|  |     const searchField = page.getByRole('textbox'); | ||||||
|  |     const searchButton = page.getByText('Search').first(); | ||||||
|  | 
 | ||||||
|  |     await searchField.fill('Max'); | ||||||
|  |     await searchButton.click(); | ||||||
|  | 
 | ||||||
|  |     const hiddenItem = page.getByText('MusterFrau'); | ||||||
|  |     await expect(hiddenItem).toHaveCount(0); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
							
								
								
									
										61
									
								
								tests/mitarbeiterErstellen.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								tests/mitarbeiterErstellen.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | ||||||
|  | import { test, expect } from "@playwright/test"; | ||||||
|  | 
 | ||||||
|  | test.describe('mitarbeiter', () => { | ||||||
|  |   test.beforeEach(async ({ page }) => { | ||||||
|  |     await page.goto('http://localhost:4200'); | ||||||
|  | 
 | ||||||
|  |     await page.getByRole('button').click(); | ||||||
|  |     await page.waitForFunction(() => window.location.href.includes('keycloak')); | ||||||
|  |     await page.getByLabel('Username or email').fill('user'); | ||||||
|  |     await page.getByLabel('Password').fill('test'); | ||||||
|  |     await page.click('#kc-login'); | ||||||
|  | 
 | ||||||
|  |     await page.goto('http://localhost:4200/mitarbeitererstellen'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('pageShouldLoad', async ({ page }) => { | ||||||
|  |     await expect(page.getByText('Save')).toHaveCount(1); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('backButtonShouldGoBack', async ({ page }) => { | ||||||
|  |     await page.getByText('Back').click(); | ||||||
|  | 
 | ||||||
|  |     expect(page.url().includes('erstellen')).toBeFalsy(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('EveryFieldShouldValidateEmptiness', async ({ page }) => { | ||||||
|  |     await page.getByText('Save').click(); | ||||||
|  | 
 | ||||||
|  |     const errors = page.getByText('This field is required'); | ||||||
|  | 
 | ||||||
|  |     await expect(errors).toHaveCount(6); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('PhoneNumberShouldBeValidated', async ({ page }) => { | ||||||
|  |     await page.getByLabel('Phone').fill("asd"); | ||||||
|  |     await page.getByText('Save').click(); | ||||||
|  | 
 | ||||||
|  |     const error = page.getByText('This field must be a valid phone number'); | ||||||
|  | 
 | ||||||
|  |     await expect(error).toHaveCount(1); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('PostCodeShouldValidateTooShort', async ({ page }) => { | ||||||
|  |     await page.getByLabel('Postcode').fill("1"); | ||||||
|  |     await page.getByText('Save').click(); | ||||||
|  | 
 | ||||||
|  |     const error = page.getByText('The value is too short'); | ||||||
|  | 
 | ||||||
|  |     await expect(error).toHaveCount(1); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('PostCodeShouldValidateTooLong', async ({ page }) => { | ||||||
|  |     await page.getByLabel('Postcode').fill("123456"); | ||||||
|  |     await page.getByText('Save').click(); | ||||||
|  | 
 | ||||||
|  |     const error = page.getByText('The value is too long'); | ||||||
|  | 
 | ||||||
|  |     await expect(error).toHaveCount(1); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
							
								
								
									
										24
									
								
								tests/mitarbeiterbearbeiten.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								tests/mitarbeiterbearbeiten.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | ||||||
|  | import { test, expect } from "@playwright/test"; | ||||||
|  | 
 | ||||||
|  | test.describe('mitarbeiterbearbeiten', () => { | ||||||
|  |   test.beforeEach(async ({ page }) => { | ||||||
|  |     await page.goto('http://localhost:4200'); | ||||||
|  | 
 | ||||||
|  |     await page.getByRole('button').click(); | ||||||
|  |     await page.waitForFunction(() => window.location.href.includes('keycloak')); | ||||||
|  |     await page.getByLabel('Username or email').fill('user'); | ||||||
|  |     await page.getByLabel('Password').fill('test'); | ||||||
|  |     await page.click('#kc-login'); | ||||||
|  | 
 | ||||||
|  |     await page.goto('http://localhost:4200/mitarbeiterbearbeiten/1'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('ShouldLoad', async ({ page }) => { | ||||||
|  |     expect(page.getByText("Save")).toBeTruthy(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('FieldsShouldHaveValues', async ({page}) => { | ||||||
|  |     await expect(page.getByLabel('First Name')).toHaveValue('Max'); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
							
								
								
									
										23
									
								
								tests/qualifikationbearbeiten.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								tests/qualifikationbearbeiten.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | ||||||
|  | import { test, expect } from "@playwright/test"; | ||||||
|  | 
 | ||||||
|  | test.describe('qualifikationbearbeiten', () => { | ||||||
|  |   test.beforeEach(async ({ page }) => { | ||||||
|  |     await page.goto('http://localhost:4200'); | ||||||
|  | 
 | ||||||
|  |     await page.getByRole('button').click(); | ||||||
|  |     await page.waitForFunction(() => window.location.href.includes('keycloak')); | ||||||
|  |     await page.getByLabel('Username or email').fill('user'); | ||||||
|  |     await page.getByLabel('Password').fill('test'); | ||||||
|  |     await page.click('#kc-login'); | ||||||
|  | 
 | ||||||
|  |     await page.goto('http://localhost:4200/qualifikationbearbeiten/1'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('ShouldLoad', async ({ page }) => { | ||||||
|  |     expect(page.getByText("Save")).toBeTruthy(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('FieldsShouldHaveValues', async ({page}) => { | ||||||
|  |     await expect(page.getByLabel('Name')).toHaveValue('Java'); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										58
									
								
								tests/qualifikationsverwaltung.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								tests/qualifikationsverwaltung.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,58 @@ | ||||||
|  | import { test, expect } from "@playwright/test"; | ||||||
|  | 
 | ||||||
|  | test.describe('qualifikationen', () => { | ||||||
|  |   test.beforeEach(async ({ page }) => { | ||||||
|  |     await page.goto('http://localhost:4200'); | ||||||
|  | 
 | ||||||
|  |     await page.getByRole('button').click(); | ||||||
|  |     await page.waitForFunction(() => window.location.href.includes('keycloak')); | ||||||
|  |     await page.getByLabel('Username or email').fill('user'); | ||||||
|  |     await page.getByLabel('Password').fill('test'); | ||||||
|  |     await page.click('#kc-login'); | ||||||
|  | 
 | ||||||
|  |     await page.goto('http://localhost:4200/qualifikationsverwaltung'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('ShouldLoad', async ({ page }) => { | ||||||
|  |     await expect(page.getByRole('heading')).toHaveText("Qualifications"); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('ShouldLoadQualifications', async ({ page }) => { | ||||||
|  |     expect(page.getByText('Java')).toBeTruthy(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   // TODO
 | ||||||
|  |   // test('AddQualificationShouldRedirect', async ({ page }) => {
 | ||||||
|  |   //   await page.getByText('Add qualification').click();
 | ||||||
|  | 
 | ||||||
|  |   //   expect(page.url()).toContain('mitarbeitererstellen');
 | ||||||
|  |   // });
 | ||||||
|  | 
 | ||||||
|  |   test('EditShouldRedirectToCorrespondingPage', async ({ page }) => { | ||||||
|  |     const button = page.getByText('Edit').first(); | ||||||
|  |     await button.click(); | ||||||
|  | 
 | ||||||
|  |     expect(page.url()).toContain('qualifikationbearbeiten'); | ||||||
|  |     expect(page.url()).toContain('1'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('DeleteShouldBeThere', async ({ page }) => { | ||||||
|  |     const button = page.getByText('Delete').first(); | ||||||
|  | 
 | ||||||
|  |     const users = page.getByText('Delete'); | ||||||
|  |     await users.first().waitFor({ state: "visible" }); | ||||||
|  |     expect(await users.count()).toBe(2); | ||||||
|  |     expect(button).toBeTruthy(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('SearchShouldWork', async ({ page }) => { | ||||||
|  |     const searchField = page.getByRole('textbox'); | ||||||
|  |     const searchButton = page.getByText('Search').first(); | ||||||
|  | 
 | ||||||
|  |     await searchField.fill('Java'); | ||||||
|  |     await searchButton.click(); | ||||||
|  | 
 | ||||||
|  |     const hiddenItem = page.getByText('Angular'); | ||||||
|  |     await expect(hiddenItem).toHaveCount(0); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
		Reference in a new issue