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>
|
<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" />
|
||||||
|
|||||||
@@ -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,33 +1,29 @@
|
|||||||
@defer (hydrate on viewport) {
|
<div class="container">
|
||||||
<div class="container">
|
<div>
|
||||||
<div>
|
<h1>Calendar Settings</h1>
|
||||||
<h1>Calendar Settings</h1>
|
<p>Configure your calendar settings here.</p>
|
||||||
<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>
|
</div>
|
||||||
} @placeholder {
|
<div style="display: flex; flex-direction: column; gap: 1rem">
|
||||||
<p>loading ...</p>
|
<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 { 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',
|
||||||
|
|||||||
@@ -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);
|
null,
|
||||||
}, 0);
|
new DateRange<Date>(new Date(groupStart), new Date(groupEnd)),
|
||||||
|
),
|
||||||
const motherGroup = new SelectedDate(
|
|
||||||
null,
|
|
||||||
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 }[] = [];
|
||||||
|
|||||||
Reference in new issue
Block a user