Enhance employee and qualification forms with hints and improved layouts (#37)
- Added hints to input fields in create and edit employee forms for better user guidance. - Updated the layout of dialog actions in employee and qualification forms for improved usability. - Enhanced delete confirmation dialogs for qualifications and employees with better styling and error handling. - Improved the display of employee details and qualifications with better formatting and structure. These changes aim to improve user experience and accessibility across the application. Reviewed-on: http://git.simonis.lol/angular/ems-frontend/pulls/37 Co-authored-by: Jan-Marlon Leibl <jleibl@proton.me> Co-committed-by: Jan-Marlon Leibl <jleibl@proton.me>
This commit is contained in:
committed by
Hop In, I Have Puppies AND WiFi

parent
37b5c27a50
commit
95bf76f9c1
@ -1,24 +1,43 @@
|
||||
<h2 mat-dialog-title>Create Qualification</h2>
|
||||
<mat-dialog-content>
|
||||
<form [formGroup]="qualificationForm" (ngSubmit)="create()">
|
||||
<div class="!space-y-4">
|
||||
<h2 mat-dialog-title class="text-xl md:text-2xl font-semibold text-gray-800 mb-3 md:mb-4">Create Qualification</h2>
|
||||
|
||||
<mat-dialog-content class="!px-3 md:!px-6">
|
||||
<form [formGroup]="qualificationForm" (ngSubmit)="create()" class="w-full min-w-[280px] md:min-w-[400px]">
|
||||
<div class="space-y-4 md:space-y-6">
|
||||
@if (apiErrorMessage) {
|
||||
<mat-error>{{ apiErrorMessage }}</mat-error>
|
||||
<div class="bg-red-50 p-3 md:p-4 rounded-lg">
|
||||
<div class="flex items-start space-x-2 md:space-x-3">
|
||||
<mat-icon class="text-red-600 text-xl md:text-2xl">error_outline</mat-icon>
|
||||
<mat-error class="text-sm md:text-base text-red-700">{{ apiErrorMessage }}</mat-error>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<mat-form-field class="!w-full">
|
||||
<mat-label>Skill</mat-label>
|
||||
<input matInput
|
||||
formControlName="skill"
|
||||
required>
|
||||
<mat-error *ngIf="isFieldInvalid('skill')">
|
||||
{{ getErrorMessage('skill') }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<div class="bg-gray-50 p-3 md:p-4 rounded-lg space-y-3">
|
||||
<mat-form-field class="w-full">
|
||||
<mat-label>Skill</mat-label>
|
||||
<input matInput
|
||||
formControlName="skill"
|
||||
placeholder="Enter skill name"
|
||||
required>
|
||||
<mat-hint class="text-sm">Enter the skill name</mat-hint>
|
||||
<mat-error *ngIf="isFieldInvalid('skill')" class="text-sm">
|
||||
{{ getErrorMessage('skill') }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button mat-dialog-close>Cancel</button>
|
||||
<button mat-flat-button color="primary" type="submit">Create</button>
|
||||
<mat-dialog-actions align="end" class="!px-0 !mb-0 flex flex-col sm:flex-row w-full gap-3">
|
||||
<button mat-button
|
||||
mat-dialog-close
|
||||
class="text-sm md:text-base hover:bg-gray-100 py-2 px-4 md:px-6 rounded-md w-full sm:flex-1">
|
||||
Cancel
|
||||
</button>
|
||||
<button mat-flat-button
|
||||
color="primary"
|
||||
type="submit"
|
||||
class="!ml-0 text-sm md:text-base py-2 px-4 md:px-6 rounded-md w-full sm:flex-1">
|
||||
Create
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -9,9 +9,10 @@ import {
|
||||
MatDialogTitle
|
||||
} from "@angular/material/dialog";
|
||||
import {NgIf} from "@angular/common";
|
||||
import {MatError, MatFormField, MatLabel} from "@angular/material/form-field";
|
||||
import {MatError, MatFormField, MatHint, MatLabel} from "@angular/material/form-field";
|
||||
import {MatButton} from "@angular/material/button";
|
||||
import {MatInput} from "@angular/material/input";
|
||||
import {MatIcon} from "@angular/material/icon";
|
||||
import {filter} from "rxjs";
|
||||
|
||||
@Component({
|
||||
@ -27,7 +28,9 @@ import {filter} from "rxjs";
|
||||
MatDialogActions,
|
||||
MatButton,
|
||||
MatInput,
|
||||
MatDialogClose
|
||||
MatDialogClose,
|
||||
MatHint,
|
||||
MatIcon
|
||||
],
|
||||
templateUrl: './create.component.html',
|
||||
styleUrl: './create.component.css'
|
||||
|
@ -1,15 +1,39 @@
|
||||
<h2 mat-dialog-title>Delete Qualification</h2>
|
||||
<h2 mat-dialog-title class="text-xl md:text-2xl font-semibold text-gray-800 mb-3 md:mb-4">Delete Qualification</h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
@if (apiError) {
|
||||
<div class="!text-red-600 !mb-4 !p-3 !bg-red-50 !rounded">
|
||||
<p>{{ apiError }}</p>
|
||||
<mat-dialog-content class="!px-3 md:!px-6">
|
||||
<div class="w-full min-w-[280px] md:min-w-[400px] space-y-4 md:space-y-6">
|
||||
@if (apiError) {
|
||||
<div class="bg-red-50 p-3 md:p-4 rounded-lg">
|
||||
<div class="flex items-start space-x-2 md:space-x-3">
|
||||
<mat-icon class="text-red-600 text-xl md:text-2xl">error_outline</mat-icon>
|
||||
<mat-error class="text-sm md:text-base text-red-700">{{ apiError }}</mat-error>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="bg-amber-50 p-3 md:p-4 rounded-lg border border-amber-200">
|
||||
<div class="flex items-start space-x-2 md:space-x-3">
|
||||
<mat-icon class="text-amber-600 text-xl md:text-2xl">warning</mat-icon>
|
||||
<div>
|
||||
<p class="text-gray-800 font-medium text-sm md:text-base">Are you sure you want to delete this qualification?</p>
|
||||
<p class="text-gray-600 mt-1 text-xs md:text-sm">This action cannot be undone.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
Are you sure you want to delete this qualification? This can't be undone.
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="closeModal()">Cancel</button>
|
||||
<button mat-button (click)="delete()" cdkFocusInitial>Delete</button>
|
||||
</mat-dialog-actions>
|
||||
<mat-dialog-actions align="end" class="!px-0 !mb-0 flex flex-col sm:flex-row w-full gap-3">
|
||||
<button mat-button
|
||||
(click)="closeModal()"
|
||||
class="text-sm md:text-base hover:bg-gray-100 py-2 px-4 md:px-6 rounded-md w-full sm:flex-1">
|
||||
Cancel
|
||||
</button>
|
||||
<button mat-flat-button
|
||||
color="warn"
|
||||
(click)="delete()"
|
||||
class="!ml-0 text-sm md:text-base py-2 px-4 md:px-6 rounded-md w-full sm:flex-1"
|
||||
cdkFocusInitial>
|
||||
Delete
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
@ -10,6 +10,8 @@ import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||
import QualificationService from "../../services/qualification.service";
|
||||
import {MatButton} from "@angular/material/button";
|
||||
import {HttpErrorResponse} from "@angular/common/http";
|
||||
import { MatError } from '@angular/material/form-field'
|
||||
import {MatIcon} from "@angular/material/icon";
|
||||
|
||||
@Component({
|
||||
selector: 'app-delete-qualification',
|
||||
@ -19,7 +21,9 @@ import {HttpErrorResponse} from "@angular/common/http";
|
||||
MatDialogTitle,
|
||||
ReactiveFormsModule,
|
||||
MatDialogActions,
|
||||
MatButton
|
||||
MatButton,
|
||||
MatError,
|
||||
MatIcon
|
||||
],
|
||||
templateUrl: './delete.component.html',
|
||||
standalone: true,
|
||||
|
@ -1,26 +1,42 @@
|
||||
<h2 mat-dialog-title class="text-xl font-semibold mb-4">
|
||||
<h2 mat-dialog-title class="text-xl md:text-2xl font-semibold text-gray-800 mb-3 md:mb-4">
|
||||
{{ qualification.skill }} Developers
|
||||
</h2>
|
||||
|
||||
<mat-dialog-content class="px-1">
|
||||
@if (employees$ | async; as employees) {
|
||||
@if (employees.length === 0) {
|
||||
<p class="text-gray-500 italic">No employees found with this qualification.</p>
|
||||
} @else {
|
||||
<div class="space-y-1">
|
||||
@for (employee of employees; track employee.id) {
|
||||
<a
|
||||
class="block w-full px-4 py-2 text-blue-600 rounded-lg hover:bg-blue-50 transition-colors cursor-pointer"
|
||||
(click)="openEmployeeDetailsModal(employee.id)"
|
||||
>
|
||||
<span class="font-medium">{{ employee.firstName }} {{ employee.lastName }}</span>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<mat-dialog-content class="!px-3 md:!px-6">
|
||||
<div class="w-full min-w-[280px] md:min-w-[400px] space-y-4 md:space-y-6">
|
||||
@if (employees$ | async; as employees) {
|
||||
@if (employees.length === 0) {
|
||||
<div class="bg-gray-50 p-3 md:p-4 rounded-lg text-center">
|
||||
<mat-icon class="text-gray-400 text-xl md:text-2xl mb-2">person_off</mat-icon>
|
||||
<p class="text-gray-600 text-sm md:text-base">No employees found with this qualification.</p>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="bg-gray-50 p-3 md:p-4 rounded-lg space-y-2">
|
||||
@for (employee of employees; track employee.id) {
|
||||
<a class="block w-full p-3 bg-white rounded-lg hover:bg-blue-50 transition-colors cursor-pointer border border-gray-100 hover:border-blue-100"
|
||||
(click)="openEmployeeDetailsModal(employee.id)">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="h-8 w-8 md:h-10 md:w-10 rounded-full bg-blue-100 flex items-center justify-center flex-shrink-0">
|
||||
<span class="text-blue-600 font-medium text-sm md:text-base">
|
||||
{{ employee.firstName?.charAt(0) }}{{ employee.lastName?.charAt(0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-medium text-gray-900 text-sm md:text-base">{{ employee.firstName }} {{ employee.lastName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions align="end" class="mt-4">
|
||||
<button mat-button (click)="closeModal()">Close</button>
|
||||
<mat-dialog-actions align="end" class="!px-3 md:!px-6 !py-4 !mt-4 border-t">
|
||||
<button mat-button
|
||||
(click)="closeModal()"
|
||||
class="text-sm md:text-base hover:bg-gray-100 py-2 px-4 md:px-6 rounded-md w-full">
|
||||
Close
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
|
@ -13,6 +13,7 @@ import {AsyncPipe} from "@angular/common";
|
||||
import {MatButton} from "@angular/material/button";
|
||||
import {DetailsComponent as EmployeeDetailsComponent} from "../../employee/details/details.component";
|
||||
import EmployeeApiService from "../../services/employee-api.service";
|
||||
import {MatIcon} from "@angular/material/icon";
|
||||
|
||||
@Component({
|
||||
selector: 'app-details',
|
||||
@ -21,7 +22,8 @@ import EmployeeApiService from "../../services/employee-api.service";
|
||||
MatDialogContent,
|
||||
MatDialogTitle,
|
||||
MatDialogActions,
|
||||
MatButton
|
||||
MatButton,
|
||||
MatIcon
|
||||
],
|
||||
templateUrl: './details.component.html',
|
||||
styleUrl: './details.component.css'
|
||||
|
@ -1,24 +1,43 @@
|
||||
<h2 mat-dialog-title>Edit Qualification</h2>
|
||||
<mat-dialog-content>
|
||||
<form [formGroup]="qualificationForm" (ngSubmit)="edit()">
|
||||
<div class="!space-y-4">
|
||||
<h2 mat-dialog-title class="text-xl md:text-2xl font-semibold text-gray-800 mb-3 md:mb-4">Edit Qualification</h2>
|
||||
|
||||
<mat-dialog-content class="!px-3 md:!px-6">
|
||||
<form [formGroup]="qualificationForm" (ngSubmit)="edit()" class="w-full min-w-[280px] md:min-w-[400px]">
|
||||
<div class="space-y-4 md:space-y-6">
|
||||
@if (apiErrorMessage) {
|
||||
<mat-error>{{ apiErrorMessage }}</mat-error>
|
||||
<div class="bg-red-50 p-3 md:p-4 rounded-lg">
|
||||
<div class="flex items-start space-x-2 md:space-x-3">
|
||||
<mat-icon class="text-red-600 text-xl md:text-2xl">error_outline</mat-icon>
|
||||
<mat-error class="text-sm md:text-base text-red-700">{{ apiErrorMessage }}</mat-error>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<mat-form-field class="!w-full">
|
||||
<mat-label>Skill</mat-label>
|
||||
<input matInput
|
||||
formControlName="skill"
|
||||
required>
|
||||
<mat-error *ngIf="isFieldInvalid('skill')">
|
||||
{{ getErrorMessage('skill') }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<div class="bg-gray-50 p-3 md:p-4 rounded-lg space-y-3">
|
||||
<mat-form-field class="w-full">
|
||||
<mat-label>Skill</mat-label>
|
||||
<input matInput
|
||||
formControlName="skill"
|
||||
placeholder="Enter skill name"
|
||||
required>
|
||||
<mat-hint class="text-sm">Enter the skill name</mat-hint>
|
||||
<mat-error *ngIf="isFieldInvalid('skill')" class="text-sm">
|
||||
{{ getErrorMessage('skill') }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button mat-dialog-close>Cancel</button>
|
||||
<button mat-flat-button color="primary" type="submit">Edit</button>
|
||||
<mat-dialog-actions align="end" class="!px-0 !mb-0 flex flex-col sm:flex-row w-full gap-3">
|
||||
<button mat-button
|
||||
mat-dialog-close
|
||||
class="text-sm md:text-base hover:bg-gray-100 py-2 px-4 md:px-6 rounded-md w-full sm:flex-1">
|
||||
Cancel
|
||||
</button>
|
||||
<button mat-flat-button
|
||||
color="primary"
|
||||
type="submit"
|
||||
class="!ml-0 text-sm md:text-base py-2 px-4 md:px-6 rounded-md w-full sm:flex-1">
|
||||
Save Changes
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -9,10 +9,11 @@ import {
|
||||
MatDialogTitle
|
||||
} from "@angular/material/dialog";
|
||||
import {MatButton} from "@angular/material/button";
|
||||
import {MatError, MatFormField, MatLabel} from "@angular/material/form-field";
|
||||
import {MatError, MatFormField, MatHint, MatLabel} from "@angular/material/form-field";
|
||||
import {MatInput} from "@angular/material/input";
|
||||
import {NgIf} from "@angular/common";
|
||||
import {Qualification} from "../Qualification";
|
||||
import {MatIcon} from "@angular/material/icon";
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-qualification',
|
||||
@ -28,7 +29,9 @@ import {Qualification} from "../Qualification";
|
||||
MatLabel,
|
||||
NgIf,
|
||||
ReactiveFormsModule,
|
||||
MatDialogClose
|
||||
MatDialogClose,
|
||||
MatHint,
|
||||
MatIcon
|
||||
],
|
||||
templateUrl: './edit.component.html',
|
||||
styleUrl: './edit.component.css'
|
||||
|
@ -7,42 +7,50 @@
|
||||
<button mat-flat-button color="primary" class="!bg-blue-600 !text-white"
|
||||
(click)="openCreateModal()">
|
||||
<mat-icon class="!mr-2">add</mat-icon>
|
||||
Create Qualification
|
||||
Add Qualification
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (qualifications) {
|
||||
<div class="!overflow-x-auto !rounded-lg !bg-gray-50 !p-4">
|
||||
<table mat-table [dataSource]="qualifications" class="!w-full">
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef class="!text-left">ID</th>
|
||||
<td mat-cell *matCellDef="let qualification" class="!py-4">{{ qualification.id }}</td>
|
||||
</ng-container>
|
||||
|
||||
<table mat-table [dataSource]="qualifications" matSort class="!w-full">
|
||||
<ng-container matColumnDef="skill">
|
||||
<th mat-header-cell *matHeaderCellDef class="!text-left">Skill</th>
|
||||
<th mat-header-cell *matHeaderCellDef class="!text-left !w-full">Skill</th>
|
||||
<td mat-cell *matCellDef="let qualification" class="!py-4">
|
||||
<a
|
||||
class="!text-blue-600 hover:!underline cursor-pointer"
|
||||
[matTooltip]="'Click to view qualification details'"
|
||||
(click)="openDetailsModal(qualification)"
|
||||
>
|
||||
{{ qualification.skill }}
|
||||
</a>
|
||||
<div class="!flex !items-center">
|
||||
<div class="!h-10 !w-10 !rounded-full !bg-blue-100 !flex !items-center !justify-center !mr-3">
|
||||
<span class="!text-blue-600 !font-medium">
|
||||
{{ qualification.skill[0]?.toUpperCase() }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<a class="!text-blue-600 hover:!underline cursor-pointer"
|
||||
[matTooltip]="'Click to view qualification details'"
|
||||
(click)="openDetailsModal(qualification)">
|
||||
{{ qualification.skill }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
||||
<td mat-cell *matCellDef="let qualification" class="!py-4">
|
||||
<button mat-icon-button color="primary" [matTooltip]="'Edit qualification'"
|
||||
class="!mr-2" (click)="openEditModal(qualification)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" [matTooltip]="'Delete qualification'"
|
||||
(click)="openDeleteModal(qualification.id)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
<th mat-header-cell *matHeaderCellDef class="!text-right !w-[120px]">Actions</th>
|
||||
<td mat-cell *matCellDef="let qualification" class="!text-right !py-4 !whitespace-nowrap">
|
||||
<div class="!flex !justify-end !items-center !gap-1">
|
||||
<button mat-icon-button
|
||||
color="primary"
|
||||
[matTooltip]="'Edit qualification'"
|
||||
(click)="openEditModal(qualification)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button
|
||||
color="warn"
|
||||
[matTooltip]="'Delete qualification'"
|
||||
(click)="openDeleteModal(qualification.id)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
@ -22,6 +22,7 @@ import {MatCard, MatCardContent} from "@angular/material/card";
|
||||
import {MatTooltip} from "@angular/material/tooltip";
|
||||
import {MatProgressSpinner} from "@angular/material/progress-spinner";
|
||||
import {DetailsComponent} from "../details/details.component";
|
||||
import {MatSort} from "@angular/material/sort";
|
||||
|
||||
@Component({
|
||||
selector: 'app-qualifications',
|
||||
@ -43,14 +44,15 @@ import {DetailsComponent} from "../details/details.component";
|
||||
MatCard,
|
||||
MatCardContent,
|
||||
MatTooltip,
|
||||
MatProgressSpinner
|
||||
MatProgressSpinner,
|
||||
MatSort
|
||||
],
|
||||
templateUrl: './table.component.html',
|
||||
styleUrl: './table.component.css'
|
||||
})
|
||||
export class QualificationsComponent implements OnInit {
|
||||
public qualifications$!: Observable<Qualification[]>;
|
||||
public readonly displayedColumns: string[] = ['id', 'skill', 'actions'];
|
||||
public readonly displayedColumns: string[] = ['skill', 'actions'];
|
||||
|
||||
private readonly dialog: MatDialog = inject(MatDialog);
|
||||
private readonly qualificationService: QualificationService = inject(QualificationService);
|
||||
|
Reference in New Issue
Block a user