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) Co-authored-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com> Co-committed-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
97 lines
3.1 KiB
TypeScript
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;
|
|
}
|