Enhance employee and qualification forms with hints and improved layouts #37

Merged
jleibl merged 1 commits from task/improve-layout-styling into main 2025-01-15 10:34:11 +00:00
18 changed files with 354 additions and 144 deletions
Showing only changes of commit e1f58acdee - Show all commits

View File

@ -6,12 +6,14 @@
<mat-form-field class="!w-full"> <mat-form-field class="!w-full">
<mat-label>First Name</mat-label> <mat-label>First Name</mat-label>
<input matInput formControlName="firstName" required> <input matInput formControlName="firstName" required>
<mat-hint>Enter the first name</mat-hint>
<mat-error *ngIf="errors['firstName']">{{errors['firstName']}}</mat-error> <mat-error *ngIf="errors['firstName']">{{errors['firstName']}}</mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field class="!w-full"> <mat-form-field class="!w-full">
<mat-label>Last Name</mat-label> <mat-label>Last Name</mat-label>
<input matInput formControlName="lastName" required> <input matInput formControlName="lastName" required>
<mat-hint>Enter the last name</mat-hint>
<mat-error *ngIf="errors['lastName']">{{errors['lastName']}}</mat-error> <mat-error *ngIf="errors['lastName']">{{errors['lastName']}}</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
@ -19,6 +21,7 @@
<mat-form-field class="!w-full"> <mat-form-field class="!w-full">
<mat-label>Street</mat-label> <mat-label>Street</mat-label>
<input matInput formControlName="street" required> <input matInput formControlName="street" required>
<mat-hint>Enter the street address</mat-hint>
<mat-error *ngIf="errors['street']">{{errors['street']}}</mat-error> <mat-error *ngIf="errors['street']">{{errors['street']}}</mat-error>
</mat-form-field> </mat-form-field>
@ -26,12 +29,14 @@
<mat-form-field class="!w-full"> <mat-form-field class="!w-full">
<mat-label>City</mat-label> <mat-label>City</mat-label>
<input matInput formControlName="city" required> <input matInput formControlName="city" required>
<mat-hint>Enter the city</mat-hint>
<mat-error *ngIf="errors['city']">{{errors['city']}}</mat-error> <mat-error *ngIf="errors['city']">{{errors['city']}}</mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field class="!w-1/2"> <mat-form-field class="!w-1/2">
<mat-label>Postcode</mat-label> <mat-label>Postcode</mat-label>
<input matInput formControlName="postcode" minlength="5" maxlength="5" type="number" required> <input matInput formControlName="postcode" minlength="5" maxlength="5" type="number" required>
<mat-hint>Enter postcode</mat-hint>
<mat-error *ngIf="errors['postcode']">{{errors['postcode']}}</mat-error> <mat-error *ngIf="errors['postcode']">{{errors['postcode']}}</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
@ -39,11 +44,13 @@
<mat-form-field class="!w-full"> <mat-form-field class="!w-full">
<mat-label>Phone</mat-label> <mat-label>Phone</mat-label>
<input matInput formControlName="phone" required> <input matInput formControlName="phone" required>
<mat-hint>Enter the phone number</mat-hint>
<mat-error *ngIf="errors['phone']">{{errors['phone']}}</mat-error> <mat-error *ngIf="errors['phone']">{{errors['phone']}}</mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field class="!w-full"> <mat-form-field class="!w-full">
<mat-label>Qualifications</mat-label> <mat-label>Qualifications</mat-label>
<mat-hint>Select the qualifications</mat-hint>
<mat-select formControlName="qualifications" multiple> <mat-select formControlName="qualifications" multiple>
<mat-option *ngFor="let qualification of qualifications" [value]="qualification.id"> <mat-option *ngFor="let qualification of qualifications" [value]="qualification.id">
{{qualification.skill}} {{qualification.skill}}
@ -52,9 +59,18 @@
<mat-error *ngIf="errors['qualifications']">{{errors['qualifications']}}</mat-error> <mat-error *ngIf="errors['qualifications']">{{errors['qualifications']}}</mat-error>
</mat-form-field> </mat-form-field>
<mat-dialog-actions align="end"> <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>Cancel</button> <button mat-button
<button mat-flat-button color="primary" type="submit">Submit</button> mat-dialog-close
class="text-sm md:text-base hover:bg-gray-100 py-2 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-6 rounded-md w-full sm:flex-1">
Submit
</button>
</mat-dialog-actions> </mat-dialog-actions>
</div> </div>
</form> </form>

