feat: enhance loading spinner with progress indication and add warnin… (#49)
* feat: enhance loading spinner with progress indication and add warning for unplaced vacation days * Update src/app/services/calendar-service.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/app/components/calendar-year/calendar-year.scss Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/app/components/user-input/user-input.html Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix setTimeout async anti-pattern in calendar-year component (#50) * Initial plan * fix: replace setTimeout async anti-pattern with queueMicrotask and void Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
8 files changed
+157
-37
No files matched your search
@@ -2,7 +2,10 @@
|
||||
@if (isLoading()) {
|
||||
<div class="loading-overlay">
|
||||
<div class="loading-spinner">
|
||||
<mat-spinner></mat-spinner>
|
||||
<div class="spinner-container">
|
||||
<mat-spinner [mode]="'determinate'" [value]="loadingProgress()"></mat-spinner>
|
||||
<div class="spinner-text">{{ loadingProgress() }}%</div>
|
||||
</div>
|
||||
<p>Calcul en cours...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -51,25 +51,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
.spinner-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
::ng-deep .mat-mdc-progress-spinner {
|
||||
// Override Material spinner size
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.spinner-text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
pointer-events: none;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
@include mat.progress-spinner-overrides(
|
||||
(
|
||||
size: 0.5rem,
|
||||
active-indicator-width: 0.25rem,
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -21,6 +21,7 @@ export class CalendarYear {
|
||||
private calendarSettingsService = inject(CalendarSettingsService);
|
||||
|
||||
protected isLoading = signal(true);
|
||||
protected loadingProgress = signal(0);
|
||||
|
||||
// Use DateCacheService instead of creating Date objects
|
||||
private get currentMonth() {
|
||||
@@ -39,18 +40,26 @@ export class CalendarYear {
|
||||
console.log('Samedi Malin setting:', c);
|
||||
console.log('Optimizing vacations with data:', vacationData);
|
||||
this.isLoading.set(true);
|
||||
// Simulate server-side calculation with a small delay
|
||||
setTimeout(() => {
|
||||
if (this.selectedDates) {
|
||||
// Create a copy to avoid mutating the original signal data
|
||||
this.selectedDates.optimizeVacations(
|
||||
{ ...vacationData },
|
||||
this.calendarSettingsService,
|
||||
this.calendarService,
|
||||
);
|
||||
}
|
||||
this.isLoading.set(false);
|
||||
}, 1);
|
||||
this.loadingProgress.set(0);
|
||||
// Defer execution to allow UI to update
|
||||
queueMicrotask(() => {
|
||||
void (async () => {
|
||||
if (this.selectedDates) {
|
||||
// Create a copy to avoid mutating the original signal data
|
||||
const remainingDays = await this.selectedDates.optimizeVacations(
|
||||
{ ...vacationData },
|
||||
this.calendarSettingsService,
|
||||
this.calendarService,
|
||||
(progress: number) => {
|
||||
this.loadingProgress.set(progress);
|
||||
},
|
||||
);
|
||||
// Update the remaining vacation days signal
|
||||
this.userInput.remainingVacationDaysSignal.set(remainingDays);
|
||||
}
|
||||
this.isLoading.set(false);
|
||||
})();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -14,5 +14,16 @@
|
||||
<input matInput type="number" max="365" min="0" formControlName="Others" />
|
||||
</mat-form-field>
|
||||
</form>
|
||||
@if (hasRemainingDays()) {
|
||||
<mat-card class="mat-elevation-z2 warning-card">
|
||||
<mat-card-content class="warning-content">
|
||||
<mat-icon color="warn" aria-hidden="true">warning</mat-icon>
|
||||
<span>
|
||||
{{ remainingDays() }} jour{{ remainingDays() > 1 ? 's' : '' }} de vacances non
|
||||
placé{{ remainingDays() > 1 ? 's' : '' }}. Veuillez les placer manuellement.
|
||||
</span>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
}
|
||||
<app-calendar-settings></app-calendar-settings>
|
||||
</div>
|
||||
@@ -13,6 +13,16 @@
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.warning-card {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.warning-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit, OnDestroy, effect } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, effect, computed } from '@angular/core';
|
||||
import { MatFormField, MatLabel } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
@@ -6,6 +6,8 @@ import { UserInputService } from '../../services/user-input-service';
|
||||
import { VacationsNumber } from '../../services/calendar-service';
|
||||
import { debounceTime, takeUntil, Subject } from 'rxjs';
|
||||
import { CalendarSettings } from '../calendar-settings/calendar-settings';
|
||||
import { MatCard, MatCardContent } from '@angular/material/card';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-input',
|
||||
@@ -15,7 +17,9 @@ import { CalendarSettings } from '../calendar-settings/calendar-settings';
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
CalendarSettings,
|
||||
CalendarSettings,
|
||||
MatCard,
|
||||
MatCardContent,
|
||||
MatIcon,
|
||||
],
|
||||
templateUrl: './user-input.html',
|
||||
styleUrl: './user-input.scss',
|
||||
@@ -44,6 +48,13 @@ export class UserInput implements OnInit, OnDestroy {
|
||||
Others: new FormControl<number | null>(null),
|
||||
});
|
||||
|
||||
protected remainingDays = computed(() => {
|
||||
const remaining = this.userInputService.remainingVacationDaysSignal();
|
||||
return remaining.cp + remaining.rtt + remaining.other;
|
||||
});
|
||||
|
||||
protected hasRemainingDays = computed(() => this.remainingDays() > 0);
|
||||
|
||||
ngOnInit() {
|
||||
this.userInputForm.valueChanges
|
||||
.pipe(debounceTime(1500), takeUntil(this.destroy$))
|
||||
|
||||
Reference in new issue
Block a user