diff --git a/src/app/app.config.ts b/src/app/app.config.ts index b047b4b..bd19328 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -2,7 +2,7 @@ import { APP_INITIALIZER, ApplicationConfig, provideZoneChangeDetection } from ' import { provideRouter } from '@angular/router'; 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'; export const initializeKeycloak = (keycloak: KeycloakService) => async () => @@ -28,20 +28,20 @@ function initializeApp(keycloak: KeycloakService): () => Promise { export const appConfig: ApplicationConfig = { providers: [ - provideRouter(routes), - KeycloakAngularModule, - { - provide: APP_INITIALIZER, - useFactory: initializeApp, - multi: true, - deps: [KeycloakService] - }, - KeycloakService, - provideHttpClient(withInterceptorsFromDi()), - { - provide: HTTP_INTERCEPTORS, - useClass: KeycloakBearerInterceptor, - multi: true - } - ] + provideRouter(routes), + KeycloakAngularModule, + { + provide: APP_INITIALIZER, + useFactory: initializeApp, + multi: true, + deps: [KeycloakService] + }, + KeycloakService, + provideHttpClient(withInterceptorsFromDi()), + { + provide: HTTP_INTERCEPTORS, + useClass: KeycloakBearerInterceptor, + multi: true + } + ] }; diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index d0683e9..93c626a 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -5,6 +5,7 @@ import { EmployeeDetailComponent } from "./components/employee-detail/employee-d import { QualifikatonBearbeitenViewComponent } from "./components/qualifikaton-bearbeiten-view/qualifikaton-bearbeiten-view.component"; import { KeycloakAuthGuard } from "keycloak-angular"; import { AuthGuard } from "./service/auth.service"; +import { MitarbeiterBearbeitenViewComponent } from "./components/mitarbeiter-bearbeiten-view/mitarbeiter-bearbeiten-view.component"; export const routes: Routes = [ { @@ -16,6 +17,11 @@ export const routes: Routes = [ component: MitarbeiterverwaltungViewComponent, canActivate: [AuthGuard], }, + { + path: "mitarbeiterbearbeiten/:id", + component: MitarbeiterBearbeitenViewComponent, + canActivate: [AuthGuard], + }, { path: "mitarbeiterdetails", component: EmployeeDetailComponent, diff --git a/src/app/components/mitarbeiter-bearbeiten-view/mitarbeiter-bearbeiten-view.component.css b/src/app/components/mitarbeiter-bearbeiten-view/mitarbeiter-bearbeiten-view.component.css index ac54d29..e69de29 100644 --- a/src/app/components/mitarbeiter-bearbeiten-view/mitarbeiter-bearbeiten-view.component.css +++ b/src/app/components/mitarbeiter-bearbeiten-view/mitarbeiter-bearbeiten-view.component.css @@ -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; -} - diff --git a/src/app/components/mitarbeiter-bearbeiten-view/mitarbeiter-bearbeiten-view.component.html b/src/app/components/mitarbeiter-bearbeiten-view/mitarbeiter-bearbeiten-view.component.html index 4bc03b5..682a986 100644 --- a/src/app/components/mitarbeiter-bearbeiten-view/mitarbeiter-bearbeiten-view.component.html +++ b/src/app/components/mitarbeiter-bearbeiten-view/mitarbeiter-bearbeiten-view.component.html @@ -1,49 +1 @@ -
- - - -
-

Skills

-
    -
  • - Skill 1 - -
  • -
-
- - -
-
- -
+ diff --git a/src/app/components/mitarbeiter-bearbeiten-view/mitarbeiter-bearbeiten-view.component.ts b/src/app/components/mitarbeiter-bearbeiten-view/mitarbeiter-bearbeiten-view.component.ts index ccf171e..253070e 100644 --- a/src/app/components/mitarbeiter-bearbeiten-view/mitarbeiter-bearbeiten-view.component.ts +++ b/src/app/components/mitarbeiter-bearbeiten-view/mitarbeiter-bearbeiten-view.component.ts @@ -1,12 +1,43 @@ import { Component } from '@angular/core'; +import { MitarbeiterFormComponent } from '../mitarbeiter-form/mitarbeiter-form.component'; +import { EmployeeResponseDTO } from '../../models/mitarbeiter'; +import { EmployeeService } from '../../service/employee.service'; +import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'app-mitarbeiter-bearbeiten-view', standalone: true, - imports: [], + imports: [MitarbeiterFormComponent], templateUrl: './mitarbeiter-bearbeiten-view.component.html', styleUrl: './mitarbeiter-bearbeiten-view.component.css' }) 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; + }); + } } diff --git a/src/app/components/mitarbeiter-form/mitarbeiter-form.component.css b/src/app/components/mitarbeiter-form/mitarbeiter-form.component.css new file mode 100644 index 0000000..ac54d29 --- /dev/null +++ b/src/app/components/mitarbeiter-form/mitarbeiter-form.component.css @@ -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; +} + diff --git a/src/app/components/mitarbeiter-form/mitarbeiter-form.component.html b/src/app/components/mitarbeiter-form/mitarbeiter-form.component.html new file mode 100644 index 0000000..5fe664d --- /dev/null +++ b/src/app/components/mitarbeiter-form/mitarbeiter-form.component.html @@ -0,0 +1,78 @@ +
+ +
+ + + +
+

