Enhance calendar settings dialog and optimize date selection logic
This commit is contained in:
1 parent
d01c93c763
commit
8aad6e4cef
6 files changed
+89
-61
No files matched your search
+1
-1
@@ -1,6 +1,6 @@
|
||||
<app-dark-light-mode-button class="modeButton"></app-dark-light-mode-button>
|
||||
<main class="main">
|
||||
<h1>{{ title() }}</h1>
|
||||
<h1 style="display: flex; gap: 1rem"><mat-icon>beach_access</mat-icon> {{ title() }}</h1>
|
||||
<div class="content">
|
||||
<app-user-input></app-user-input>
|
||||
<mat-divider [vertical]="true" />
|
||||
|
||||
@@ -5,6 +5,7 @@ import { MatDivider } from '@angular/material/divider';
|
||||
import { UserInput } from './components/user-input/user-input';
|
||||
import { CalendarYear } from './components/calendar-year/calendar-year';
|
||||
import { DarkLightModeButton } from './components/dark-light-mode-button/dark-light-mode-button';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -15,6 +16,7 @@ import { DarkLightModeButton } from './components/dark-light-mode-button/dark-li
|
||||
CalendarYear,
|
||||
DarkLightModeButton,
|
||||
DarkLightModeButton,
|
||||
MatIcon,
|
||||
],
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.scss',
|
||||
|
||||
@@ -24,7 +24,9 @@ export class CalendarYear {
|
||||
effect(() => {
|
||||
this.selectedDates = this.calendarService.getSelectedDatesForYear();
|
||||
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);
|
||||
// Simulate server-side calculation with a small delay
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -1,33 +1,29 @@
|
||||
@defer (hydrate on viewport) {
|
||||
<div class="container">
|
||||
<div>
|
||||
<h1>Calendar Settings</h1>
|
||||
<p>Configure your calendar settings here.</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 1rem">
|
||||
<div style="align-self: flex-start">
|
||||
<mat-slide-toggle
|
||||
id="showWeekNumbers"
|
||||
name="showWeekNumbers"
|
||||
[checked]="calendarSettingsService.showWeekNumbers()"
|
||||
(change)="calendarSettingsService.showWeekNumbers.set($event.checked)"
|
||||
>
|
||||
Afficher les numéros de semaine
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<div style="align-self: flex-start">
|
||||
<mat-slide-toggle
|
||||
id="samediMalin"
|
||||
name="samediMalin"
|
||||
[checked]="calendarSettingsService.samediMalin()"
|
||||
(change)="calendarSettingsService.samediMalin.set($event.checked)"
|
||||
>
|
||||
Activer le Samedi Malin
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
</div>
|
||||
<button mat-button (click)="closeDialog()">Fermer</button>
|
||||
<div class="container">
|
||||
<div>
|
||||
<h1>Calendar Settings</h1>
|
||||
<p>Configure your calendar settings here.</p>
|
||||
</div>
|
||||
} @placeholder {
|
||||
<p>loading ...</p>
|
||||
}
|
||||
<div style="display: flex; flex-direction: column; gap: 1rem">
|
||||
<div style="align-self: flex-start">
|
||||
<mat-slide-toggle
|
||||
id="showWeekNumbers"
|
||||
name="showWeekNumbers"
|
||||
[checked]="calendarSettingsService.showWeekNumbers()"
|
||||
(change)="calendarSettingsService.showWeekNumbers.set($event.checked)"
|
||||
>
|
||||
Afficher les numéros de semaine
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<div style="align-self: flex-start">
|
||||
<mat-slide-toggle
|
||||
id="samediMalin"
|
||||
name="samediMalin"
|
||||
[checked]="calendarSettingsService.samediMalin()"
|
||||
(change)="calendarSettingsService.samediMalin.set($event.checked)"
|
||||
>
|
||||
Activer le Samedi Malin
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
</div>
|
||||
<button mat-button (click)="closeDialog()">Fermer</button>
|
||||
</div>
|
||||
@@ -4,6 +4,7 @@ import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { CalendarSettingsService } from '../../../services/calendar-settings-service';
|
||||
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
||||
import { CdkFixedSizeVirtualScroll } from '@angular/cdk/scrolling';
|
||||
|
||||
@Component({
|
||||
selector: 'app-calendar-settings-dialog',
|
||||
|
||||
@@ -217,6 +217,7 @@ export class SelectedDate implements SelectedDateInterface {
|
||||
|
||||
isSelected(date: Date): boolean {
|
||||
return (
|
||||
this.type !== DayType.TODAY &&
|
||||
this.range.start !== null &&
|
||||
this.range.end !== null &&
|
||||
date >= this.range.start &&
|
||||
@@ -262,6 +263,9 @@ export class SelectedDates implements SelectedDateInterface {
|
||||
_datesSelected: SelectedDate[] = [];
|
||||
datesSelected: SelectedDate[] = [];
|
||||
|
||||
private minDate: Date | null = null;
|
||||
private maxDate: Date | null = null;
|
||||
|
||||
update(): void {
|
||||
this.datesSelected = this._datesSelected.slice();
|
||||
this.grouping();
|
||||
@@ -270,15 +274,38 @@ export class SelectedDates implements SelectedDateInterface {
|
||||
push(selectedDate: SelectedDate) {
|
||||
this._datesSelected.push(selectedDate);
|
||||
this.datesSelected = this._datesSelected.slice();
|
||||
this.updateMinMaxDates();
|
||||
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
|
||||
grouping(): void {
|
||||
if (this._datesSelected.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oneDayInMs = 24 * 60 * 60 * 1000;
|
||||
|
||||
// Sort dates by start date
|
||||
const sorted = [...this._datesSelected].sort((a, b) => {
|
||||
const aStart = a.range.start?.getTime() ?? 0;
|
||||
@@ -289,25 +316,23 @@ export class SelectedDates implements SelectedDateInterface {
|
||||
// Group adjacent or overlapping dates
|
||||
const groups: SelectedDate[][] = [];
|
||||
let currentGroup: SelectedDate[] = [sorted[0]];
|
||||
let currentGroupMaxEnd = sorted[0].range.end?.getTime() ?? 0;
|
||||
|
||||
for (let i = 1; i < sorted.length; 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 currEnd = current.range.end?.getTime() ?? 0;
|
||||
|
||||
// Check if dates are adjacent (next day) or overlapping
|
||||
const oneDayInMs = 24 * 60 * 60 * 1000;
|
||||
const isAdjacent = currStart - prevEnd <= oneDayInMs;
|
||||
|
||||
if (isAdjacent) {
|
||||
// Check if current date is adjacent or overlapping with the maximum end of the current group
|
||||
if (currStart - currentGroupMaxEnd <= oneDayInMs) {
|
||||
currentGroup.push(current);
|
||||
currentGroupMaxEnd = Math.max(currentGroupMaxEnd, currEnd);
|
||||
} else {
|
||||
if (currentGroup.length > 1) {
|
||||
groups.push(currentGroup);
|
||||
}
|
||||
currentGroup = [current];
|
||||
currentGroupMaxEnd = Math.max(currentGroupMaxEnd, currEnd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,26 +341,19 @@ export class SelectedDates implements SelectedDateInterface {
|
||||
groups.push(currentGroup);
|
||||
}
|
||||
|
||||
// Create mother groups and rebuild datesSelected
|
||||
// Create mother groups and add to result
|
||||
const result: SelectedDate[] = [...this._datesSelected];
|
||||
|
||||
for (const group of groups) {
|
||||
const groupStart = group.reduce((min, date) => {
|
||||
const start = date.range.start?.getTime() ?? 0;
|
||||
return min === 0 ? start : Math.min(min, start);
|
||||
}, 0);
|
||||
const groupStart = Math.min(...group.map((d) => d.range.start?.getTime() ?? Infinity));
|
||||
const groupEnd = Math.max(...group.map((d) => d.range.end?.getTime() ?? 0));
|
||||
|
||||
const groupEnd = group.reduce((max, date) => {
|
||||
const end = date.range.end?.getTime() ?? 0;
|
||||
return Math.max(max, end);
|
||||
}, 0);
|
||||
|
||||
const motherGroup = new SelectedDate(
|
||||
null,
|
||||
new DateRange<Date>(new Date(groupStart), new Date(groupEnd)),
|
||||
result.push(
|
||||
new SelectedDate(
|
||||
null,
|
||||
new DateRange<Date>(new Date(groupStart), new Date(groupEnd)),
|
||||
),
|
||||
);
|
||||
|
||||
result.push(motherGroup);
|
||||
}
|
||||
|
||||
this.datesSelected = result;
|
||||
@@ -513,6 +531,9 @@ export class SelectedDates implements SelectedDateInterface {
|
||||
}
|
||||
|
||||
samediMalin(vacationsNumber: VacationsNumber): void {
|
||||
const now = new Date();
|
||||
now.setHours(0, 0, 0, 0);
|
||||
|
||||
const samedi_ferie = this.datesSelected.filter(
|
||||
(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);
|
||||
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 =
|
||||
!this.isSelected(twoDaysBefore1) && !this.isSelected(twoDaysBefore2);
|
||||
twoDaysBefore1 >= now &&
|
||||
!this.isSelected(twoDaysBefore1) &&
|
||||
!this.isSelected(twoDaysBefore2);
|
||||
|
||||
const canApplyTwoAfterStrategy =
|
||||
!this.isSelected(twoDaysAfter1) && !this.isSelected(twoDaysAfter2);
|
||||
twoDaysAfter1 >= now &&
|
||||
!this.isSelected(twoDaysAfter1) &&
|
||||
!this.isSelected(twoDaysAfter2);
|
||||
|
||||
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
|
||||
const strategies: { apply: () => void; heuristic: number }[] = [];
|
||||
|
||||
Reference in new issue
Block a user