Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95f927386d | ||
|
|
efe7b3da42 | ||
|
|
54c2160f0d | ||
|
|
1f3789d82b | ||
|
|
3fab4c4922 | ||
|
|
df1b8a509d | ||
|
|
b97e9486e8 |
No files matched your search
@@ -57,8 +57,8 @@
|
|||||||
|
|
||||||
::ng-deep .mat-mdc-progress-spinner {
|
::ng-deep .mat-mdc-progress-spinner {
|
||||||
// Override Material spinner size
|
// Override Material spinner size
|
||||||
width: 100px !important;
|
width: 100px;
|
||||||
height: 100px !important;
|
height: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spinner-text {
|
.spinner-text {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
||||||
import { CalendarSettingsService } from '../../services/calendar-settings-service';
|
import { CalendarSettingsService } from '../../services/calendar-settings-service';
|
||||||
import { DateCacheService } from '../../services/date-cache.service';
|
import { DateCacheService } from '../../services/date-cache.service';
|
||||||
|
import { LoggerService } from '../../services/logger.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-calendar-year',
|
selector: 'app-calendar-year',
|
||||||
@@ -19,6 +20,7 @@ export class CalendarYear {
|
|||||||
private userInput = inject(UserInputService);
|
private userInput = inject(UserInputService);
|
||||||
private calendarService = inject(CalendarService);
|
private calendarService = inject(CalendarService);
|
||||||
private calendarSettingsService = inject(CalendarSettingsService);
|
private calendarSettingsService = inject(CalendarSettingsService);
|
||||||
|
private logger = inject(LoggerService);
|
||||||
|
|
||||||
protected isLoading = signal(true);
|
protected isLoading = signal(true);
|
||||||
protected loadingProgress = signal(0);
|
protected loadingProgress = signal(0);
|
||||||
@@ -41,23 +43,26 @@ export class CalendarYear {
|
|||||||
console.log('Optimizing vacations with data:', vacationData);
|
console.log('Optimizing vacations with data:', vacationData);
|
||||||
this.isLoading.set(true);
|
this.isLoading.set(true);
|
||||||
this.loadingProgress.set(0);
|
this.loadingProgress.set(0);
|
||||||
// Simulate server-side calculation with a small delay
|
// Defer execution to allow UI to update
|
||||||
setTimeout(async () => {
|
queueMicrotask(() => {
|
||||||
if (this.selectedDates) {
|
void (async () => {
|
||||||
// Create a copy to avoid mutating the original signal data
|
if (this.selectedDates) {
|
||||||
const remainingDays = await this.selectedDates.optimizeVacations(
|
// Create a copy to avoid mutating the original signal data
|
||||||
{ ...vacationData },
|
const remainingDays = await this.selectedDates.optimizeVacations(
|
||||||
this.calendarSettingsService,
|
{ ...vacationData },
|
||||||
this.calendarService,
|
this.calendarSettingsService,
|
||||||
(progress: number) => {
|
this.calendarService,
|
||||||
this.loadingProgress.set(progress);
|
(progress: number) => {
|
||||||
},
|
this.loadingProgress.set(progress);
|
||||||
);
|
},
|
||||||
// Update the remaining vacation days signal
|
this.logger,
|
||||||
this.userInput.remainingVacationDaysSignal.set(remainingDays);
|
);
|
||||||
}
|
// Update the remaining vacation days signal
|
||||||
this.isLoading.set(false);
|
this.userInput.remainingVacationDaysSignal.set(remainingDays);
|
||||||
}, 1);
|
}
|
||||||
|
this.isLoading.set(false);
|
||||||
|
})();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
@if (hasRemainingDays()) {
|
@if (hasRemainingDays()) {
|
||||||
<mat-card class="mat-elevation-z2 warning-card">
|
<mat-card class="mat-elevation-z2 warning-card">
|
||||||
<mat-card-content class="warning-content">
|
<mat-card-content class="warning-content">
|
||||||
<mat-icon color="warn">warning</mat-icon>
|
<mat-icon color="warn" aria-hidden="true">warning</mat-icon>
|
||||||
<span>
|
<span>
|
||||||
{{ remainingDays() }} jour{{ remainingDays() > 1 ? 's' : '' }} de vacances non
|
{{ remainingDays() }} jour{{ remainingDays() > 1 ? 's' : '' }} de vacances non
|
||||||
placé{{ remainingDays() > 1 ? 's' : '' }}. Veuillez les placer manuellement.
|
placé{{ remainingDays() > 1 ? 's' : '' }}. Veuillez les placer manuellement.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import weekOfYear from 'dayjs/plugin/weekOfYear';
|
|||||||
import isoWeek from 'dayjs/plugin/isoWeek';
|
import isoWeek from 'dayjs/plugin/isoWeek';
|
||||||
import { CalendarSettingsService } from './calendar-settings-service';
|
import { CalendarSettingsService } from './calendar-settings-service';
|
||||||
import { DateCacheService } from './date-cache.service';
|
import { DateCacheService } from './date-cache.service';
|
||||||
|
import { LoggerService } from './logger.service';
|
||||||
|
|
||||||
dayjs.extend(weekOfYear);
|
dayjs.extend(weekOfYear);
|
||||||
dayjs.extend(isoWeek);
|
dayjs.extend(isoWeek);
|
||||||
@@ -21,6 +22,7 @@ const allTypesOptions: HolidaysTypes.Options = {
|
|||||||
})
|
})
|
||||||
export class CalendarService {
|
export class CalendarService {
|
||||||
private dateCache = inject(DateCacheService);
|
private dateCache = inject(DateCacheService);
|
||||||
|
private logger = inject(LoggerService);
|
||||||
|
|
||||||
// Cache for holiday timestamps
|
// Cache for holiday timestamps
|
||||||
private holidayTimestampsCache: Set<number> | null = null;
|
private holidayTimestampsCache: Set<number> | null = null;
|
||||||
@@ -502,7 +504,7 @@ export class SelectedDates implements SelectedDateInterface {
|
|||||||
|
|
||||||
// Check if adjacent (next day) - this is the best case!
|
// Check if adjacent (next day) - this is the best case!
|
||||||
if (nextStartTime - currentEndTime === oneDayInMs) {
|
if (nextStartTime - currentEndTime === oneDayInMs) {
|
||||||
totalScore += 15; // Big bonus for adjacency
|
totalScore += 15; // 50% increased bonus for adjacency; adjacency is highly valued in the scoring system
|
||||||
} else if (nextStartTime > currentEndTime) {
|
} else if (nextStartTime > currentEndTime) {
|
||||||
// Penalty based on days between the two dates
|
// Penalty based on days between the two dates
|
||||||
const daysBetween = Math.floor((nextStartTime - currentEndTime) / oneDayInMs) - 1;
|
const daysBetween = Math.floor((nextStartTime - currentEndTime) / oneDayInMs) - 1;
|
||||||
@@ -541,6 +543,7 @@ export class SelectedDates implements SelectedDateInterface {
|
|||||||
calendarSettingsService: CalendarSettingsService,
|
calendarSettingsService: CalendarSettingsService,
|
||||||
calendarService: CalendarService,
|
calendarService: CalendarService,
|
||||||
progressCallback?: (progress: number) => void,
|
progressCallback?: (progress: number) => void,
|
||||||
|
logger?: LoggerService,
|
||||||
): Promise<VacationsNumber> {
|
): Promise<VacationsNumber> {
|
||||||
if (calendarSettingsService.get('samediMalin')()) this.samediMalin(vacationsNumber);
|
if (calendarSettingsService.get('samediMalin')()) this.samediMalin(vacationsNumber);
|
||||||
|
|
||||||
@@ -548,13 +551,18 @@ export class SelectedDates implements SelectedDateInterface {
|
|||||||
const totalDays = initialVacations.cp + initialVacations.rtt + initialVacations.other;
|
const totalDays = initialVacations.cp + initialVacations.rtt + initialVacations.other;
|
||||||
let processedDays = 0;
|
let processedDays = 0;
|
||||||
|
|
||||||
// Helper function to update progress
|
// Helper function to update progress, throttled to every 5%
|
||||||
|
let lastReportedProgress = 0;
|
||||||
|
const PROGRESS_STEP = 5; // percent
|
||||||
const updateProgress = async () => {
|
const updateProgress = async () => {
|
||||||
if (progressCallback && totalDays > 0) {
|
if (progressCallback && totalDays > 0) {
|
||||||
const progress = Math.round((processedDays / totalDays) * 100);
|
const progress = Math.round((processedDays / totalDays) * 100);
|
||||||
progressCallback(progress);
|
if (progress - lastReportedProgress >= PROGRESS_STEP || progress === 100) {
|
||||||
// Allow UI to update with a microtask
|
progressCallback(progress);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
lastReportedProgress = progress;
|
||||||
|
// Allow UI to update with a microtask
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -595,8 +603,8 @@ export class SelectedDates implements SelectedDateInterface {
|
|||||||
|
|
||||||
// Inform user if not all days were placed
|
// Inform user if not all days were placed
|
||||||
const remainingDays = vacationsNumber.cp + vacationsNumber.rtt + vacationsNumber.other;
|
const remainingDays = vacationsNumber.cp + vacationsNumber.rtt + vacationsNumber.other;
|
||||||
if (remainingDays > 0) {
|
if (remainingDays > 0 && logger) {
|
||||||
console.warn(
|
logger.warn(
|
||||||
`Optimization stopped: ${remainingDays} of ${totalDays} vacation days remain unplaced. ` +
|
`Optimization stopped: ${remainingDays} of ${totalDays} vacation days remain unplaced. ` +
|
||||||
`Too many days have equivalent placement quality - please place remaining days manually.`,
|
`Too many days have equivalent placement quality - please place remaining days manually.`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { LoggerService } from './logger.service';
|
||||||
|
|
||||||
|
describe('LoggerService', () => {
|
||||||
|
let service: LoggerService;
|
||||||
|
let consoleLogSpy: jasmine.Spy;
|
||||||
|
let consoleWarnSpy: jasmine.Spy;
|
||||||
|
let consoleErrorSpy: jasmine.Spy;
|
||||||
|
let consoleDebugSpy: jasmine.Spy;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(LoggerService);
|
||||||
|
|
||||||
|
// Spy on console methods
|
||||||
|
consoleLogSpy = spyOn(console, 'log');
|
||||||
|
consoleWarnSpy = spyOn(console, 'warn');
|
||||||
|
consoleErrorSpy = spyOn(console, 'error');
|
||||||
|
consoleDebugSpy = spyOn(console, 'debug');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log messages', () => {
|
||||||
|
service.log('test message');
|
||||||
|
expect(consoleLogSpy).toHaveBeenCalledWith('test message');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log warnings', () => {
|
||||||
|
service.warn('test warning');
|
||||||
|
expect(consoleWarnSpy).toHaveBeenCalledWith('test warning');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log errors', () => {
|
||||||
|
service.error('test error');
|
||||||
|
expect(consoleErrorSpy).toHaveBeenCalledWith('test error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log debug messages', () => {
|
||||||
|
service.debug('test debug');
|
||||||
|
expect(consoleDebugSpy).toHaveBeenCalledWith('test debug');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass additional arguments to console methods', () => {
|
||||||
|
const arg1 = { key: 'value' };
|
||||||
|
const arg2 = [1, 2, 3];
|
||||||
|
service.log('test', arg1, arg2);
|
||||||
|
expect(consoleLogSpy).toHaveBeenCalledWith('test', arg1, arg2);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger service that wraps console methods.
|
||||||
|
* Can be configured to disable logging in production environments.
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class LoggerService {
|
||||||
|
private isProduction = false; // Can be configured based on environment
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an informational message
|
||||||
|
*/
|
||||||
|
log(message: string, ...args: unknown[]): void {
|
||||||
|
if (!this.isProduction) {
|
||||||
|
console.log(message, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a warning message
|
||||||
|
*/
|
||||||
|
warn(message: string, ...args: unknown[]): void {
|
||||||
|
if (!this.isProduction) {
|
||||||
|
console.warn(message, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an error message
|
||||||
|
*/
|
||||||
|
error(message: string, ...args: unknown[]): void {
|
||||||
|
// Always log errors, even in production
|
||||||
|
console.error(message, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a debug message
|
||||||
|
*/
|
||||||
|
debug(message: string, ...args: unknown[]): void {
|
||||||
|
if (!this.isProduction) {
|
||||||
|
console.debug(message, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user