Skills

+
    + @for (skill of mitarbeiter.skillSet; track skill) { +
  • + {{skill.skill}} + +
  • + } +
+ @if (!hasAllSkills) { +
+ + +
+ } +
+ +
+ +
diff --git a/src/app/components/mitarbeiter-form/mitarbeiter-form.component.spec.ts b/src/app/components/mitarbeiter-form/mitarbeiter-form.component.spec.ts new file mode 100644 index 0000000..76b7600 --- /dev/null +++ b/src/app/components/mitarbeiter-form/mitarbeiter-form.component.spec.ts @@ -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; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MitarbeiterFormComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(MitarbeiterFormComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/mitarbeiter-form/mitarbeiter-form.component.ts b/src/app/components/mitarbeiter-form/mitarbeiter-form.component.ts new file mode 100644 index 0000000..0e73354 --- /dev/null +++ b/src/app/components/mitarbeiter-form/mitarbeiter-form.component.ts @@ -0,0 +1,131 @@ +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 } 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: [MitarbeiterFormComponent, ReactiveFormsModule, NgFor, AsyncPipe], + templateUrl: './mitarbeiter-form.component.html', + styleUrl: './mitarbeiter-form.component.css' +}) +export class MitarbeiterFormComponent { + @Input() mitarbeiter!: EmployeeResponseDTO; + @Output() mitarbeiterChange = new EventEmitter(); + + public mitarbeiterForm!: FormGroup; + public allSkills: Observable> = of([]); + public hasAllSkills: boolean = false; + errorMessages: Record = {}; + + constructor(public http: HttpClient, public router: Router, private skillService: SkillService) { + + } + + returnToEmployeeOverview() { + this.router.navigate(["mitarbeiter"]); + } + + 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(), + }); + } + + 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): 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); + this.allSkills.subscribe(skills => { + const newSkill = skills.filter(skill => skill.id == id)[0]; + this.mitarbeiter.skillSet?.push(newSkill); + this.skillsChanged(); + }); + } + + private validationErrorMessages: Record = { + 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 && control.touched) { + 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); + } +} diff --git a/src/app/models/mitarbeiter.ts b/src/app/models/mitarbeiter.ts new file mode 100644 index 0000000..9b67f85 --- /dev/null +++ b/src/app/models/mitarbeiter.ts @@ -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, +} + +export interface EmployeeResponseDTO { + id: number, + lastName: string, + firstName: string, + street: string, + postcode: string, + city: string, + phone: string, + skillSet?: Array, +} + +export interface EmployeeRequestDTO { + lastName: string, + firstName: string, + street: string, + postcode: string, + city: string, + phone: string, + skillSet?: Array, +} + +export interface EmployeeNameAndSkillDataDTO { + id: number, + lastName: string, + firstName: string, + skillSet: Array, +} + +export interface EmployeeNameDataDTO { + id: number, + lastName: string, + firstName: string, +} + + diff --git a/src/app/models/skill.ts b/src/app/models/skill.ts new file mode 100644 index 0000000..43b4411 --- /dev/null +++ b/src/app/models/skill.ts @@ -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, +} diff --git a/src/app/service/employee.service.ts b/src/app/service/employee.service.ts new file mode 100644 index 0000000..a8a4d38 --- /dev/null +++ b/src/app/service/employee.service.ts @@ -0,0 +1,46 @@ +import { Injectable } from "@angular/core"; +import { 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) { } + + + 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) || [], + } + } + + updateEmployee(employee: EmployeeResponseDTO) { + this.http.put(`${SkillService.BASE_URL}/employees/${employee.id}`, this.responseDtoToPutDto(employee)).subscribe(); + } + + getAllEmployees(): Observable> { + return this.http.get>(`${SkillService.BASE_URL}/employees`); + } + + + getEmployeeById(id: number): Observable { + return this.http.get(`${SkillService.BASE_URL}/employees/${id}`); + } + + + addSkillToEmployee(skillId: number, employee: EmployeeResponseDTO) { + let employeePut = this.responseDtoToPutDto(employee); + employeePut.skillSet.push(skillId); + + this.http.put(`${SkillService.BASE_URL}/employees/${employee.id}`, employeePut).subscribe(); + } +} diff --git a/src/app/service/skill.service.ts b/src/app/service/skill.service.ts new file mode 100644 index 0000000..9f6764b --- /dev/null +++ b/src/app/service/skill.service.ts @@ -0,0 +1,20 @@ +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { QualificationGetDTO } from "../models/skill"; +import { Observable } from "rxjs"; + +@Injectable({ + providedIn: 'root' +}) +export class SkillService { + + public static readonly BASE_URL = "http://localhost:8089"; + + constructor(private http: HttpClient) { + + } + + getAllSkills(): Observable> { + return this.http.get>(`${SkillService.BASE_URL}/qualifications`); + } +}