Fix signal immutability violations and cleanup unused code (#62)

* Initial plan

* fix: address PR review comments for signal immutability and cleanup

Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>
This commit is contained in:
CopilotandTHoguet authored and GitHub committed 2025-11-30 01:24:26 +01:00
1 parent 5cd45b86c8
commit dea632f67d
5 files changed
+38 -29

No files matched your search

@@ -3,7 +3,7 @@
<div class="listOfDays"> <div class="listOfDays">
@for (day of daysOff; track day.id) { @for (day of daysOff; track day.id) {
<div class="day-input-wrapper" animate.enter="day-enter" animate.leave="day-leave"> <div class="day-input-wrapper" animate.enter="day-enter" animate.leave="day-leave">
<app-day-input [day]="day" (deleteEntry)="removeFromList($index)"></app-day-input> <app-day-input [day]="day" (dayChange)="updateDay($index, $event)" (deleteEntry)="removeFromList($index)"></app-day-input>
</div> </div>
} }
<mat-card class="add-day-card mat-elevation-z2" (click)="addDay()"> <mat-card class="add-day-card mat-elevation-z2" (click)="addDay()">
+19 -23
View File
@@ -1,27 +1,17 @@
import { import { Component, computed, inject, ChangeDetectionStrategy } from '@angular/core';
Component,
OnInit,
OnDestroy,
computed,
inject,
ChangeDetectionStrategy,
} from '@angular/core';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { ReactiveFormsModule } from '@angular/forms';
import { UserInputService } from '../../services/user-input-service'; import { UserInputService } from '../../services/user-input-service';
import { Subject } from 'rxjs';
import { CalendarSettings } from '../calendar-settings/calendar-settings'; import { CalendarSettings } from '../calendar-settings/calendar-settings';
import { MatCard, MatCardContent } from '@angular/material/card'; import { MatCard, MatCardContent } from '@angular/material/card';
import { MatIcon } from '@angular/material/icon'; import { MatIcon } from '@angular/material/icon';
import { DayInput } from '../userInput/day-input/day-input'; import { DayInput } from '../userInput/day-input/day-input';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { tooltipTypeMapping, DayType } from '../../services/calendar-service'; import { tooltipTypeMapping, DayType, VacDay } from '../../services/calendar-service';
@Component({ @Component({
selector: 'app-user-input', selector: 'app-user-input',
imports: [ imports: [
MatInputModule, MatInputModule,
ReactiveFormsModule,
CalendarSettings, CalendarSettings,
MatCard, MatCard,
MatCardContent, MatCardContent,
@@ -33,10 +23,8 @@ import { tooltipTypeMapping, DayType } from '../../services/calendar-service';
styleUrl: './user-input.scss', styleUrl: './user-input.scss',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class UserInput implements OnInit, OnDestroy { export class UserInput {
private userInputService: UserInputService = inject(UserInputService); private userInputService: UserInputService = inject(UserInputService);
public daysOff = this.userInputService.vacationNumberSignal();
private destroy$ = new Subject<void>();
protected remainingDays = computed(() => { protected remainingDays = computed(() => {
const remaining = this.userInputService.remainingVacationDaysSignal(); const remaining = this.userInputService.remainingVacationDaysSignal();
@@ -60,23 +48,31 @@ export class UserInput implements OnInit, OnDestroy {
protected hasRemainingDays = computed(() => this.remainingDays() > 0); protected hasRemainingDays = computed(() => this.remainingDays() > 0);
ngOnInit() {} protected get daysOff() {
return this.userInputService.vacationNumberSignal();
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
} }
public addDay() { public addDay() {
this.daysOff.push(this.userInputService.createVacationDay()); this.userInputService.vacationNumberSignal.update((days) => [
...days,
this.userInputService.createVacationDay(),
]);
} }
public removeFromList(index: number) { public removeFromList(index: number) {
this.daysOff.splice(index, 1); this.userInputService.vacationNumberSignal.update((days) =>
days.filter((_, i) => i !== index),
);
}
public updateDay(index: number, updatedDay: VacDay) {
this.userInputService.vacationNumberSignal.update((days) =>
days.map((day, i) => (i === index ? updatedDay : day)),
);
} }
public triggerComputation() { public triggerComputation() {
// Trigger signal update by setting a new array reference // Trigger signal update by setting a new array reference
this.userInputService.vacationNumberSignal.set([...this.daysOff]); this.userInputService.vacationNumberSignal.update((days) => [...days]);
} }
} }
@@ -10,7 +10,7 @@
/> />
</mat-form-field> </mat-form-field>
<mat-form-field class="dayTypeInput"> <mat-form-field class="dayTypeInput">
<mat-select (valueChange)="dayChange.emit($event.value)" [value]="day().type"> <mat-select (valueChange)="onTypeChange($event)" [value]="day().type">
<mat-option></mat-option> <mat-option></mat-option>
@for (dayType of selectableDayTypes; track $index) { @for (dayType of selectableDayTypes; track $index) {
<mat-option [value]="dayType">{{ tooltipTypeMapping[dayType] }}</mat-option> <mat-option [value]="dayType">{{ tooltipTypeMapping[dayType] }}</mat-option>
@@ -1,18 +1,29 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { DayInput } from './day-input'; import { DayInput } from './day-input';
import { SelectableDayType, VacDay } from '../../../services/calendar-service';
describe('DayInput', () => { describe('DayInput', () => {
let component: DayInput; let component: DayInput;
let fixture: ComponentFixture<DayInput>; let fixture: ComponentFixture<DayInput>;
const mockVacDay: VacDay = {
id: 'test-id',
type: SelectableDayType.CP,
numberOfDays: 5,
expiryDate: new Date('2025-12-31'),
};
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [DayInput], imports: [DayInput],
providers: [provideAnimationsAsync()],
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(DayInput); fixture = TestBed.createComponent(DayInput);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.componentRef.setInput('day', mockVacDay);
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -90,12 +90,14 @@ export class DayInput {
onInput(event: Event) { onInput(event: Event) {
const value = Number((event.target as HTMLInputElement).value); const value = Number((event.target as HTMLInputElement).value);
this.day().numberOfDays = value; this.dayChange.emit({ ...this.day(), numberOfDays: value });
this.dayChange.emit(this.day()); }
onTypeChange(type: SelectableDayType) {
this.dayChange.emit({ ...this.day(), type });
} }
addEvent(event: MatDatepickerInputEvent<Date>) { addEvent(event: MatDatepickerInputEvent<Date>) {
this.day().expiryDate = event.value!; this.dayChange.emit({ ...this.day(), expiryDate: event.value! });
this.dayChange.emit(this.day());
} }
} }