/** @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) => ( )), ]; } return buttons; } return list.map((item) => ( )); }, [list, handleButtonClick, daysCount]); return {listOfButtons}; } ); function AddNewSlotButton({ item, horizontal, handleButtonClick, }: { item: IPlusButtonData; horizontal?: boolean; handleButtonClick: (item: IPlusButtonData) => void; }) { const colors = useTheme() as IColors; const { options = {} } = useMiddlewareContext(); const { addNewSlotButton = {} } = options; const { hoverDelay = '.3s' } = addNewSlotButton; const shift = horizontal ? 0 : 6; const hourSize = horizontal ? oneHourWidth : oneHourHeight; const currentHour = (item.top - shift) / hourSize; const currentMinutes = Math.round( (currentHour - Math.floor(currentHour)) * 60 ); const hoursLabel = dayjs .utc() .hour(currentHour) .minute(currentMinutes) .format('hh:mma'); return (
{hoursLabel}
handleButtonClick(item)} >
); }