/** @jsxRuntime classic */
/** @jsx jsx */
import { Fragment, useMemo, memo, useCallback } from 'react';
import { jsx, useTheme } from '@emotion/react';
import dayjs from 'dayjs';
import {
IEventsGroup,
IAddNewSlotButtonsProps,
IPlusButtonData,
} from '../../types';
import { AddNewSlotButtonsContainer } from '../styles';
import {
calculateMarginFromMinutes,
calculateMarginLeftFromMinutes,
oneHourHeight,
oneHourWidth,
} from '../../utils/timeUtils';
import { Plus } from '../../../components/icons_v2/all/Plus';
import { IColors } from '../../../types/theme';
import { useMiddlewareContext } from '../../hooks/useMiddlewareContext';
const defaultDuration = 60;
const defaultStep = 60;
const minimumMinutes = 10;
export const AddNewSlotButtons = memo(
({
events,
onClick = (data: any) => {},
horizontal,
parentId,
}: IAddNewSlotButtonsProps) => {
const { options, getTopAndHeight, day: daysCount } = useMiddlewareContext();
const { date, addNewSlotButton = {} } = options || {};
const { duration, step } = addNewSlotButton;
const currentDate = dayjs.utc(date);
const shift = horizontal ? 0 : 6;
const hourSize = horizontal ? oneHourWidth : oneHourHeight;
const minimumSpace = hourSize / 2;
const getHeightByMinutes = (minutes: number) =>
horizontal
? calculateMarginLeftFromMinutes(minutes)
: calculateMarginFromMinutes(minutes);
const buttonDuration = Math.max(
minimumMinutes,
duration || defaultDuration
);
const buttonStep = Math.max(minimumMinutes, step || defaultStep);
const buttonDurationHeight = getHeightByMinutes(buttonDuration);
const buttonStepHeight = getHeightByMinutes(buttonStep);
const handleButtonClick = useCallback(
(item: IPlusButtonData) => {
let currentHour = (item.top - shift) / hourSize;
let currentMinutes = Math.round(
(currentHour - Math.floor(currentHour)) * 60
);
const startDate = dayjs
.utc(currentDate)
.hour(currentHour)
.minute(currentMinutes)
.toISOString();
const startTime = dayjs
.utc(currentDate)
.hour(currentHour)
.minute(currentMinutes)
.format('HH:mm');
currentHour = (item.bottom - shift) / hourSize;
currentMinutes = Math.round(
(currentHour - Math.floor(currentHour)) * 60
);
const endDate = dayjs
.utc(currentDate)
.hour(currentHour)
.minute(currentMinutes)
.toISOString();
const endTime = dayjs
.utc(currentDate)
.hour(currentHour)
.minute(currentMinutes)
.format('HH:mm');
if (onClick) {
onClick({
type: 'NEW_BOOKING',
data: {
startTime,
endTime,
startDate,
endDate,
parentId,
},
});
}
},
[onClick, parentId, shift, hourSize, currentDate]
);
const list = useMemo(() => {
// make a list of events/groups like IPlusButtonData objects
const eventsItems = (JSON.parse(events) as IEventsGroup[])
.map(({ item, ...rest }) => {
if (!item) {
const { top = 0, height = 0 } = rest;
return {
top,
height,
bottom: top + height,
isEvent: true,
isGroup: true,
} as IPlusButtonData;
}
const { top, height } = getTopAndHeight(
dayjs.utc(item.startDate).toDate().getTime(),
dayjs.utc(item.endDate).toDate().getTime(),
horizontal
);
return {
top,
height,
bottom: top + height,
isEvent: true,
} as IPlusButtonData;
})
.filter(
(item) =>
item.top >= shift && item.bottom < 24 * hourSize * daysCount + shift
)
.sort((a, b) => a.top - b.top);
if (eventsItems.length === 0) return [];
let list = eventsItems.slice();
// fill top empty space
let topEmptySpace = eventsItems[0].top - shift;
let emptyHoursCount = Math.floor(topEmptySpace / buttonStepHeight);
let emptyTopSpaces = [] as IPlusButtonData[];
for (let i = 0; i < emptyHoursCount; i++) {
emptyTopSpaces.push({
top: shift + i * buttonStepHeight,
height: buttonDurationHeight,
bottom: i * hourSize + buttonDurationHeight + shift,
});
}
let last = emptyTopSpaces[emptyTopSpaces.length - 1];
if (last) {
last = {
...last,
bottom: eventsItems[0].top,
height: eventsItems[0].top - last.top,
};
}
if (last) {
list = [...list, ...emptyTopSpaces.slice(0, -1), { ...last }].sort(
(a, b) => a.top - b.top
);
} else {
if (eventsItems[0].top <= buttonDurationHeight + shift) {
list = [
{
top: shift,
height: eventsItems[0].top - shift,
bottom: eventsItems[0].top,
},
...list,
].sort((a, b) => a.top - b.top);
}
}
// end filling top empty space
// fill spaces between events
for (let i = 0; i < eventsItems.length - 1; i++) {
const current = eventsItems.slice()[i];
const next = eventsItems.slice()[i + 1];
const space = next.top - current.bottom;
// check less than one empty place between 2 events
if (
space > getHeightByMinutes(minimumMinutes) &&
space < buttonStepHeight
) {
list.push({
top: current.bottom,
bottom: next.top,
height: space,
});
} else if (space > getHeightByMinutes(minimumMinutes)) {
const emptyHoursCount = Math.floor(space / buttonStepHeight);
const currentHour = (current.bottom - shift) / hourSize;
let currentMinutes = Math.round(
(currentHour - Math.floor(currentHour)) * 60
);
while (currentMinutes % buttonStep !== 0) currentMinutes++;
let emptyHours = [] as IPlusButtonData[];
const shiftSpace =
getHeightByMinutes(Math.floor(currentHour) * 60 + currentMinutes) +
shift;
for (let i = 0; i < emptyHoursCount; i++) {
emptyHours.push({
top: i * buttonStepHeight + shiftSpace,
height: buttonDurationHeight,
bottom: i * buttonStepHeight + shiftSpace + buttonDurationHeight,
});
}
// Feeling space after last event/group and first button
emptyHours[0] = {
...emptyHours[0],
top: current.bottom,
height: emptyHours[0].bottom - current.bottom,
};
// Feeling space before next event/group and last button
emptyHours[emptyHours.length - 1] = {
...emptyHours[emptyHours.length - 1],
bottom: next.top,
height: next.top - emptyHours[emptyHours.length - 1].top,
};
list = list.concat(emptyHours).sort((a, b) => a.top - b.top);
}
}
list.sort((a, b) => a.top - b.top);
// end filling spaces between events
// fill bottom space
let bottomEmptySpace =
24 * hourSize * daysCount +
shift -
eventsItems[eventsItems.length - 1].bottom;
let currentHour =
(eventsItems[eventsItems.length - 1].bottom - shift) / hourSize;
if (Math.floor(currentHour) === 24 * daysCount) currentHour--;
let currentMinutes = Math.round(
(currentHour - Math.floor(currentHour)) * 60
);
while (currentMinutes % buttonStep !== 0) currentMinutes++;
const shiftSpace =
getHeightByMinutes(Math.floor(currentHour) * 60 + currentMinutes) +
shift;
emptyHoursCount = Math.floor(bottomEmptySpace / buttonStepHeight);
let emptyBottomSpaces = [] as IPlusButtonData[];
for (let i = 0; i < emptyHoursCount; i++) {
emptyBottomSpaces.push({
top: i * buttonStepHeight + shiftSpace,
height: buttonDurationHeight,
bottom: i * buttonStepHeight + shiftSpace + buttonDurationHeight,
});
}
if (emptyHoursCount > 0 && emptyBottomSpaces[0]) {
emptyBottomSpaces[0] = {
bottom: emptyBottomSpaces[0].bottom,
top: eventsItems[eventsItems.length - 1].bottom,
height:
emptyBottomSpaces[0].bottom -
eventsItems[eventsItems.length - 1].bottom,
};
emptyBottomSpaces[emptyBottomSpaces.length - 1] = {
...emptyBottomSpaces[emptyBottomSpaces.length - 1],
bottom: emptyBottomSpaces[emptyBottomSpaces.length - 1].bottom,
height:
daysCount * 24 * hourSize +
shift -
emptyBottomSpaces[emptyBottomSpaces.length - 1].top,
};
} else {
const top = eventsItems[eventsItems.length - 1].bottom;
const bottom = daysCount * hourSize * 24 + shift;
const height = bottom - top;
if (height >= minimumSpace) {
emptyBottomSpaces.push({
top,
height,
bottom,
});
}
}
list = list.concat(emptyBottomSpaces);
return list
.filter(
(item) =>
!item.isEvent &&
item.top + item.height <= daysCount * 24 * hourSize + shift
)
.sort((a, b) => a.top - b.top);
}, [events, daysCount]);
const listOfButtons = useMemo(() => {
if (list.length === 0) {
let buttons = [] as any[];
for (let day = 0; day < daysCount; day++) {
const getTop = (i: number) =>
i * buttonStepHeight + day * hourSize * 24 + shift;
const count = Math.floor((24 * 60) / buttonStep);
buttons = [
...buttons,
...Array(count)
.fill(0)
.map((_, i) => {
const isLast = day + 1 === daysCount && i + 1 === count;
return {
top: getTop(i),
bottom: getTop(i) + buttonDurationHeight,
height: isLast
? Math.min(
buttonDurationHeight,
daysCount * 24 * hourSize + shift - getTop(i)
)
: buttonDurationHeight,
} as IPlusButtonData;
})
.filter(
(item) =>
item.top + item.height <= daysCount * 24 * hourSize + shift
)
.map((item) => (