diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index 87d779e..00a38ec 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -1,5 +1,6 @@
import { Routes } from '@angular/router';
import { DashboardComponent } from '@app/views/dashboard/dashboard.component';
+import {QualificationsComponent} from '@app/views/qualifications/qualifications.component';
import { AuthService } from '@core/auth/auth.service';
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 = [
{ path: '', component: DashboardComponent, title: 'Home' },
{ 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]}
];
diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts
index 3e08782..5fc8908 100644
--- a/src/app/header/header.component.ts
+++ b/src/app/header/header.component.ts
@@ -19,6 +19,7 @@ import {AuthService} from '@core/auth/auth.service';
TitleCasePipe,
],
templateUrl: './header.component.html',
+ standalone: true,
styleUrl: './header.component.scss'
})
export class HeaderComponent implements OnInit {
diff --git a/src/app/views/qualifications/qualifications.component.html b/src/app/views/qualifications/qualifications.component.html
new file mode 100644
index 0000000..ef816aa
--- /dev/null
+++ b/src/app/views/qualifications/qualifications.component.html
@@ -0,0 +1,54 @@
+
+
+
+ Qualification
+
+
+
+
+
+
+ Id |
+ {{qualification.id}} |
+
+
+ Skill |
+
+ {{qualification.skill}}
+
+
+ |
+
+
+ |
+
+ @if (!isEditing(qualification.id)) {
+
+
+ } @else {
+
+
+ }
+ |
+
+
+
+
+
+
diff --git a/src/app/views/qualifications/qualifications.component.scss b/src/app/views/qualifications/qualifications.component.scss
new file mode 100644
index 0000000..915d8e8
--- /dev/null
+++ b/src/app/views/qualifications/qualifications.component.scss
@@ -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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/app/views/qualifications/qualifications.component.ts b/src/app/views/qualifications/qualifications.component.ts
new file mode 100644
index 0000000..3860079
--- /dev/null
+++ b/src/app/views/qualifications/qualifications.component.ts
@@ -0,0 +1,159 @@
+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',
+ standalone: true,
+ styleUrl: './qualifications.component.scss'
+})
+export class QualificationsComponent {
+ qualificationDataSource: MatTableDataSource = new MatTableDataSource([]);
+ qualificationsDisplayedColumns = ['id', 'skill', 'actions'];
+
+ qualificationSkillFormGroups: Map = new Map();
+ qualificationEdits: Map = 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(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> = [];
+ for (const employee of employees.employees) {
+ requests.push(this.employeeService.removeQualificationFromEmployee({
+ employeeId: employee.id,
+ qualificationId: qualification.id
+ }));
+ }
+ forkJoin(requests).subscribe((employeesQualifications: Array)=>{
+ 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);
+ });
+ });
+ });
+ });
+ }
+}