View File

@ -1,6 +1,6 @@
import {Component, inject, OnInit} from '@angular/core'; import {Component, inject, OnInit} from '@angular/core';
import {AbstractControl, FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; import {AbstractControl, FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms";
import {MatFormField, MatLabel} from "@angular/material/form-field"; import {MatFormField, MatHint, MatLabel} from "@angular/material/form-field";
import {MatError, MatInput} from "@angular/material/input"; import {MatError, MatInput} from "@angular/material/input";
import {MatButton} from "@angular/material/button"; import {MatButton} from "@angular/material/button";
import { import {
@ -36,6 +36,7 @@ import {debounceTime} from "rxjs";
MatSelect, MatSelect,
NgForOf, NgForOf,
MatError, MatError,
MatHint
], ],
templateUrl: './create.component.html', templateUrl: './create.component.html',
standalone: true, standalone: true,

View File

@ -1,6 +1,33 @@
<h2 mat-dialog-title>Delete {{employee.firstName}} {{employee.lastName}}</h2> <h2 mat-dialog-title class="text-xl md:text-2xl font-semibold text-gray-800 mb-3 md:mb-4">Delete Employee</h2>
<mat-dialog-content>Are you sure you want to delete {{employee.firstName}} {{employee.lastName}}? This cant be undone.</mat-dialog-content>
<mat-dialog-actions> <mat-dialog-content class="!px-3 md:!px-6">
<button mat-button [mat-dialog-close]="false">Cancel</button> <div class="w-full min-w-[280px] md:min-w-[400px] space-y-4 md:space-y-6">
<button mat-button mat-dialog-close (click)="deleteEmployee(employee.id ?? 0)" cdkFocusInitial>Delete</button> <div class="bg-amber-50 p-3 md:p-4 rounded-lg border border-amber-200">
</mat-dialog-actions> <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 {{employee.firstName}} {{employee.lastName}}?
</p>
<p class="text-gray-600 mt-1 text-xs md:text-sm">This action cannot be undone.</p>
</div>
</div>
</div>
<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]="false"
class="text-sm md:text-base hover:bg-gray-100 py-2 px-6 rounded-md w-full sm:flex-1">
Cancel
</button>
<button mat-flat-button
color="warn"
(click)="deleteEmployee(employee.id ?? 0)"
mat-dialog-close
class="!ml-0 text-sm md:text-base py-2 px-6 rounded-md w-full sm:flex-1"
cdkFocusInitial>
Delete
</button>
</mat-dialog-actions>
</div>
</mat-dialog-content>

View File

@ -8,6 +8,7 @@ import {
MatDialogTitle MatDialogTitle
} from "@angular/material/dialog"; } from "@angular/material/dialog";
import {MatButton} from "@angular/material/button"; import {MatButton} from "@angular/material/button";
import {MatIcon} from "@angular/material/icon";
import EmployeeApiService from "../../services/employee-api.service"; import EmployeeApiService from "../../services/employee-api.service";
@Component({ @Component({
@ -17,7 +18,8 @@ import EmployeeApiService from "../../services/employee-api.service";
MatDialogTitle, MatDialogTitle,
MatDialogActions, MatDialogActions,
MatButton, MatButton,
MatDialogClose MatDialogClose,
MatIcon
], ],
templateUrl: './delete.component.html', templateUrl: './delete.component.html',
standalone: true, standalone: true,

View File

@ -1,27 +1,68 @@
<h2 mat-dialog-title>{{ employee.firstName }} {{ employee.lastName }}</h2> <h2 mat-dialog-title class="text-2xl font-semibold text-gray-800 mb-4">Employee Details</h2>
<mat-dialog-content> <mat-dialog-content class="!px-4 sm:!px-6">
<div class="flex"> <div class="w-full min-w-[280px] sm:min-w-[500px] space-y-6 sm:space-y-8">
<div class="flex-1"> <div class="flex flex-col sm:flex-row items-center sm:items-start text-center sm:text-left space-y-4 sm:space-y-0 sm:space-x-4">
<p><strong>ID:</strong> {{ employee.id }}</p> <div class="h-20 w-20 sm:h-16 sm:w-16 rounded-full bg-blue-100 flex items-center justify-center flex-shrink-0">
<p><strong>First Name:</strong> {{ employee.firstName }}</p> <span class="text-blue-600 text-2xl sm:text-xl font-medium">
<p><strong>Last Name:</strong> {{ employee.lastName }}</p> {{ employee.firstName?.charAt(0) ?? '' }}{{ employee.lastName?.charAt(0) ?? '' }}
<p><strong>Phone:</strong> {{ employee.phone }}</p> </span>
<p><strong>Postcode:</strong> {{ employee.postcode }}</p> </div>
<p><strong>Street:</strong> {{ employee.street }}</p> <div>
<p><strong>City:</strong> {{ employee.city }}</p> <h3 class="text-xl font-medium text-gray-900">{{ employee.firstName }} {{ employee.lastName }}</h3>
<p><strong>Skills:</strong></p> <p class="text-gray-500">ID: {{ employee.id }}</p>
<ul> </div>
@for (skill of employee.skillSet; track null) { </div>
<li> <strong></strong> {{skill.skill}}</li>
<div class="space-y-3 sm:space-y-4">
<h4 class="text-lg font-medium text-gray-900 mb-2 sm:mb-3">Contact Information</h4>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1">
<p class="text-sm text-gray-500">Phone</p>
<p class="text-gray-900 break-all">{{ employee.phone }}</p>
</div>
</div>
</div>
<div class="space-y-3 sm:space-y-4">
<h4 class="text-lg font-medium text-gray-900 mb-2 sm:mb-3">Address</h4>
<div class="bg-gray-50 p-3 sm:p-4 rounded-lg space-y-3">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
<div class="space-y-1">
<p class="text-sm text-gray-500">Street</p>
<p class="text-gray-900 break-words">{{ employee.street }}</p>
</div>
<div class="space-y-1">
<p class="text-sm text-gray-500">City</p>
<p class="text-gray-900">{{ employee.city }}</p>
</div>
</div>
<div class="space-y-1">
<p class="text-sm text-gray-500">Postcode</p>
<p class="text-gray-900">{{ employee.postcode }}</p>
</div>
</div>
</div>
<div class="space-y-3 sm:space-y-4">
<h4 class="text-lg font-medium text-gray-900 mb-2 sm:mb-3">Qualifications</h4>
<div class="flex flex-wrap gap-2">
@for (skill of employee.skillSet; track skill.id) {
<div class="bg-blue-50 text-blue-700 px-3 py-1 rounded-full text-sm font-medium">
{{skill.skill}}
</div>
} @empty { } @empty {
<li><strong>-</strong></li> <p class="text-gray-500 italic">No qualifications added</p>
} }
</ul> </div>
</div> </div>
</div> </div>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions align="end" class="mt-4"> <mat-dialog-actions align="end" class="!px-4 sm:!px-6 !py-4 !mt-4 border-t">
<button mat-button (click)="closeModal()">Close</button> <button mat-button
(click)="closeModal()"
class="text-sm md:text-base hover:bg-gray-100 py-2 px-6 rounded-md w-full">
Close
</button>
</mat-dialog-actions> </mat-dialog-actions>

View File

@ -6,34 +6,40 @@
<mat-form-field class="!w-full"> <mat-form-field class="!w-full">
<mat-label>First Name</mat-label> <mat-label>First Name</mat-label>
<input matInput formControlName="firstName" required> <input matInput formControlName="firstName" required>
<mat-hint>Enter the first name</mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="!w-full"> <mat-form-field class="!w-full">
<mat-label>Last Name</mat-label> <mat-label>Last Name</mat-label>
<input matInput formControlName="lastName" required> <input matInput formControlName="lastName" required>
<mat-hint>Enter the last name</mat-hint>
</mat-form-field> </mat-form-field>
</div> </div>
<mat-form-field class="!w-full"> <mat-form-field class="!w-full">
<mat-label>Street</mat-label> <mat-label>Street</mat-label>
<input matInput formControlName="street" required> <input matInput formControlName="street" required>
<mat-hint>Enter the street address</mat-hint>
</mat-form-field> </mat-form-field>
<div class="flex gap-x-4"> <div class="flex gap-x-4">
<mat-form-field class="!w-full"> <mat-form-field class="!w-full">
<mat-label>City</mat-label> <mat-label>City</mat-label>
<input matInput formControlName="city" required> <input matInput formControlName="city" required>
<mat-hint>Enter the city</mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="!w-1/2"> <mat-form-field class="!w-1/2">
<mat-label>Postcode</mat-label> <mat-label>Postcode</mat-label>
<input matInput formControlName="postcode" minlength="5" maxlength="5" type="number" required> <input matInput formControlName="postcode" minlength="5" maxlength="5" type="number" required>
<mat-hint>Enter postcode</mat-hint>
</mat-form-field> </mat-form-field>
</div> </div>
<mat-form-field class="!w-full"> <mat-form-field class="!w-full">
<mat-label>Phone</mat-label> <mat-label>Phone</mat-label>
<input matInput formControlName="phone" required> <input matInput formControlName="phone" required>
<mat-hint>Enter phone number</mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="!w-full"> <mat-form-field class="!w-full">
@ -43,11 +49,21 @@
{{qualification.skill}} {{qualification.skill}}
</mat-option> </mat-option>
</mat-select> </mat-select>
<mat-hint>Select qualifications</mat-hint>
</mat-form-field> </mat-form-field>
<mat-dialog-actions align="end"> <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>Cancel</button> <button mat-button
<button mat-flat-button color="primary" type="submit">Submit</button> mat-dialog-close
class="text-sm md:text-base hover:bg-gray-100 py-2 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-6 rounded-md w-full sm:flex-1">
Submit
</button>
</mat-dialog-actions> </mat-dialog-actions>
</div> </div>
</form> </form>

View File

@ -9,7 +9,7 @@ import {
MatDialogTitle MatDialogTitle
} from "@angular/material/dialog"; } from "@angular/material/dialog";
import {NgForOf, NgIf} from "@angular/common"; import {NgForOf, NgIf} from "@angular/common";
import {MatFormField} from "@angular/material/form-field"; import {MatFormField, MatHint} from "@angular/material/form-field";
import {MatInput, MatLabel} from "@angular/material/input"; import {MatInput, MatLabel} from "@angular/material/input";
import {MatButton} from "@angular/material/button"; import {MatButton} from "@angular/material/button";
import {Employee} from "../Employee"; import {Employee} from "../Employee";
@ -33,7 +33,8 @@ import {MatOption, MatSelect} from "@angular/material/select";
MatLabel, MatLabel,
MatSelect, MatSelect,
MatOption, MatOption,
NgForOf NgForOf,
MatHint
], ],
templateUrl: './edit.component.html', templateUrl: './edit.component.html',
standalone: true, standalone: true,

