Feature: Qualification management #22
4 changed files with 263 additions and 1 deletions
|
@ -1,5 +1,6 @@
|
||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
import { DashboardComponent } from '@app/views/dashboard/dashboard.component';
|
import { DashboardComponent } from '@app/views/dashboard/dashboard.component';
|
||||||
|
import {QualificationsComponent} from '@app/views/qualifications/qualifications.component';
|
||||||
import { AuthService } from '@core/auth/auth.service';
|
import { AuthService } from '@core/auth/auth.service';
|
||||||
|
|
||||||
import { EmployeeDetailComponent } from './views/employee-detail/employee-detail.component';
|
import { EmployeeDetailComponent } from './views/employee-detail/employee-detail.component';
|
||||||
|
@ -7,5 +8,6 @@ import { EmployeeDetailComponent } from './views/employee-detail/employee-detail
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{ path: '', component: DashboardComponent, title: 'Home' },
|
{ path: '', component: DashboardComponent, title: 'Home' },
|
||||||
{ path: 'employee/new', component: EmployeeDetailComponent, title: 'New Employee', canActivate: [AuthService] },
|
{ path: 'employee/new', component: EmployeeDetailComponent, title: 'New Employee', canActivate: [AuthService] },
|
||||||
{ path: 'employee/:id', component: EmployeeDetailComponent, title: 'Edit Employee', canActivate: [AuthService] }
|
{ path: 'employee/:id', component: EmployeeDetailComponent, title: 'Edit Employee', canActivate: [AuthService] },
|
||||||
|
{path: 'qualifications', component: QualificationsComponent, title: 'Qualifications', canActivate: [AuthService]}
|
||||||
];
|
];
|
||||||
|
|
54
src/app/views/qualifications/qualifications.component.html
Normal file
54
src/app/views/qualifications/qualifications.component.html
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<div class="qualifications">
|
||||||
|
<div class="qualifications__action-row">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Qualification</mat-label>
|
||||||
|
<input matInput>
|
||||||
|
</mat-form-field>
|
||||||
|
<button mat-fab class="shadowless">
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<mat-table class="qualifications__view" [dataSource]="qualificationDataSource">
|
||||||
|
<ng-container matColumnDef="id">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Id</th>
|
||||||
|
<td mat-cell *matCellDef="let qualification">{{qualification.id}}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="skill">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Skill</th>
|
||||||
|
<td mat-cell *matCellDef="let qualification" [class.edit]="isEditing(qualification.id)">
|
||||||
|
<p>{{qualification.skill}}</p>
|
||||||
|
<form [formGroup]="getSkillFormGroup(qualification)">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Skill</mat-label>
|
||||||
|
<input matInput formControlName="skill" required>
|
||||||
|
<mat-error>Skill can't be empty</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="actions">
|
||||||
|
<th mat-header-cell *matHeaderCellDef></th>
|
||||||
|
<td mat-cell *matCellDef="let qualification">
|
||||||
|
@if (!isEditing(qualification.id)) {
|
||||||
|
<button mat-mini-fab class="shadowless" (click)="startEdit(qualification.id)">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-mini-fab class="warn shadowless" (click)="onDelete(qualification)">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
</button>
|
||||||
|
} @else {
|
||||||
|
<button mat-mini-fab class="shadowless" (click)="endEdit(qualification, true)">
|
||||||
|
<mat-icon>save</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-mini-fab class="warn shadowless" (click)="endEdit(qualification, false)">
|
||||||
|
<mat-icon>cancel</mat-icon>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="qualificationsDisplayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: qualificationsDisplayedColumns;"></tr>
|
||||||
|
</mat-table>
|
||||||
|
</div>
|
||||||
|
|
48
src/app/views/qualifications/qualifications.component.scss
Normal file
48
src/app/views/qualifications/qualifications.component.scss
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
.qualifications{
|
||||||
|
&__action-row{
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
mat-form-field:first-of-type{
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__view{
|
||||||
|
tr{
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: fit-content;
|
||||||
|
|
||||||
|
.mat-column-id{
|
||||||
|
width: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-column-skill{
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
form{
|
||||||
|
display: none;
|
||||||
|
padding-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.edit{
|
||||||
|
p{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
form{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-column-actions{
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0;
|
||||||
|
padding-top: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
158
src/app/views/qualifications/qualifications.component.ts
Normal file
158
src/app/views/qualifications/qualifications.component.ts
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {FormBuilder, FormGroup, ReactiveFormsModule} from '@angular/forms';
|
||||||
|
import {MatButtonModule} from '@angular/material/button';
|
||||||
|
import {MatDialog} from '@angular/material/dialog';
|
||||||
|
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||||
|
import {MatIcon} from '@angular/material/icon';
|
||||||
|
import {MatInputModule} from '@angular/material/input';
|
||||||
|
import {MatSelectModule} from '@angular/material/select';
|
||||||
|
import {MatTableDataSource, MatTableModule} from '@angular/material/table';
|
||||||
|
import {DeleteModelComponent} from '@app/delete-model/delete-model.component';
|
||||||
|
import {
|
||||||
|
EmployeeQualifications,
|
||||||
|
EmployeeService,
|
||||||
|
Qualification,
|
||||||
|
QualificationEmployees,
|
||||||
|
QualificationService,
|
||||||
|
RemoveQualificationFromEmployeeResponse
|
||||||
|
} from '@core/ems';
|
||||||
|
import {NotificationService, NotificationType} from '@core/notification/notification.service';
|
||||||
|
import {forkJoin, Observable} from 'rxjs';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-qualifications',
|
||||||
|
imports: [
|
||||||
|
MatIcon,
|
||||||
|
MatButtonModule,
|
||||||
|
MatInputModule,
|
||||||
|
MatSelectModule,
|
||||||
|
MatTableModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
ReactiveFormsModule
|
||||||
|
],
|
||||||
|
templateUrl: './qualifications.component.html',
|
||||||
|
styleUrl: './qualifications.component.scss'
|
||||||
|
})
|
||||||
|
export class QualificationsComponent {
|
||||||
|
qualificationDataSource: MatTableDataSource<Qualification> = new MatTableDataSource<Qualification>([]);
|
||||||
|
qualificationsDisplayedColumns = ['id', 'skill', 'actions'];
|
||||||
|
|
||||||
|
qualificationSkillFormGroups: Map<number, FormGroup> = new Map();
|
||||||
|
qualificationEdits: Map<number, boolean> = new Map();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private qualificationService: QualificationService,
|
||||||
|
private employeeService: EmployeeService,
|
||||||
|
protected formBuilder: FormBuilder,
|
||||||
|
private notifications: NotificationService,
|
||||||
|
private dialog: MatDialog
|
||||||
|
) {
|
||||||
|
this.qualificationService.getAllQualifications().subscribe((qualifications) => {
|
||||||
|
this.qualificationDataSource = new MatTableDataSource<Qualification>(qualifications);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getSkillFormGroup(skill: Qualification): FormGroup {
|
||||||
|
|
||||||
|
let formGroup = this.qualificationSkillFormGroups.get(skill.id);
|
||||||
|
if (formGroup == undefined) {
|
||||||
|
formGroup = this.formBuilder.group(skill);
|
||||||
|
this.qualificationSkillFormGroups.set(skill.id, formGroup);
|
||||||
|
}
|
||||||
|
return formGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
isEditing(id: number): boolean {
|
||||||
|
const editing = this.qualificationEdits.get(id);
|
||||||
|
if (editing == undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return editing;
|
||||||
|
}
|
||||||
|
|
||||||
|
startEdit(id: number) {
|
||||||
|
this.qualificationEdits.set(id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
endEdit(oldQualification: Qualification, save: boolean) {
|
||||||
|
|
||||||
|
const qualificationFormGroup = this.qualificationSkillFormGroups.get(oldQualification.id);
|
||||||
|
if (qualificationFormGroup == undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const qualification: Qualification = qualificationFormGroup.value;
|
||||||
|
|
||||||
|
if (!save) {
|
||||||
|
qualificationFormGroup.setValue(oldQualification);
|
||||||
|
this.qualificationEdits.set(oldQualification.id, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qualificationFormGroup.invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.qualificationService.updateQualification({
|
||||||
|
id: oldQualification.id,
|
||||||
|
requestBody: {
|
||||||
|
skill: qualification.skill
|
||||||
|
}
|
||||||
|
}).subscribe(() => {
|
||||||
|
const data = this.qualificationDataSource.data;
|
||||||
|
const i = data.indexOf(oldQualification);
|
||||||
|
data[i] = qualification;
|
||||||
|
this.qualificationDataSource.data = data;
|
||||||
|
|
||||||
|
this.qualificationEdits.set(oldQualification.id, false);
|
||||||
|
this.notifications.publish(`Saved ${qualification.skill}`, NotificationType.Information);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDelete(qualification: Qualification) {
|
||||||
|
const dialogRef = this.dialog.open(DeleteModelComponent, {
|
||||||
|
data: {
|
||||||
|
title: `Delete ${qualification.skill}`,
|
||||||
|
description: `Do you really want to delete ${qualification.skill}?`,
|
||||||
|
cancel: 'No',
|
||||||
|
ok: 'Yes',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe((accepted: boolean) => {
|
||||||
|
if (!accepted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.qualificationService.getAllQualificationEmployees({id: qualification.id}).subscribe((employees: QualificationEmployees) => {
|
||||||
|
const requests: Array<Observable<RemoveQualificationFromEmployeeResponse>> = [];
|
||||||
|
for (const employee of employees.employees) {
|
||||||
|
requests.push(this.employeeService.removeQualificationFromEmployee({
|
||||||
|
employeeId: employee.id,
|
||||||
|
qualificationId: qualification.id
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
forkJoin(requests).subscribe((employeesQualifications: Array<EmployeeQualifications>)=>{
|
||||||
|
for (const employee of employeesQualifications){
|
||||||
|
if (employee.skillSet?.map((q)=>q.id).includes(qualification.id)){
|
||||||
|
this.notifications.publish(`Could not remove ${qualification.skill} from ${employee.firstName} ${employee.lastName}`, NotificationType.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.qualificationService.deleteQualification({id: qualification.id}).subscribe(() => {
|
||||||
|
const data = this.qualificationDataSource.data;
|
||||||
|
const i = data.indexOf(qualification);
|
||||||
|
if (i != -1) {
|
||||||
|
data.splice(i, 1);
|
||||||
|
this.qualificationDataSource.data = data;
|
||||||
|
}
|
||||||
|
this.notifications.publish(`Deleted ${qualification.skill}`, NotificationType.Information);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue