Enhance calendar settings dialog and optimize date selection logic

This commit is contained in:
nessar committed 2025-11-01 20:59:50 +01:00
1 parent d01c93c763
commit 8aad6e4cef
6 files changed
+59 -31

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
<app-dark-light-mode-button class="modeButton"></app-dark-light-mode-button> <app-dark-light-mode-button class="modeButton"></app-dark-light-mode-button>
<main class="main"> <main class="main">
<h1>{{ title() }}</h1> <h1 style="display: flex; gap: 1rem"><mat-icon>beach_access</mat-icon> {{ title() }}</h1>
<div class="content"> <div class="content">
<app-user-input></app-user-input> <app-user-input></app-user-input>
<mat-divider [vertical]="true" /> <mat-divider [vertical]="true" />
+2
View File
@@ -5,6 +5,7 @@ import { MatDivider } from '@angular/material/divider';
import { UserInput } from './components/user-input/user-input'; import { UserInput } from './components/user-input/user-input';
import { CalendarYear } from './components/calendar-year/calendar-year'; import { CalendarYear } from './components/calendar-year/calendar-year';
import { DarkLightModeButton } from './components/dark-light-mode-button/dark-light-mode-button'; import { DarkLightModeButton } from './components/dark-light-mode-button/dark-light-mode-button';
import { MatIcon } from '@angular/material/icon';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@@ -15,6 +16,7 @@ import { DarkLightModeButton } from './components/dark-light-mode-button/dark-li
CalendarYear, CalendarYear,
DarkLightModeButton, DarkLightModeButton,
DarkLightModeButton, DarkLightModeButton,
MatIcon,
], ],
templateUrl: './app.html', templateUrl: './app.html',
styleUrl: './app.scss', styleUrl: './app.scss',
@@ -24,7 +24,9 @@ export class CalendarYear {
effect(() => { effect(() => {
this.selectedDates = this.calendarService.getSelectedDatesForYear(); this.selectedDates = this.calendarService.getSelectedDatesForYear();
const vacationData = this.userInput.vacationNumberSignal(); const vacationData = this.userInput.vacationNumberSignal();
this.calendarSettingsService.samediMalin(); const c = this.calendarSettingsService.samediMalin();
console.log('Samedi Malin setting:', c);
console.log('Optimizing vacations with data:', vacationData);
this.isLoading.set(true); this.isLoading.set(true);
// Simulate server-side calculation with a small delay // Simulate server-side calculation with a small delay
setTimeout(() => { setTimeout(() => {
@@ -1,4 +1,3 @@
@defer (hydrate on viewport) {
<div class="container"> <div class="container">
<div> <div>
<h1>Calendar Settings</h1> <h1>Calendar Settings</h1>
@@ -28,6 +27,3 @@
</div> </div>
<button mat-button (click)="closeDialog()">Fermer</button> <button mat-button (click)="closeDialog()">Fermer</button>
</div> </div>
} @placeholder {
<p>loading ...</p>
}
@@ -4,6 +4,7 @@ import { MatCheckbox } from '@angular/material/checkbox';
import { MatDialogRef } from '@angular/material/dialog'; import { MatDialogRef } from '@angular/material/dialog';
import { CalendarSettingsService } from '../../../services/calendar-settings-service'; import { CalendarSettingsService } from '../../../services/calendar-settings-service';
import { MatSlideToggle } from '@angular/material/slide-toggle'; import { MatSlideToggle } from '@angular/material/slide-toggle';
import { CdkFixedSizeVirtualScroll } from '@angular/cdk/scrolling';
@Component({ @Component({
selector: 'app-calendar-settings-dialog', selector: 'app-calendar-settings-dialog',
+52 -25
View File
@@ -217,6 +217,7 @@ export class SelectedDate implements SelectedDateInterface {
isSelected(date: Date): boolean { isSelected(date: Date): boolean {
return ( return (
this.type !== DayType.TODAY &&
this.range.start !== null && this.range.start !== null &&
this.range.end !== null && this.range.end !== null &&
date >= this.range.start && date >= this.range.start &&
@@ -262,6 +263,9 @@ export class SelectedDates implements SelectedDateInterface {
_datesSelected: SelectedDate[] = []; _datesSelected: SelectedDate[] = [];
datesSelected: SelectedDate[] = []; datesSelected: SelectedDate[] = [];
private minDate: Date | null = null;
private maxDate: Date | null = null;
update(): void { update(): void {
this.datesSelected = this._datesSelected.slice(); this.datesSelected = this._datesSelected.slice();
this.grouping(); this.grouping();
@@ -270,15 +274,38 @@ export class SelectedDates implements SelectedDateInterface {
push(selectedDate: SelectedDate) { push(selectedDate: SelectedDate) {
this._datesSelected.push(selectedDate); this._datesSelected.push(selectedDate);
this.datesSelected = this._datesSelected.slice(); this.datesSelected = this._datesSelected.slice();
this.updateMinMaxDates();
this.grouping(); this.grouping();
} }
private updateMinMaxDates(): void {
if (this._datesSelected.length === 0) {
this.minDate = null;
this.maxDate = null;
return;
}
this.minDate = this._datesSelected.reduce((min, date) => {
if (!date.range.start) return min;
if (!min) return date.range.start;
return date.range.start < min ? date.range.start : min;
}, this._datesSelected[0].range.start);
this.maxDate = this._datesSelected.reduce((max, date) => {
if (!date.range.end) return max;
if (!max) return date.range.end;
return date.range.end > max ? date.range.end : max;
}, this._datesSelected[0].range.end);
}
// make a "mother group" with type = null when two or more groups are adjacent or overlapping; keep the children groups // make a "mother group" with type = null when two or more groups are adjacent or overlapping; keep the children groups
grouping(): void { grouping(): void {
if (this._datesSelected.length <= 1) { if (this._datesSelected.length <= 1) {
return; return;
} }
const oneDayInMs = 24 * 60 * 60 * 1000;
// Sort dates by start date // Sort dates by start date
const sorted = [...this._datesSelected].sort((a, b) => { const sorted = [...this._datesSelected].sort((a, b) => {
const aStart = a.range.start?.getTime() ?? 0; const aStart = a.range.start?.getTime() ?? 0;
@@ -289,25 +316,23 @@ export class SelectedDates implements SelectedDateInterface {
// Group adjacent or overlapping dates // Group adjacent or overlapping dates
const groups: SelectedDate[][] = []; const groups: SelectedDate[][] = [];
let currentGroup: SelectedDate[] = [sorted[0]]; let currentGroup: SelectedDate[] = [sorted[0]];
let currentGroupMaxEnd = sorted[0].range.end?.getTime() ?? 0;
for (let i = 1; i < sorted.length; i++) { for (let i = 1; i < sorted.length; i++) {
const current = sorted[i]; const current = sorted[i];
const previous = sorted[i - 1];
const prevEnd = previous.range.end?.getTime() ?? 0;
const currStart = current.range.start?.getTime() ?? 0; const currStart = current.range.start?.getTime() ?? 0;
const currEnd = current.range.end?.getTime() ?? 0;
// Check if dates are adjacent (next day) or overlapping // Check if current date is adjacent or overlapping with the maximum end of the current group
const oneDayInMs = 24 * 60 * 60 * 1000; if (currStart - currentGroupMaxEnd <= oneDayInMs) {
const isAdjacent = currStart - prevEnd <= oneDayInMs;
if (isAdjacent) {
currentGroup.push(current); currentGroup.push(current);
currentGroupMaxEnd = Math.max(currentGroupMaxEnd, currEnd);
} else { } else {
if (currentGroup.length > 1) { if (currentGroup.length > 1) {
groups.push(currentGroup); groups.push(currentGroup);
} }
currentGroup = [current]; currentGroup = [current];
currentGroupMaxEnd = Math.max(currentGroupMaxEnd, currEnd);
} }
} }
@@ -316,26 +341,19 @@ export class SelectedDates implements SelectedDateInterface {
groups.push(currentGroup); groups.push(currentGroup);
} }
// Create mother groups and rebuild datesSelected // Create mother groups and add to result
const result: SelectedDate[] = [...this._datesSelected]; const result: SelectedDate[] = [...this._datesSelected];
for (const group of groups) { for (const group of groups) {
const groupStart = group.reduce((min, date) => { const groupStart = Math.min(...group.map((d) => d.range.start?.getTime() ?? Infinity));
const start = date.range.start?.getTime() ?? 0; const groupEnd = Math.max(...group.map((d) => d.range.end?.getTime() ?? 0));
return min === 0 ? start : Math.min(min, start);
}, 0);
const groupEnd = group.reduce((max, date) => { result.push(
const end = date.range.end?.getTime() ?? 0; new SelectedDate(
return Math.max(max, end);
}, 0);
const motherGroup = new SelectedDate(
null, null,
new DateRange<Date>(new Date(groupStart), new Date(groupEnd)), new DateRange<Date>(new Date(groupStart), new Date(groupEnd)),
),
); );
result.push(motherGroup);
} }
this.datesSelected = result; this.datesSelected = result;
@@ -513,6 +531,9 @@ export class SelectedDates implements SelectedDateInterface {
} }
samediMalin(vacationsNumber: VacationsNumber): void { samediMalin(vacationsNumber: VacationsNumber): void {
const now = new Date();
now.setHours(0, 0, 0, 0);
const samedi_ferie = this.datesSelected.filter( const samedi_ferie = this.datesSelected.filter(
(date) => date.type === DayType.CLOSED_DAY && date.range.start?.getDay() === 6, (date) => date.type === DayType.CLOSED_DAY && date.range.start?.getDay() === 6,
); );
@@ -549,15 +570,21 @@ export class SelectedDates implements SelectedDateInterface {
const oneDayAfter = new Date(sundayDate); const oneDayAfter = new Date(sundayDate);
oneDayAfter.setDate(sundayDate.getDate() + 1); oneDayAfter.setDate(sundayDate.getDate() + 1);
// Check if we can apply each strategy // Check if we can apply each strategy (and not in the past)
const canApplyTwoBeforeStrategy = const canApplyTwoBeforeStrategy =
!this.isSelected(twoDaysBefore1) && !this.isSelected(twoDaysBefore2); twoDaysBefore1 >= now &&
!this.isSelected(twoDaysBefore1) &&
!this.isSelected(twoDaysBefore2);
const canApplyTwoAfterStrategy = const canApplyTwoAfterStrategy =
!this.isSelected(twoDaysAfter1) && !this.isSelected(twoDaysAfter2); twoDaysAfter1 >= now &&
!this.isSelected(twoDaysAfter1) &&
!this.isSelected(twoDaysAfter2);
const canApplyMixedStrategy = const canApplyMixedStrategy =
!this.isSelected(oneDayBefore) && !this.isSelected(oneDayAfter); oneDayBefore >= now &&
!this.isSelected(oneDayBefore) &&
!this.isSelected(oneDayAfter);
// Test all applicable strategies and choose the one with the best heuristic score // Test all applicable strategies and choose the one with the best heuristic score
const strategies: { apply: () => void; heuristic: number }[] = []; const strategies: { apply: () => void; heuristic: number }[] = [];