Files
vac-optimizer/src/app/components/user-input/user-input.ts
T
2026-10-07 00:28:29 +02:00

124 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
}
}