124 lines
4.5 KiB
TypeScript
124 lines
4.5 KiB
TypeScript
import { Component, computed, inject, ChangeDetectionStrategy } from '@angular/core';
|
||
import { MatInputModule } from '@angular/material/input';
|
||
import { UserInputService } from '../../services/user-input-service';
|
||
import { RulesPanel } from '../rules-panel/rules-panel';
|
||
import { MatCard, MatCardContent } from '@angular/material/card';
|
||
import { MatIcon } from '@angular/material/icon';
|
||
import { DayInput } from '../userInput/day-input/day-input';
|
||
import { MatButtonModule } from '@angular/material/button';
|
||
import { VacDay } from '../../services/calendar-service';
|
||
import type { LeaveType } from '../../services/leave-planner.service';
|
||
import { TranslationService } from '../../services/translation-service';
|
||
import { CalendarSettingsService } from '../../services/calendar-settings-service';
|
||
import { PlanningDurationService } from '../../services/planning-duration.service';
|
||
|
||
@Component({
|
||
selector: 'app-user-input',
|
||
imports: [
|
||
MatInputModule,
|
||
RulesPanel,
|
||
MatCard,
|
||
MatCardContent,
|
||
MatIcon,
|
||
MatButtonModule,
|
||
DayInput,
|
||
],
|
||
templateUrl: './user-input.html',
|
||
styleUrl: './user-input.scss',
|
||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||
})
|
||
export class UserInput {
|
||
private userInputService: UserInputService = inject(UserInputService);
|
||
protected readonly declarationEntry = this.userInputService.declarationEntrySignal;
|
||
protected readonly declaredType = this.userInputService.declaredTypeSignal;
|
||
protected readonly declaredTypes: readonly LeaveType[] = ['cp', 'rtt', 'other'];
|
||
protected readonly calendarSettings = inject(CalendarSettingsService);
|
||
protected readonly duration = inject(PlanningDurationService);
|
||
protected readonly translationService = inject(TranslationService);
|
||
protected readonly durationLabel = computed(() => {
|
||
const estimate = this.duration.estimate();
|
||
if (!estimate) return '';
|
||
return this.tr()
|
||
.userInput.durationEstimate.replace(
|
||
'{range}',
|
||
`${estimate.minimum}–${estimate.maximum}`,
|
||
)
|
||
.replace('{unit}', this.tr().userInput.durationUnits[estimate.unit]);
|
||
});
|
||
|
||
get tr() {
|
||
return this.translationService.tr;
|
||
}
|
||
|
||
protected remainingDays = computed(() => {
|
||
const remaining = this.userInputService.remainingVacationDaysSignal();
|
||
return remaining.reduce((acc, day) => acc + day.numberOfDays, 0);
|
||
});
|
||
|
||
protected remainingDaysDetails = computed(() => {
|
||
const remaining = this.userInputService.remainingVacationDaysSignal();
|
||
return remaining
|
||
.filter((day) => day.numberOfDays > 0)
|
||
.map((day) => {
|
||
const typeName = this.translationService.getDayTypeLabel(day.type);
|
||
const expiryDate = day.expiryDate.toLocaleDateString(
|
||
this.translationService.locale(),
|
||
{
|
||
day: 'numeric',
|
||
month: 'short',
|
||
year: 'numeric',
|
||
},
|
||
);
|
||
const expiresLabel = this.tr().userInput.expires;
|
||
const classification = day.classification
|
||
? this.getClassificationLabel(day.classification)
|
||
: '';
|
||
const reason = day.classification ? this.getReasonLabel(day.classification) : '';
|
||
const explanation = classification ? `${classification}: ${reason}; ` : '';
|
||
return `${day.numberOfDays} ${typeName} (${explanation}${expiresLabel} ${expiryDate})`;
|
||
});
|
||
});
|
||
|
||
private getClassificationLabel(classification: NonNullable<VacDay['classification']>): string {
|
||
const labels = this.tr().userInput.remainingClassification;
|
||
if (classification === 'unavoidable-expiry') return labels.unavoidableExpiry;
|
||
if (classification === 'at-risk') return labels.atRisk;
|
||
return labels.deferred;
|
||
}
|
||
|
||
private getReasonLabel(classification: NonNullable<VacDay['classification']>): string {
|
||
const labels = this.tr().userInput.remainingReason;
|
||
if (classification === 'unavoidable-expiry') return labels.unavoidableExpiry;
|
||
if (classification === 'at-risk') return labels.atRisk;
|
||
return labels.deferred;
|
||
}
|
||
|
||
protected hasRemainingDays = computed(() => this.remainingDays() > 0);
|
||
|
||
protected get daysOff() {
|
||
return this.userInputService.vacationNumberSignal();
|
||
}
|
||
|
||
public addDay() {
|
||
this.userInputService.updateBalances((days) => [
|
||
...days,
|
||
this.userInputService.createVacationDay(),
|
||
]);
|
||
}
|
||
|
||
public removeFromList(index: number) {
|
||
this.userInputService.updateBalances((days) => days.filter((_, i) => i !== index));
|
||
}
|
||
|
||
public updateDay(index: number, updatedDay: VacDay) {
|
||
this.userInputService.updateBalances((days) =>
|
||
days.map((day, i) => (i === index ? updatedDay : day)),
|
||
);
|
||
}
|
||
|
||
public triggerComputation() {
|
||
if (this.calendarSettings.planningHorizonError()) return;
|
||
this.userInputService.requestComputation();
|
||
}
|
||
}
|