feat: implement rules engine and management panel for custom holiday optimization
This commit is contained in:
1 parent
c0549de4dd
commit
08ee84e5c9
20 files changed
+3951
-16847
No files matched your search
@@ -0,0 +1,188 @@
|
||||
import { Component, ChangeDetectionStrategy, inject, signal } from '@angular/core';
|
||||
import {
|
||||
RuleService,
|
||||
RuleType,
|
||||
IfThenRule,
|
||||
TimeWindowRule,
|
||||
VacationRule,
|
||||
PlacementStrategy,
|
||||
} from '../../services/rule-service';
|
||||
import { SelectableDayType, tooltipTypeMapping, DayType } from '../../services/calendar-service';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { DatePipe, LowerCasePipe } from '@angular/common';
|
||||
import {
|
||||
DateAdapter,
|
||||
MAT_DATE_FORMATS,
|
||||
MAT_DATE_LOCALE,
|
||||
NativeDateAdapter,
|
||||
} from '@angular/material/core';
|
||||
import { CdkDragDrop, CdkDropList, CdkDrag } from '@angular/cdk/drag-drop';
|
||||
|
||||
class CustomDateAdapter extends NativeDateAdapter {
|
||||
override format(date: Date, displayFormat: unknown): string {
|
||||
if (displayFormat === 'input') {
|
||||
const day = date.getDate().toString().padStart(2, '0');
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
const year = date.getFullYear();
|
||||
return `${day}/${month}/${year}`;
|
||||
}
|
||||
return super.format(date, displayFormat as object);
|
||||
}
|
||||
}
|
||||
|
||||
const MY_DATE_FORMATS = {
|
||||
parse: { dateInput: 'input' },
|
||||
display: {
|
||||
dateInput: 'input',
|
||||
monthYearLabel: { year: 'numeric', month: 'short' },
|
||||
dateA11yLabel: { year: 'numeric', month: 'long', day: 'numeric' },
|
||||
monthYearA11yLabel: { year: 'numeric', month: 'long' },
|
||||
},
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'app-rules-panel',
|
||||
imports: [
|
||||
MatExpansionModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
MatSlideToggleModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatDatepickerModule,
|
||||
FormsModule,
|
||||
DatePipe,
|
||||
LowerCasePipe,
|
||||
CdkDropList,
|
||||
CdkDrag,
|
||||
],
|
||||
templateUrl: './rules-panel.html',
|
||||
styleUrl: './rules-panel.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [
|
||||
{ provide: DateAdapter, useClass: CustomDateAdapter },
|
||||
{ provide: MAT_DATE_LOCALE, useValue: 'en-GB' },
|
||||
{ provide: MAT_DATE_FORMATS, useValue: MY_DATE_FORMATS },
|
||||
],
|
||||
})
|
||||
export class RulesPanel {
|
||||
public ruleService = inject(RuleService);
|
||||
|
||||
protected readonly RuleType = RuleType;
|
||||
protected readonly placeDayTypes = Object.values(SelectableDayType);
|
||||
protected readonly rewardDayTypes = Object.values(SelectableDayType).filter(
|
||||
(t) => t !== SelectableDayType.ANY,
|
||||
);
|
||||
protected readonly tooltipTypeMapping = tooltipTypeMapping;
|
||||
|
||||
protected readonly daysOfWeek = [
|
||||
{ value: 1, label: 'Lundi' },
|
||||
{ value: 2, label: 'Mardi' },
|
||||
{ value: 3, label: 'Mercredi' },
|
||||
{ value: 4, label: 'Jeudi' },
|
||||
{ value: 5, label: 'Vendredi' },
|
||||
{ value: 6, label: 'Samedi' },
|
||||
{ value: 0, label: 'Dimanche' },
|
||||
];
|
||||
|
||||
protected readonly placementOptions: { value: PlacementStrategy; label: string }[] = [
|
||||
{ value: 'before', label: ' Avant' },
|
||||
{ value: 'after', label: ' Après' },
|
||||
{ value: 'around', label: ' Autour' },
|
||||
{ value: 'auto', label: ' Auto (Meilleur choix)' },
|
||||
];
|
||||
|
||||
protected editingRuleId = signal<string | null>(null);
|
||||
protected isAddingNew = signal(false);
|
||||
|
||||
protected newRuleType = signal<RuleType>(RuleType.IF_THEN);
|
||||
|
||||
protected editBuffer: Partial<VacationRule> = {};
|
||||
|
||||
public get rules() {
|
||||
return this.ruleService.rules;
|
||||
}
|
||||
|
||||
public drop(event: CdkDragDrop<VacationRule[]>) {
|
||||
if (event.previousIndex !== event.currentIndex) {
|
||||
this.ruleService.reorderRules(event.previousIndex, event.currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public toggleRule(id: string) {
|
||||
this.ruleService.toggleRule(id);
|
||||
}
|
||||
|
||||
public startEdit(rule: VacationRule, event: Event) {
|
||||
event.stopPropagation();
|
||||
this.editingRuleId.set(rule.id);
|
||||
this.isAddingNew.set(false);
|
||||
this.editBuffer = { ...rule };
|
||||
}
|
||||
|
||||
public cancelEdit() {
|
||||
this.editingRuleId.set(null);
|
||||
this.isAddingNew.set(false);
|
||||
this.editBuffer = {};
|
||||
}
|
||||
|
||||
public saveEdit() {
|
||||
if (this.editBuffer.id) {
|
||||
this.ruleService.updateRule(this.editBuffer as VacationRule);
|
||||
} else {
|
||||
this.ruleService.addRule({
|
||||
...this.editBuffer,
|
||||
id: crypto.randomUUID(),
|
||||
enabled: true,
|
||||
} as VacationRule);
|
||||
}
|
||||
this.cancelEdit();
|
||||
}
|
||||
|
||||
public deleteRule(id: string, event: Event) {
|
||||
event.stopPropagation();
|
||||
this.ruleService.deleteRule(id);
|
||||
}
|
||||
|
||||
public startAddNew(type: RuleType) {
|
||||
this.isAddingNew.set(true);
|
||||
this.editingRuleId.set(null);
|
||||
this.newRuleType.set(type);
|
||||
|
||||
if (type === RuleType.IF_THEN) {
|
||||
this.editBuffer = {
|
||||
name: 'Nouvelle Règle',
|
||||
type: RuleType.IF_THEN,
|
||||
dayOfWeek: 6,
|
||||
placeDays: 2,
|
||||
placeType: SelectableDayType.CP,
|
||||
placement: 'auto',
|
||||
rewardDays: 1,
|
||||
rewardType: SelectableDayType.OTHER,
|
||||
} as Partial<IfThenRule>;
|
||||
} else {
|
||||
this.editBuffer = {
|
||||
name: 'Nouvelle Période',
|
||||
type: RuleType.TIME_WINDOW,
|
||||
startDate: new Date(),
|
||||
endDate: new Date(new Date().getTime() + 7 * 24 * 60 * 60 * 1000),
|
||||
placeDays: 5,
|
||||
placeType: SelectableDayType.CP,
|
||||
} as Partial<TimeWindowRule>;
|
||||
}
|
||||
}
|
||||
|
||||
protected getDayTypeLabel(type: string | undefined | null): string {
|
||||
if (!type) return '';
|
||||
return tooltipTypeMapping[type as unknown as DayType] || String(type);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user