Files
Nexus/apps/web/src/components/vacations/vacationExplainability.ts
T
Hartmut b41c1d2501
CI / Architecture Guardrails (push) Successful in 2m38s
CI / Assistant Split Regression (push) Successful in 3m33s
CI / Typecheck (push) Successful in 3m51s
CI / Lint (push) Successful in 5m2s
CI / E2E Tests (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / Release Images (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61)
rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61)

Co-authored-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
Co-committed-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
2026-05-21 16:28:40 +02:00

97 lines
3.1 KiB
TypeScript

"use client";
import { MILLISECONDS_PER_DAY } from "@nexus/shared";
export const HOLIDAY_SOURCE_LABELS = {
CALENDAR: "Holiday Calendar",
LEGACY_PUBLIC_HOLIDAY: "Legacy import",
CALENDAR_AND_LEGACY: "Holiday Calendar + legacy",
} as const;
export type VacationExplainabilityEntry = {
startDate: Date | string;
endDate: Date | string;
isHalfDay?: boolean | null;
deductedDays?: number | null;
holidayCountryCode?: string | null;
holidayCountryName?: string | null;
holidayFederalState?: string | null;
holidayMetroCityName?: string | null;
holidayCalendarDates?: unknown;
holidayLegacyPublicHolidayDates?: unknown;
};
function toSortedDateList(value: unknown): string[] {
return Array.isArray(value) && value.every((entry) => typeof entry === "string")
? [...value].sort()
: [];
}
export function getRequestedDays(
vacation: Pick<VacationExplainabilityEntry, "startDate" | "endDate" | "isHalfDay">,
): number {
if (vacation.isHalfDay) {
return 0.5;
}
const start = new Date(vacation.startDate);
const end = new Date(vacation.endDate);
return Math.round((end.getTime() - start.getTime()) / MILLISECONDS_PER_DAY) + 1;
}
export function getHolidayBasis(vacation: VacationExplainabilityEntry): string[] {
return [
vacation.holidayCountryName ?? vacation.holidayCountryCode ?? null,
vacation.holidayFederalState ?? null,
vacation.holidayMetroCityName ?? null,
].filter((value): value is string => Boolean(value));
}
export function getHolidayBreakdown(
vacation: VacationExplainabilityEntry,
): Array<{ date: string; source: string }> {
const calendarDates = toSortedDateList(vacation.holidayCalendarDates);
const legacyDates = toSortedDateList(vacation.holidayLegacyPublicHolidayDates);
const uniqueDates = [...new Set([...calendarDates, ...legacyDates])].sort();
return uniqueDates.map((date) => ({
date,
source:
calendarDates.includes(date) && legacyDates.includes(date)
? HOLIDAY_SOURCE_LABELS.CALENDAR_AND_LEGACY
: calendarDates.includes(date)
? HOLIDAY_SOURCE_LABELS.CALENDAR
: HOLIDAY_SOURCE_LABELS.LEGACY_PUBLIC_HOLIDAY,
}));
}
export function buildVacationExplainabilityTooltip(
vacation: VacationExplainabilityEntry & { type?: string | null },
): string | null {
const requestedDays = getRequestedDays(vacation);
const deductedDays = typeof vacation.deductedDays === "number" ? vacation.deductedDays : null;
const holidayBasis = getHolidayBasis(vacation);
const holidayBreakdown = getHolidayBreakdown(vacation);
const lines = [`Requested: ${requestedDays}d`];
if (deductedDays !== null) {
lines.push(`Deducted: ${deductedDays}d`);
}
if (holidayBasis.length > 0) {
lines.push(`Holiday basis: ${holidayBasis.join(" / ")}`);
}
if (holidayBreakdown.length > 0) {
lines.push(
`Excluded holidays: ${holidayBreakdown.map((holiday) => `${holiday.date} (${holiday.source})`).join(", ")}`,
);
}
if ((vacation.type === "SICK" || vacation.type === "PUBLIC_HOLIDAY") && deductedDays === 0) {
lines.push("Does not reduce annual vacation entitlement.");
}
return lines.length > 0 ? lines.join("\n") : null;
}