import { Injectable, signal } from '@angular/core'; import { SelectableDayType, VacDay } from './calendar-service'; import dayjs from 'dayjs'; import { DeclaredNonWorkingDay, isWorkingPlanningDate, parseDeclaredDays, } from './declared-non-working-days'; import type { LeaveType } from './leave-planner.service'; @Injectable({ providedIn: 'root', }) export class UserInputService { private readonly declaredDays = signal(this.restoreDeclaredDays()); readonly declaredDaysSignal = this.declaredDays.asReadonly(); readonly declarationEntrySignal = signal(false); readonly declaredTypeSignal = signal('cp'); private restoreDeclaredDays(): DeclaredNonWorkingDay[] { try { const saved = typeof localStorage === 'undefined' ? null : localStorage.getItem('declaredNonWorkingDays'); return saved === null ? [] : parseDeclaredDays(JSON.parse(saved)); } catch (error) { console.warn('Failed to restore declared non-working days from localStorage.', error); try { localStorage.removeItem('declaredNonWorkingDays'); } catch { // Browser storage can be unavailable; keep calendar entry usable. } return []; } } toggleDeclaredDay(date: string, type: LeaveType): void { this.toggleDeclaredDays([date], type); } toggleDeclaredDays(dates: readonly string[], type: LeaveType | null): void { const workingDates = new Set(dates.filter(isWorkingPlanningDate)); if (!workingDates.size) return; this.declaredDays.update((days) => { const existing = new Map(days.map((day) => [day.date, day.type])); const others = days.filter((day) => !workingDates.has(day.date)); return [ ...others, ...(type ? [...workingDates] .filter((date) => existing.get(date) !== type) .map((date) => ({ date, type })) : []), ].sort((a, b) => a.date.localeCompare(b.date)); }); try { if (typeof localStorage !== 'undefined') localStorage.setItem('declaredNonWorkingDays', JSON.stringify(this.declaredDays())); } catch (error) { console.warn('Failed to save declared non-working days to localStorage.', error); } } private restoreDate(value: unknown): Date { if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value)) { const date = new Date(`${value}T00:00:00`); if (dayjs(date).format('YYYY-MM-DD') === value) return date; } throw new Error('Unsupported saved balance date.'); } private getSavedValues(): VacDay[] | undefined { let stored: string | null = null; try { if (typeof localStorage === 'undefined') return; stored = localStorage.getItem('leaveBalances'); if (stored === null) return; const saved: unknown = JSON.parse(stored); if (!Array.isArray(saved)) throw new Error('Saved leave balances must be a list.'); return saved.map((value: unknown) => { if (typeof value !== 'object' || value === null) throw new Error('Unsupported saved balance record.'); const { id, type, numberOfDays, availableDate, expiryDate } = value as Record< string, unknown >; if ( typeof id !== 'string' || typeof numberOfDays !== 'number' || !Number.isFinite(numberOfDays) || (type !== SelectableDayType.CP && type !== SelectableDayType.RTT && type !== SelectableDayType.OTHER) ) throw new Error('Unsupported saved balance record.'); return { id, type, numberOfDays, availableDate: availableDate === undefined ? undefined : this.restoreDate(availableDate), expiryDate: this.restoreDate(expiryDate), }; }); } catch (error) { console.warn('Failed to restore leave balances from localStorage.', error); if (stored !== null) { try { localStorage.removeItem('leaveBalances'); } catch { // Keep the random fallback usable when browser storage is unavailable. } } } return undefined; } private getRandomNumber(): number { return Math.floor(Math.random() * 11) + 10; } private getInitialValues(): VacDay[] { const availableDate = new Date(); return [ { id: crypto.randomUUID(), type: SelectableDayType.CP, numberOfDays: this.getRandomNumber(), availableDate, // today + 365 days expiryDate: new Date(new Date().setFullYear(new Date().getFullYear() + 1)), }, { id: crypto.randomUUID(), type: SelectableDayType.RTT, numberOfDays: this.getRandomNumber(), availableDate: new Date(availableDate), expiryDate: new Date(new Date().setFullYear(new Date().getFullYear() + 1)), }, { id: crypto.randomUUID(), type: SelectableDayType.OTHER, numberOfDays: this.getRandomNumber(), availableDate: new Date(availableDate), expiryDate: new Date(new Date().setFullYear(new Date().getFullYear() + 1)), }, ]; } public createVacationDay(): VacDay { return { id: crypto.randomUUID(), type: SelectableDayType.CP, numberOfDays: 1, availableDate: new Date(), expiryDate: new Date(new Date().setFullYear(new Date().getFullYear() + 1)), }; } readonly vacationNumberSignal = signal( this.getSavedValues() ?? this.getInitialValues(), ); public updateBalances(updater: (balances: VacDay[]) => VacDay[]): void { this.vacationNumberSignal.update(updater); try { if (typeof localStorage === 'undefined') return; // Store calendar dates, so restoring them does not depend on a UTC offset. const balances = this.vacationNumberSignal().map((balance) => ({ id: balance.id, type: balance.type, numberOfDays: balance.numberOfDays, availableDate: balance.availableDate ? dayjs(balance.availableDate).format('YYYY-MM-DD') : undefined, expiryDate: dayjs(balance.expiryDate).format('YYYY-MM-DD'), })); localStorage.setItem('leaveBalances', JSON.stringify(balances)); } catch (error) { console.warn('Failed to save leave balances to localStorage.', error); } } }