Files
Calbook/lib/weekday-availability.ts

161 lines
5.5 KiB
TypeScript

export const WEEKDAY_KEYS = ["0", "1", "2", "3", "4", "5", "6"] as const;
export type WeekdayKey = (typeof WEEKDAY_KEYS)[number];
export type DayAvailability = {
enabled: boolean;
start: string;
end: string;
};
export type WeekdayAvailability = Record<WeekdayKey, DayAvailability>;
const TIME_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
const DEFAULT_START = "09:00";
const DEFAULT_END = "17:00";
const DEFAULT_ENABLED = new Set<WeekdayKey>(["0", "1", "2", "3", "4"]);
export function isValidTimeValue(value: string | undefined | null) {
return TIME_RE.test(value ?? "");
}
function parseWeekdayCsv(raw: string | undefined | null) {
const parsed = (raw ?? "")
.split(",")
.map((part) => part.trim())
.filter((part): part is WeekdayKey => WEEKDAY_KEYS.includes(part as WeekdayKey));
return Array.from(new Set(parsed));
}
export function createDefaultWeekdayAvailability(): WeekdayAvailability {
return {
"0": { enabled: true, start: DEFAULT_START, end: DEFAULT_END },
"1": { enabled: true, start: DEFAULT_START, end: DEFAULT_END },
"2": { enabled: true, start: DEFAULT_START, end: DEFAULT_END },
"3": { enabled: true, start: DEFAULT_START, end: DEFAULT_END },
"4": { enabled: true, start: DEFAULT_START, end: DEFAULT_END },
"5": { enabled: false, start: DEFAULT_START, end: DEFAULT_END },
"6": { enabled: false, start: DEFAULT_START, end: DEFAULT_END }
};
}
export function createWeekdayAvailabilityFromLegacy(
weekdaysRaw: string | undefined | null,
startRaw: string | undefined | null,
endRaw: string | undefined | null
): WeekdayAvailability {
const enabledKeys = parseWeekdayCsv(weekdaysRaw);
const start = isValidTimeValue(startRaw) ? (startRaw as string) : DEFAULT_START;
const end = isValidTimeValue(endRaw) ? (endRaw as string) : DEFAULT_END;
const hasValidRange = start < end;
const safeStart = hasValidRange ? start : DEFAULT_START;
const safeEnd = hasValidRange ? end : DEFAULT_END;
const normalizedEnabled =
enabledKeys.length > 0 ? new Set<WeekdayKey>(enabledKeys) : DEFAULT_ENABLED;
return {
"0": { enabled: normalizedEnabled.has("0"), start: safeStart, end: safeEnd },
"1": { enabled: normalizedEnabled.has("1"), start: safeStart, end: safeEnd },
"2": { enabled: normalizedEnabled.has("2"), start: safeStart, end: safeEnd },
"3": { enabled: normalizedEnabled.has("3"), start: safeStart, end: safeEnd },
"4": { enabled: normalizedEnabled.has("4"), start: safeStart, end: safeEnd },
"5": { enabled: normalizedEnabled.has("5"), start: safeStart, end: safeEnd },
"6": { enabled: normalizedEnabled.has("6"), start: safeStart, end: safeEnd }
};
}
function normalizeDayAvailability(
value: unknown,
fallback: DayAvailability
): DayAvailability {
if (!value || typeof value !== "object") {
return fallback;
}
const raw = value as Partial<DayAvailability>;
const enabled = typeof raw.enabled === "boolean" ? raw.enabled : fallback.enabled;
const start = isValidTimeValue(raw.start) ? (raw.start as string) : fallback.start;
const end = isValidTimeValue(raw.end) ? (raw.end as string) : fallback.end;
if (start >= end) {
return {
enabled,
start: fallback.start,
end: fallback.end
};
}
return {
enabled,
start,
end
};
}
export function normalizeWeekdayAvailability(
value: unknown,
fallback?: WeekdayAvailability
): WeekdayAvailability {
const base = fallback ?? createDefaultWeekdayAvailability();
const source = value && typeof value === "object" ? (value as Record<string, unknown>) : {};
return {
"0": normalizeDayAvailability(source["0"], base["0"]),
"1": normalizeDayAvailability(source["1"], base["1"]),
"2": normalizeDayAvailability(source["2"], base["2"]),
"3": normalizeDayAvailability(source["3"], base["3"]),
"4": normalizeDayAvailability(source["4"], base["4"]),
"5": normalizeDayAvailability(source["5"], base["5"]),
"6": normalizeDayAvailability(source["6"], base["6"])
};
}
export function parseWeekdayAvailabilityJson(
raw: string | undefined | null,
fallback?: WeekdayAvailability
): WeekdayAvailability {
if (!raw) {
return normalizeWeekdayAvailability(undefined, fallback);
}
try {
const parsed = JSON.parse(raw);
return normalizeWeekdayAvailability(parsed, fallback);
} catch {
return normalizeWeekdayAvailability(undefined, fallback);
}
}
export function serializeWeekdayAvailability(value: WeekdayAvailability) {
const normalized = normalizeWeekdayAvailability(value);
return JSON.stringify(normalized);
}
export function hasAtLeastOneEnabledDay(value: WeekdayAvailability) {
return WEEKDAY_KEYS.some((key) => {
const day = value[key];
return day.enabled && isValidTimeValue(day.start) && isValidTimeValue(day.end) && day.start < day.end;
});
}
export function deriveLegacyAvailability(
value: WeekdayAvailability
): {
bookingAllowedWeekdays: string;
bookingDayStartTime: string;
bookingDayEndTime: string;
} {
const normalized = normalizeWeekdayAvailability(value);
const enabledKeys = WEEKDAY_KEYS.filter((key) => normalized[key].enabled);
const finalEnabled = enabledKeys.length > 0 ? enabledKeys : (["0", "1", "2", "3", "4"] as WeekdayKey[]);
const starts = finalEnabled.map((key) => normalized[key].start).sort();
const ends = finalEnabled.map((key) => normalized[key].end).sort();
return {
bookingAllowedWeekdays: finalEnabled.join(","),
bookingDayStartTime: starts[0] ?? DEFAULT_START,
bookingDayEndTime: ends[ends.length - 1] ?? DEFAULT_END
};
}