Feature: Employee Edit - Function, Layout, Style
All checks were successful
Quality Check / Linting (push) Successful in 25s
Quality Check / Linting (pull_request) Successful in 21s

This commit is contained in:
Dominik Säume 2025-01-10 13:13:00 +01:00
parent f6664e1d8a
commit d4ec632053
3 changed files with 131 additions and 59 deletions

View file

@ -2,47 +2,56 @@
<div class="employee-detail__info-view"> <div class="employee-detail__info-view">
<div class="employee-detail__info-view__base"> <div class="employee-detail__info-view__base">
<h2>Info</h2> <h2>Info</h2>
<mat-form-field> <div class="employee-detail__info-view__base__content">
<mat-label>First Name</mat-label> <mat-form-field>
<input matInput formControlName="firstName"> <mat-label>First Name</mat-label>
</mat-form-field> <input matInput formControlName="firstName" required>
<mat-form-field> <mat-error>First Name can not be Empty</mat-error>
<mat-label>Last Name</mat-label> </mat-form-field>
<input matInput formControlName="lastName"> <mat-form-field>
</mat-form-field> <mat-label>Last Name</mat-label>
<mat-form-field> <input matInput formControlName="lastName" required>
<mat-label>Postcode</mat-label> <mat-error>Last Name can not be Empty</mat-error>
<mat-icon matSuffix>money</mat-icon> </mat-form-field>
<input matInput formControlName="postcode"> <mat-form-field>
</mat-form-field> <mat-label>Postcode</mat-label>
<mat-form-field> <mat-icon matSuffix>money</mat-icon>
<mat-label>City</mat-label> <input matInput formControlName="postcode" required minlength="5" maxlength="5">
<mat-icon matSuffix>location_city</mat-icon> <mat-error>Postcode has to be exactly 5 long</mat-error>
<input matInput formControlName="city"> </mat-form-field>
</mat-form-field> <mat-form-field>
<mat-form-field> <mat-label>City</mat-label>
<mat-label>Street</mat-label> <mat-icon matSuffix>location_city</mat-icon>
<mat-icon matSuffix>house</mat-icon> <input matInput formControlName="city" required>
<input matInput formControlName="street"> <mat-error>City can not be Empty</mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>Phone</mat-label> <mat-label>Street</mat-label>
<mat-icon matSuffix>phone</mat-icon> <mat-icon matSuffix>house</mat-icon>
<input matInput formControlName="phone"> <input matInput formControlName="street" required>
</mat-form-field> <mat-error>Street can not be Empty</mat-error>
</mat-form-field>
<mat-form-field>
<mat-label>Phone</mat-label>
<mat-icon matSuffix>phone</mat-icon>
<input matInput formControlName="phone" required>
<mat-error>Phone can not be Empty</mat-error>
</mat-form-field>
</div>
</div> </div>
<div class="employee-detail__info-view__skills"> <div class="employee-detail__info-view__skills">
<h2>Skills</h2> <h2>Skills</h2>
<div class="employee-detail__info-view__skills__action-row"> <div class="employee-detail__info-view__skills__action-row">
<mat-form-field> <mat-form-field>
<mat-label>Skill</mat-label> <mat-label>Skill</mat-label>
<mat-select [disabled]="!($unusedSkills | async)?.length"> <mat-select #skillSelect [disabled]="!($unusedSkills | async)?.length">
@for (skill of ($unusedSkills|async); track skill) { @for (skill of ($unusedSkills|async); track skill) {
<mat-option [value]="skill.skill">{{skill.skill}}</mat-option> <mat-option [value]="skill">{{skill.skill}}</mat-option>
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<button mat-fab class="shadowless"> <button mat-fab class="shadowless" (click)="onAddSkill(skillSelect)">
<mat-icon>add</mat-icon> <mat-icon>add</mat-icon>
</button> </button>
</div> </div>
@ -59,8 +68,8 @@
</ng-container> </ng-container>
<ng-container matColumnDef="action"> <ng-container matColumnDef="action">
<th mat-header-cell *matHeaderCellDef></th> <th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let row"> <td mat-cell *matCellDef="let skill">
<button mat-mini-fab class="warn shadowless"> <button mat-mini-fab class="warn shadowless" (click)="onRemoveSkill(skill)">
<mat-icon>delete</mat-icon> <mat-icon>delete</mat-icon>
</button> </button>
</td> </td>

View file

@ -3,11 +3,18 @@
.employee-detail { .employee-detail {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 4rem;
// Calculate Height and Spacing based on the Form Fields
$inner-form-height: 56px;
$form-height: 75.5px;
$inner-form-spacing: 4.5px;
$form-conter-padding: calc(($form-height + $inner-form-spacing - $inner-form-height) / 2);
$form-conter-padding-fab: calc(($form-conter-padding - $inner-form-spacing) / 2);
&__info-view { &__info-view {
display: flex; display: flex;
gap: 2rem; gap: 4rem;
>* { >* {
min-width: 0; min-width: 0;
@ -19,8 +26,14 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
input { &__content {
font-size: var(--mat-sys-body-large-size); display: flex;
flex-direction: column;
gap: $inner-form-spacing;
input {
font-size: var(--mat-sys-body-large-size);
}
} }
} }
@ -37,38 +50,34 @@
} }
} }
p {
font-size: var(--mat-sys-body-large-size);
margin: 0;
}
tr { tr {
height: 76px;
td.mat-column-skill { td.mat-column-skill {
width: 100%; width: 100%;
padding-top: 28px; justify-self: center;
padding-bottom: 28px; padding: $form-conter-padding 0;
p {
display: flex;
height: $inner-form-height;
margin: 0;
align-items: center;
font-size: var(--mat-sys-body-large-size);
}
} }
td.mat-column-action { td.mat-column-action {
width: 0; width: 0;
vertical-align: middle; vertical-align: middle;
padding-left: 24px; padding-right: 8px; // Half the difference to the Width of a FAB
padding-right: 8px;
} }
&:first-of-type { &:first-of-type {
height: 66px;
td.mat-column-skill { td.mat-column-skill {
padding-top: 0; padding-top: $inner-form-spacing;
padding-bottom: 0;
} }
td.mat-column-action { td.mat-column-action {
padding-top: 10px; padding-bottom: $form-conter-padding-fab;
vertical-align: unset;
} }
} }
} }

View file

@ -5,10 +5,11 @@ import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIcon } from '@angular/material/icon'; import { MatIcon } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select'; import { MatSelect, MatSelectModule } from '@angular/material/select';
import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { ActivatedRoute, RouterLink } from '@angular/router'; import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { Employee, EmployeeService, Qualification, QualificationService } from '@core/ems'; import { Employee, EmployeeService, NewEmployee, Qualification, QualificationService, UpdatedEmployee } from '@core/ems';
import { NotificationService, NotificationType } from '@core/notification/notification.service';
import { map, Observable } from 'rxjs'; import { map, Observable } from 'rxjs';
@Component({ @Component({
@ -32,13 +33,15 @@ export class EmployeeDetailComponent {
employeeForm: FormGroup; employeeForm: FormGroup;
skillsDataSource: MatTableDataSource<Qualification> = new MatTableDataSource<Qualification>([]); skillsDataSource: MatTableDataSource<Qualification> = new MatTableDataSource<Qualification>([]);
skillsDisplayedColumns = ['skill', 'action']; skillsDisplayedColumns = ['skill', 'action'];
$unusedSkills: Observable<Array<Qualification> | undefined>; $unusedSkills: Observable<Array<Qualification>>;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router,
private employeeService: EmployeeService, private employeeService: EmployeeService,
private qualificationService: QualificationService, private qualificationService: QualificationService,
private formBuilder: FormBuilder private formBuilder: FormBuilder,
private notificationService: NotificationService
) { ) {
const idParam = this.route.snapshot.paramMap.get('id'); const idParam = this.route.snapshot.paramMap.get('id');
this.isNewEmployee = idParam == null; this.isNewEmployee = idParam == null;
@ -74,10 +77,61 @@ export class EmployeeDetailComponent {
} }
onCreate() { onCreate() {
if (!this.employeeForm.valid) {
return;
}
const employee: NewEmployee = this.employeeForm.value;
employee.skillSet = this.skillsDataSource.data.map((qualification) => qualification.id);
this.employeeService.createEmployee({ requestBody: employee }).subscribe((response) => {
this.notificationService.publish(`${response.firstName} ${response.lastName} was created`, NotificationType.Information);
this.router.navigate(['']);
});
} }
onSave() { onSave() {
if (!this.employeeForm.valid) {
return;
}
const id = this.employeeForm.value.id;
const employee: UpdatedEmployee = this.employeeForm.value;
employee.skillSet = this.skillsDataSource.data.map((qualification) => qualification.id);
if (employee.skillSet.length == 0) {
this.employeeService.getAllEmployeeQualifications({ id }).subscribe((result) => {
result.skillSet?.forEach((skill) => {
this.employeeService.removeQualificationFromEmployee({ employeeId: id, qualificationId: skill.id }).subscribe(() => { });
});
});
}
this.employeeService.updateEmployee({ id: id, requestBody: employee }).subscribe((response) => {
this.notificationService.publish(`${response.firstName} ${response.lastName} was updated`, NotificationType.Information);
this.router.navigate(['']);
});
}
onAddSkill(select: MatSelect) {
if (select.value == undefined) {
return;
}
const newSkill = select.value as Qualification;
const skills = this.skillsDataSource.data;
skills.push(newSkill);
this.skillsDataSource.data = skills;
this.$unusedSkills = this.$unusedSkills.pipe(
map((skills) => skills.filter(skill => skill.id != newSkill.id))
);
}
onRemoveSkill(skill: Qualification) {
const skills = this.skillsDataSource.data;
this.skillsDataSource.data = skills.filter(filterSkill => filterSkill.id != skill.id);
this.$unusedSkills = this.$unusedSkills.pipe(
map(skills => [...skills, skill])
);
} }
} }