311 lines
10 KiB
TypeScript
311 lines
10 KiB
TypeScript
import {
|
|
AvailabilityComponent,
|
|
DateRangeComponent,
|
|
DateTimeRangeComponent,
|
|
HourComponent,
|
|
SlotAvailablity,
|
|
SlotComponent,
|
|
} from '@seed/interfaces/components.dates';
|
|
import _ from 'lodash';
|
|
import { DateTime, Interval } from 'luxon';
|
|
|
|
interface hoursRange {
|
|
startHours: string;
|
|
startHoursInNumber: number;
|
|
endHours: string;
|
|
endHoursInNumber: number;
|
|
}
|
|
/*
|
|
breakTime: [
|
|
['11:00', '14:00'],
|
|
['16:00', '18:00'],
|
|
],
|
|
*/
|
|
|
|
export function getNumberOfWeekendDays(dateBegin: DateTime, dateEnd: DateTime): number {
|
|
let weekendDays = 0;
|
|
|
|
let begin = dateBegin;
|
|
|
|
while (begin <= dateEnd) {
|
|
if (begin.weekday == 6 || begin.weekday == 7) weekendDays = weekendDays + 1;
|
|
|
|
begin = begin.plus({ day: 1 });
|
|
}
|
|
|
|
return weekendDays;
|
|
}
|
|
|
|
function isInTimeInterval(slotTime: DateTime, slotInterval: number, timeRange: hoursRange[]): boolean {
|
|
const startTime = slotTime;
|
|
const endTime = slotTime.plus({ minutes: slotInterval });
|
|
|
|
const startTimeInNumber = startTime.hour * 60 + startTime.minute;
|
|
const endTimeInNumber = endTime.hour * 60 + endTime.minute;
|
|
|
|
const isInInterval = timeRange.some((br) => {
|
|
return startTimeInNumber < br.endHoursInNumber && endTimeInNumber > br.startHoursInNumber;
|
|
// return (date.isSameOrAfter(startTime) && date.isSameOrBefore(endTime)) || (date.isSameOrAfter(startTime) && date.isSame(endTime));
|
|
});
|
|
|
|
return isInInterval;
|
|
}
|
|
|
|
export const getMinutesFromTimeInString = (str: string): number => {
|
|
const timeArray = str.split(':');
|
|
return parseInt(timeArray[0]) * 60 + parseInt(timeArray[1]);
|
|
};
|
|
|
|
export const getDateNoTZ = (old: Date): Date => {
|
|
const userTimezoneOffset = old.getTimezoneOffset() * 60000;
|
|
return new Date(old.getTime() - userTimezoneOffset);
|
|
};
|
|
|
|
export const slotToDatesRange = (slot: SlotComponent): DateRangeComponent[] => {
|
|
const dates: DateRangeComponent[] = [];
|
|
const { startDate, endDate, startTime, endTime } = slot;
|
|
|
|
const startTimes = startTime.split(':');
|
|
const endTimes = endTime.split(':');
|
|
|
|
let beginDate = DateTime.fromJSDate(startDate).startOf('day');
|
|
const stopDate = DateTime.fromJSDate(endDate).endOf('day');
|
|
|
|
while (beginDate < stopDate) {
|
|
dates.push({
|
|
startDate: beginDate
|
|
.set({ hour: parseInt(startTimes[0]) })
|
|
.set({ minute: parseInt(startTimes[1]) })
|
|
.toJSDate(),
|
|
endDate: beginDate
|
|
.set({ hour: parseInt(endTimes[0]) })
|
|
.set({ minute: parseInt(endTimes[1]) })
|
|
// Remove one seconds for overlapping
|
|
.minus({ second: 1 })
|
|
.toJSDate(),
|
|
});
|
|
beginDate = beginDate.plus({ day: 1 });
|
|
}
|
|
return dates;
|
|
};
|
|
|
|
export const getOneDaySlots = (input: {
|
|
day: DateTime;
|
|
dayUnavailability: DateTimeRangeComponent[];
|
|
slotInfo: string[][];
|
|
slotInterval: number;
|
|
slotDuration: number;
|
|
options?: {
|
|
zone?: string;
|
|
};
|
|
}): string[] => {
|
|
const { day, dayUnavailability, slotInfo, slotDuration, slotInterval, options } = input;
|
|
|
|
const zone = options?.zone || process.env.TIMEZONE || 'utc';
|
|
|
|
let currentDayMoment = day;
|
|
|
|
// Check if currentDay = today
|
|
const currentDayStart = currentDayMoment.toISODate();
|
|
const todayStart = DateTime.local().toISODate();
|
|
|
|
if (currentDayStart != todayStart) {
|
|
currentDayMoment = day.startOf('day');
|
|
}
|
|
|
|
const currentDayTimeInNumber = currentDayMoment.hour * 60 + currentDayMoment.minute;
|
|
|
|
console.log('current day', currentDayMoment.toISO(), currentDayMoment.weekday);
|
|
|
|
// Getting the slots for that day
|
|
const daySlot = slotInfo[day.weekday - 1];
|
|
const currentDaySlots: {
|
|
startTime: DateTime;
|
|
endTime: DateTime;
|
|
}[] = [];
|
|
|
|
if (daySlot && daySlot.length > 0) {
|
|
if (_.isArray(daySlot[0])) {
|
|
_.each(daySlot, (d) => {
|
|
currentDaySlots.push({
|
|
startTime: DateTime.fromFormat(d[0], 'HH:mm', { zone }),
|
|
endTime: DateTime.fromFormat(d[1], 'HH:mm', { zone }),
|
|
});
|
|
});
|
|
} else {
|
|
currentDaySlots.push({
|
|
startTime: DateTime.fromFormat(daySlot[0], 'HH:mm', { zone }),
|
|
endTime: DateTime.fromFormat(daySlot[1], 'HH:mm', { zone }),
|
|
});
|
|
}
|
|
}
|
|
|
|
const thisDaysUnavailabilities: {
|
|
startHours: string;
|
|
startHoursInNumber: number;
|
|
endHours: string;
|
|
endHoursInNumber: number;
|
|
}[] = getUnavailabilityOfThisDay(dayUnavailability, currentDayMoment);
|
|
|
|
const currentDateSlots: string[] = [];
|
|
console.log('thisDaysUnavailabilities', thisDaysUnavailabilities);
|
|
|
|
currentDaySlots.forEach((element) => {
|
|
let { startTime, endTime } = element;
|
|
|
|
while (startTime < endTime) {
|
|
if (
|
|
!(startTime.hour * 60 + startTime.minute < currentDayTimeInNumber) &&
|
|
!isInTimeInterval(startTime, slotDuration, thisDaysUnavailabilities) &&
|
|
startTime.hour * 60 + startTime.minute + slotDuration <= endTime.hour * 60 + endTime.minute
|
|
) {
|
|
currentDateSlots.push(startTime.toFormat('HH:mm'));
|
|
}
|
|
startTime = startTime.plus({ minutes: slotInterval });
|
|
}
|
|
});
|
|
|
|
return currentDateSlots;
|
|
};
|
|
|
|
export const getMultipleDaysSlots = (input: {
|
|
datesWanted: DateRangeComponent;
|
|
datesUnavailable: DateTimeRangeComponent[];
|
|
slotInterval: number;
|
|
slotInfo: string[][];
|
|
slotDuration: number;
|
|
options?: {
|
|
zone?: string;
|
|
};
|
|
}): SlotAvailablity[] => {
|
|
const returnSlots: SlotAvailablity[] = [];
|
|
const { datesWanted, datesUnavailable, slotInterval, slotInfo, slotDuration, options } = input;
|
|
const { startDate, endDate } = datesWanted;
|
|
|
|
const zone = options?.zone || process.env.TIMEZONE || 'utc';
|
|
|
|
let beginDate = DateTime.fromJSDate(startDate, { zone });
|
|
const stopDate = DateTime.fromJSDate(endDate, { zone });
|
|
|
|
// Two loops
|
|
// Looping into the wanted dates and keeping an index for the slotInfo
|
|
while (beginDate < stopDate) {
|
|
// Get the slot info from the array of array
|
|
const slots = getOneDaySlots({
|
|
day: beginDate,
|
|
dayUnavailability: datesUnavailable,
|
|
slotInfo,
|
|
slotInterval,
|
|
slotDuration,
|
|
options: { zone },
|
|
});
|
|
|
|
// Adding it to the array
|
|
returnSlots.push({
|
|
date: beginDate.toJSDate(),
|
|
slots,
|
|
});
|
|
|
|
beginDate = beginDate.plus({ day: 1 }).startOf('day');
|
|
}
|
|
|
|
return returnSlots;
|
|
};
|
|
|
|
export function getUnavailabilityOfThisDay(dayUnavailability: DateTimeRangeComponent[], currentDayMoment: DateTime) {
|
|
const thisDaysUnavailabilities: {
|
|
startHours: string;
|
|
startHoursInNumber: number;
|
|
endHours: string;
|
|
endHoursInNumber: number;
|
|
}[] = [];
|
|
|
|
dayUnavailability.forEach((element) => {
|
|
const startDate = element.startDate;
|
|
const endDate = element.endDate;
|
|
|
|
// If there are some reservations in that day or in multiple days
|
|
const isSameThanStartDate = currentDayMoment.hasSame(startDate, 'day');
|
|
const isSameThanEndDate = currentDayMoment.hasSame(endDate, 'day');
|
|
|
|
const startHours = startDate.toFormat('HH:mm');
|
|
const endHours = endDate.toFormat('HH:mm');
|
|
const starts = startHours.split(':');
|
|
const ends = endHours.split(':');
|
|
|
|
const startDateDay = startDate.startOf('day');
|
|
const endDateDay = startDate.endOf('day');
|
|
|
|
const startHoursInNumber = parseInt(starts[0]) * 60 + parseInt(starts[1]);
|
|
const endHoursInNumber = parseInt(ends[0]) * 60 + parseInt(ends[1]);
|
|
|
|
// If the reservation only last a day
|
|
if (isSameThanStartDate && isSameThanEndDate) {
|
|
thisDaysUnavailabilities.push({
|
|
startHours,
|
|
endHours,
|
|
startHoursInNumber,
|
|
endHoursInNumber,
|
|
});
|
|
}
|
|
|
|
// If the reservation last multiple days
|
|
else if (currentDayMoment >= startDateDay && currentDayMoment <= endDateDay) {
|
|
// check if start date
|
|
if (isSameThanStartDate)
|
|
thisDaysUnavailabilities.push({
|
|
startHours,
|
|
endHours: '23:59',
|
|
startHoursInNumber,
|
|
endHoursInNumber: 23 * 60 + 59,
|
|
});
|
|
else if (isSameThanEndDate)
|
|
thisDaysUnavailabilities.push({
|
|
startHours: '00:00',
|
|
endHours,
|
|
startHoursInNumber: 0,
|
|
endHoursInNumber,
|
|
});
|
|
else
|
|
thisDaysUnavailabilities.push({
|
|
startHours: '00:00',
|
|
endHours: '23:59',
|
|
startHoursInNumber: 0,
|
|
endHoursInNumber: 23 * 60 + 59,
|
|
});
|
|
}
|
|
});
|
|
return thisDaysUnavailabilities;
|
|
}
|
|
|
|
export function isTimeSlotInDates(dates: DateTimeRangeComponent[], startTime: string, endTime: string) {
|
|
const startTimesInMinutes = getMinutesFromTimeInString(startTime);
|
|
const endTimesInMinutes = getMinutesFromTimeInString(endTime);
|
|
|
|
// Check for each date if the minutes are in it
|
|
return dates.some((br) => {
|
|
const brStartTimeInMinutes = br.startDate.hour * 60 + br.startDate.minute;
|
|
const brEndTimeInMinutes = br.endDate.hour * 60 + br.endDate.minute;
|
|
return startTimesInMinutes < brEndTimeInMinutes && endTimesInMinutes > brStartTimeInMinutes;
|
|
});
|
|
}
|
|
|
|
export function converDateRangeToDateTime(
|
|
dates: DateRangeComponent[],
|
|
options?: {
|
|
zone?: string;
|
|
},
|
|
): DateTimeRangeComponent[] {
|
|
const results: DateTimeRangeComponent[] = [];
|
|
const zone = options?.zone || process.env.TIMEZONE || 'utc';
|
|
|
|
dates.forEach((element) => {
|
|
results.push({
|
|
startDate: DateTime.fromJSDate(element.startDate, { zone }),
|
|
endDate: DateTime.fromJSDate(element.endDate, { zone }),
|
|
});
|
|
});
|
|
|
|
return results;
|
|
}
|