View File

@ -14,7 +14,7 @@
<div class="!overflow-x-auto !rounded-lg !bg-gray-50 !p-4"> <div class="!overflow-x-auto !rounded-lg !bg-gray-50 !p-4">
<table mat-table [dataSource]="employees" matSort class="!w-full"> <table mat-table [dataSource]="employees" matSort class="!w-full">
<ng-container matColumnDef="name"> <ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef class="!text-left"> Name</th> <th mat-header-cell *matHeaderCellDef class="!text-left !w-full"> Name</th>
<td mat-cell *matCellDef="let employee" class="!py-4"> <td mat-cell *matCellDef="let employee" class="!py-4">
<div class="!flex !items-center"> <div class="!flex !items-center">
<div class="!h-10 !w-10 !rounded-full !bg-blue-100 !flex !items-center !justify-center !mr-3"> <div class="!h-10 !w-10 !rounded-full !bg-blue-100 !flex !items-center !justify-center !mr-3">
@ -35,16 +35,22 @@
</ng-container> </ng-container>
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef class="!text-right"> Actions</th> <th mat-header-cell *matHeaderCellDef class="!text-right !w-[120px]"> Actions</th>
<td mat-cell *matCellDef="let employee" class="!text-right !py-4"> <td mat-cell *matCellDef="let employee" class="!text-right !py-4 !whitespace-nowrap">
<button mat-icon-button color="primary" [matTooltip]="'Edit employee'" class="!mr-2" <div class="!flex !justify-end !items-center !gap-1">
(click)="showEditEmployeeModal(employee)"> <button mat-icon-button
<mat-icon>edit</mat-icon> color="primary"
</button> [matTooltip]="'Edit employee'"
<button mat-icon-button color="warn" [matTooltip]="'Delete employee'" (click)="showEditEmployeeModal(employee)">
(click)="openDeleteDialogue(employee)"> <mat-icon>edit</mat-icon>
<mat-icon>delete</mat-icon> </button>
</button> <button mat-icon-button
color="warn"
[matTooltip]="'Delete employee'"
(click)="openDeleteDialogue(employee)">
<mat-icon>delete</mat-icon>
</button>
</div>
</td> </td>
</ng-container> </ng-container>

View File

@ -1,24 +1,43 @@
<h2 mat-dialog-title>Create Qualification</h2> <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>
<form [formGroup]="qualificationForm" (ngSubmit)="create()"> <mat-dialog-content class="!px-3 md:!px-6">
<div class="!space-y-4"> <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) { @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"> <div class="bg-gray-50 p-3 md:p-4 rounded-lg space-y-3">
<mat-label>Skill</mat-label> <mat-form-field class="w-full">
<input matInput <mat-label>Skill</mat-label>
formControlName="skill" <input matInput
required> formControlName="skill"
<mat-error *ngIf="isFieldInvalid('skill')"> placeholder="Enter skill name"
{{ getErrorMessage('skill') }} required>
</mat-error> <mat-hint class="text-sm">Enter the skill name</mat-hint>
</mat-form-field> <mat-error *ngIf="isFieldInvalid('skill')" class="text-sm">
{{ getErrorMessage('skill') }}
</mat-error>
</mat-form-field>
</div>
<mat-dialog-actions align="end"> <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>Cancel</button> <button mat-button
<button mat-flat-button color="primary" type="submit">Create</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> </mat-dialog-actions>
</div> </div>
</form> </form>

View File

@ -9,9 +9,10 @@ import {
MatDialogTitle MatDialogTitle
} from "@angular/material/dialog"; } from "@angular/material/dialog";
import {NgIf} from "@angular/common"; 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 {MatButton} from "@angular/material/button";
import {MatInput} from "@angular/material/input"; import {MatInput} from "@angular/material/input";
import {MatIcon} from "@angular/material/icon";
import {filter} from "rxjs"; import {filter} from "rxjs";
@Component({ @Component({
@ -27,7 +28,9 @@ import {filter} from "rxjs";
MatDialogActions, MatDialogActions,
MatButton, MatButton,
MatInput, MatInput,
MatDialogClose MatDialogClose,
MatHint,
MatIcon
], ],
templateUrl: './create.component.html', templateUrl: './create.component.html',
styleUrl: './create.component.css' styleUrl: './create.component.css'

View File

@ -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> <mat-dialog-content class="!px-3 md:!px-6">
@if (apiError) { <div class="w-full min-w-[280px] md:min-w-[400px] space-y-4 md:space-y-6">
<div class="!text-red-600 !mb-4 !p-3 !bg-red-50 !rounded"> @if (apiError) {
<p>{{ apiError }}</p> <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> </div>
}
Are you sure you want to delete this qualification? This can't be undone.
</mat-dialog-content>
<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()">Cancel</button> <button mat-button
<button mat-button (click)="delete()" cdkFocusInitial>Delete</button> (click)="closeModal()"
</mat-dialog-actions> 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>

View File

@ -10,6 +10,8 @@ import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import QualificationService from "../../services/qualification.service"; import QualificationService from "../../services/qualification.service";
import {MatButton} from "@angular/material/button"; import {MatButton} from "@angular/material/button";
import {HttpErrorResponse} from "@angular/common/http"; import {HttpErrorResponse} from "@angular/common/http";
import { MatError } from '@angular/material/form-field'
import {MatIcon} from "@angular/material/icon";
@Component({ @Component({
selector: 'app-delete-qualification', selector: 'app-delete-qualification',
@ -19,7 +21,9 @@ import {HttpErrorResponse} from "@angular/common/http";
MatDialogTitle, MatDialogTitle,
ReactiveFormsModule, ReactiveFormsModule,
MatDialogActions, MatDialogActions,
MatButton MatButton,
MatError,
MatIcon
], ],
templateUrl: './delete.component.html', templateUrl: './delete.component.html',
standalone: true, standalone: true,

View File

@ -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 {{ qualification.skill }} Developers
</h2> </h2>
<mat-dialog-content class="px-1"> <mat-dialog-content class="!px-3 md:!px-6">
@if (employees$ | async; as employees) { <div class="w-full min-w-[280px] md:min-w-[400px] space-y-4 md:space-y-6">
@if (employees.length === 0) { @if (employees$ | async; as employees) {
<p class="text-gray-500 italic">No employees found with this qualification.</p> @if (employees.length === 0) {
} @else { <div class="bg-gray-50 p-3 md:p-4 rounded-lg text-center">
<div class="space-y-1"> <mat-icon class="text-gray-400 text-xl md:text-2xl mb-2">person_off</mat-icon>
@for (employee of employees; track employee.id) { <p class="text-gray-600 text-sm md:text-base">No employees found with this qualification.</p>
<a </div>
class="block w-full px-4 py-2 text-blue-600 rounded-lg hover:bg-blue-50 transition-colors cursor-pointer" } @else {
(click)="openEmployeeDetailsModal(employee.id)" <div class="bg-gray-50 p-3 md:p-4 rounded-lg space-y-2">
> @for (employee of employees; track employee.id) {
<span class="font-medium">{{ employee.firstName }} {{ employee.lastName }}</span> <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"
</a> (click)="openEmployeeDetailsModal(employee.id)">
} <div class="flex items-center space-x-3">
</div> <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-content>
<mat-dialog-actions align="end" class="mt-4"> <mat-dialog-actions align="end" class="!px-3 md:!px-6 !py-4 !mt-4 border-t">
<button mat-button (click)="closeModal()">Close</button> <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> </mat-dialog-actions>

View File

@ -13,6 +13,7 @@ import {AsyncPipe} from "@angular/common";
import {MatButton} from "@angular/material/button"; import {MatButton} from "@angular/material/button";
import {DetailsComponent as EmployeeDetailsComponent} from "../../employee/details/details.component"; import {DetailsComponent as EmployeeDetailsComponent} from "../../employee/details/details.component";
import EmployeeApiService from "../../services/employee-api.service"; import EmployeeApiService from "../../services/employee-api.service";
import {MatIcon} from "@angular/material/icon";
@Component({ @Component({
selector: 'app-details', selector: 'app-details',
@ -21,7 +22,8 @@ import EmployeeApiService from "../../services/employee-api.service";
MatDialogContent, MatDialogContent,
MatDialogTitle, MatDialogTitle,
MatDialogActions, MatDialogActions,
MatButton MatButton,
MatIcon
], ],
templateUrl: './details.component.html', templateUrl: './details.component.html',
styleUrl: './details.component.css' styleUrl: './details.component.css'

View File

@ -1,24 +1,43 @@
<h2 mat-dialog-title>Edit Qualification</h2> <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>
<form [formGroup]="qualificationForm" (ngSubmit)="edit()"> <mat-dialog-content class="!px-3 md:!px-6">
<div class="!space-y-4"> <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) { @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"> <div class="bg-gray-50 p-3 md:p-4 rounded-lg space-y-3">
<mat-label>Skill</mat-label> <mat-form-field class="w-full">
<input matInput <mat-label>Skill</mat-label>
formControlName="skill" <input matInput
required> formControlName="skill"
<mat-error *ngIf="isFieldInvalid('skill')"> placeholder="Enter skill name"
{{ getErrorMessage('skill') }} required>
</mat-error> <mat-hint class="text-sm">Enter the skill name</mat-hint>
</mat-form-field> <mat-error *ngIf="isFieldInvalid('skill')" class="text-sm">
{{ getErrorMessage('skill') }}
</mat-error>
</mat-form-field>
</div>
<mat-dialog-actions align="end"> <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>Cancel</button> <button mat-button
<button mat-flat-button color="primary" type="submit">Edit</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> </mat-dialog-actions>
</div> </div>
</form> </form>

View File

@ -9,10 +9,11 @@ import {
MatDialogTitle MatDialogTitle
} from "@angular/material/dialog"; } from "@angular/material/dialog";
import {MatButton} from "@angular/material/button"; 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 {MatInput} from "@angular/material/input";
import {NgIf} from "@angular/common"; import {NgIf} from "@angular/common";
import {Qualification} from "../Qualification"; import {Qualification} from "../Qualification";
import {MatIcon} from "@angular/material/icon";
@Component({ @Component({
selector: 'app-edit-qualification', selector: 'app-edit-qualification',
@ -28,7 +29,9 @@ import {Qualification} from "../Qualification";
MatLabel, MatLabel,
NgIf, NgIf,
ReactiveFormsModule, ReactiveFormsModule,
MatDialogClose MatDialogClose,
MatHint,
MatIcon
], ],
templateUrl: './edit.component.html', templateUrl: './edit.component.html',
styleUrl: './edit.component.css' styleUrl: './edit.component.css'

View File

@ -7,42 +7,50 @@
<button mat-flat-button color="primary" class="!bg-blue-600 !text-white" <button mat-flat-button color="primary" class="!bg-blue-600 !text-white"
(click)="openCreateModal()"> (click)="openCreateModal()">
<mat-icon class="!mr-2">add</mat-icon> <mat-icon class="!mr-2">add</mat-icon>
Create Qualification Add Qualification
</button> </button>
</div> </div>
@if (qualifications) { @if (qualifications) {
<div class="!overflow-x-auto !rounded-lg !bg-gray-50 !p-4"> <div class="!overflow-x-auto !rounded-lg !bg-gray-50 !p-4">
<table mat-table [dataSource]="qualifications" class="!w-full"> <table mat-table [dataSource]="qualifications" matSort 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>
<ng-container matColumnDef="skill"> <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"> <td mat-cell *matCellDef="let qualification" class="!py-4">
<a <div class="!flex !items-center">
class="!text-blue-600 hover:!underline cursor-pointer" <div class="!h-10 !w-10 !rounded-full !bg-blue-100 !flex !items-center !justify-center !mr-3">
[matTooltip]="'Click to view qualification details'" <span class="!text-blue-600 !font-medium">
(click)="openDetailsModal(qualification)" {{ qualification.skill[0]?.toUpperCase() }}
> </span>
{{ qualification.skill }} </div>
</a> <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> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th> <th mat-header-cell *matHeaderCellDef class="!text-right !w-[120px]">Actions</th>
<td mat-cell *matCellDef="let qualification" class="!py-4"> <td mat-cell *matCellDef="let qualification" class="!text-right !py-4 !whitespace-nowrap">
<button mat-icon-button color="primary" [matTooltip]="'Edit qualification'" <div class="!flex !justify-end !items-center !gap-1">
class="!mr-2" (click)="openEditModal(qualification)"> <button mat-icon-button
<mat-icon>edit</mat-icon> color="primary"
</button> [matTooltip]="'Edit qualification'"
<button mat-icon-button color="warn" [matTooltip]="'Delete qualification'" (click)="openEditModal(qualification)">
(click)="openDeleteModal(qualification.id)"> <mat-icon>edit</mat-icon>
<mat-icon>delete</mat-icon> </button>
</button> <button mat-icon-button
color="warn"
[matTooltip]="'Delete qualification'"
(click)="openDeleteModal(qualification.id)">
<mat-icon>delete</mat-icon>
</button>
</div>
</td> </td>
</ng-container> </ng-container>

View File

@ -22,6 +22,7 @@ import {MatCard, MatCardContent} from "@angular/material/card";
import {MatTooltip} from "@angular/material/tooltip"; import {MatTooltip} from "@angular/material/tooltip";
import {MatProgressSpinner} from "@angular/material/progress-spinner"; import {MatProgressSpinner} from "@angular/material/progress-spinner";
import {DetailsComponent} from "../details/details.component"; import {DetailsComponent} from "../details/details.component";
import {MatSort} from "@angular/material/sort";
@Component({ @Component({
selector: 'app-qualifications', selector: 'app-qualifications',
@ -43,14 +44,15 @@ import {DetailsComponent} from "../details/details.component";
MatCard, MatCard,
MatCardContent, MatCardContent,
MatTooltip, MatTooltip,
MatProgressSpinner MatProgressSpinner,
MatSort
], ],
templateUrl: './table.component.html', templateUrl: './table.component.html',
styleUrl: './table.component.css' styleUrl: './table.component.css'
}) })
export class QualificationsComponent implements OnInit { export class QualificationsComponent implements OnInit {
public qualifications$!: Observable<Qualification[]>; 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 dialog: MatDialog = inject(MatDialog);
private readonly qualificationService: QualificationService = inject(QualificationService); private readonly qualificationService: QualificationService = inject(QualificationService);