/** @jsxRuntime classic */ /** @jsx jsx */ import { FC, useEffect, useMemo, useRef, useState } from 'react'; import { jsx, useTheme } from '@emotion/react'; import dayjs from 'dayjs'; import customParseFormat from 'dayjs/plugin/customParseFormat'; import { RecoilRoot } from 'recoil'; import { DndProvider } from './atoms/DndProvider'; import { MiddlewareContextProvider, useMiddlewareContext, } from './hooks/useMiddlewareContext'; import { ECalendarMode, ECalendarView, ICalendarProps } from './types'; import { bodyContainer, bodyContent, cssProvider, rootContainer, dndProviderContainer, } from './styles'; import { spaceHeight, spaceWidth } from './atoms/styles'; import { DailyViewVertical } from './atoms/daily/DailyViewVertical'; import { WeeklyView } from './atoms/weekly/WeeklyView'; import { oneHourHeight, oneHourWidth } from './utils/timeUtils'; import { HeaderContainer } from './atoms/HeaderContainer'; import { AsideContainer } from './atoms/AsideContainer'; import { DailyViewHorizontal } from './atoms/daily/horizontal/DailyViewHorizontal'; import { filterSlotsForDailyView, filterSlotsForOldSlots, getBodyContainerHeight, scrollToHorizontal, scrollToVertical, } from './utils'; import { MonthlyView } from './atoms/monthly/MonthlyView'; import { usePartialRender } from './hooks/usePartialRender'; dayjs.extend(customParseFormat); const isSafari = navigator.vendor && navigator.vendor.indexOf('Apple') > -1 && navigator.userAgent && navigator.userAgent.indexOf('CriOS') == -1 && navigator.userAgent.indexOf('FxiOS') == -1; const Calendar: FC = ({ spaces, widthChangeTrigger, slots, EventComponent, DragOverlayComponent, ResourceGroupHeader, onChange, options = {}, resourceId, }) => { const colors = useTheme(); const { mode = ECalendarMode.DAILY, view = ECalendarView.VERTICAL, date } = options || {}; const horizontal = view === ECalendarView.HORIZONTAL; const [width, setWidth] = useState(200); const { day: daysCount, incrementDay, setOptions, setPartialRanges, initiateEventsToSpaces, getTopAndHeight, } = useMiddlewareContext(); const headerRef = useRef(null); const bodyRef = useRef(null); const verticalSpacesRef = useRef(null); useEffect(() => { setOptions(options); }, [options]); const { from: minVertical, to: maxVertical } = usePartialRender({ ref: bodyRef, elementSize: view == ECalendarView.VERTICAL ? oneHourHeight : spaceHeight, disable: mode !== ECalendarMode.DAILY || !options?.partialRendering, }); const { from: minHorizontal, to: maxHorizontal } = usePartialRender({ ref: bodyRef, elementSize: view == ECalendarView.VERTICAL ? spaceWidth : oneHourWidth, disable: mode !== ECalendarMode.DAILY || !options?.partialRendering, direction: 'horizontal', }); const { horizontalFrom, horizontalTo, verticalFrom, verticalTo, } = useMemo(() => { const columnsCount = spaces.reduce( (sum, item) => sum + (item.children.length || 1), 0 ); const difference = columnsCount - spaces.length; const isVerticalView = view === ECalendarView.VERTICAL; const spaceTo = (isVerticalView ? maxHorizontal : maxVertical) - difference; let spaceFrom = (isVerticalView ? minHorizontal : minVertical) - difference; if (spaceFrom < 0) spaceFrom = 0; const hoursFrom = isVerticalView ? minVertical : minHorizontal; const hoursTo = isVerticalView ? maxVertical : maxHorizontal; if (isVerticalView) return { horizontalFrom: spaceFrom, horizontalTo: spaceTo, verticalFrom: hoursFrom, verticalTo: hoursTo, }; else return { horizontalFrom: hoursFrom, horizontalTo: hoursTo, verticalFrom: spaceFrom, verticalTo: spaceTo, }; }, [spaces, minHorizontal, maxHorizontal, minVertical, maxVertical, view]); useEffect(() => { setPartialRanges({ vertical: { from: verticalFrom, to: verticalTo }, horizontal: { from: horizontalFrom, to: horizontalTo }, }); }, [horizontalFrom, horizontalTo, verticalFrom, verticalTo]); useEffect(() => { if (slots && slots?.eventsToSpaces) { if (mode === ECalendarMode.DAILY && !options?.infiniteScrolling) { initiateEventsToSpaces({ events: slots.events, eventsToSpaces: filterSlotsForDailyView( slots.eventsToSpaces, options?.date ), }); } else if (mode === ECalendarMode.DAILY) { initiateEventsToSpaces({ events: slots.events, eventsToSpaces: filterSlotsForOldSlots( slots.eventsToSpaces, options?.date ), }); } else { initiateEventsToSpaces(slots); } } }, [slots, mode, view, options?.date, options?.infiniteScrolling]); useEffect(() => { let resizeObserver: ResizeObserver; if (bodyRef?.current) { resizeObserver = new ResizeObserver((data) => { setWidth(data?.[0]?.contentRect?.width || 0); }); resizeObserver.observe(bodyRef?.current); } return () => { resizeObserver.disconnect(); }; }, [view, options?.hourSize, bodyRef?.current]); useEffect(() => { const handleScroll = (e: Event) => { const body = e.target as Element; if ( headerRef.current && headerRef.current.scrollLeft !== body.scrollLeft ) { headerRef.current.scrollLeft = body.scrollLeft; } if ( verticalSpacesRef.current && verticalSpacesRef.current.scrollTop !== body.scrollTop ) { verticalSpacesRef.current.scrollTop = body.scrollTop; } if (options.infiniteScrolling && mode === ECalendarMode.DAILY) { let scrollPosition = 0; let frontierPosition = 0; if (view === ECalendarView.HORIZONTAL) { scrollPosition = body.scrollLeft + body.clientWidth; frontierPosition = body.scrollWidth * 0.8; } else { scrollPosition = body.scrollTop + body.clientHeight; frontierPosition = body.scrollHeight * 0.8; } if (scrollPosition > frontierPosition) { incrementDay(); } } }; bodyRef?.current?.addEventListener('scroll', handleScroll, false); return () => { bodyRef?.current?.removeEventListener('scroll', handleScroll); }; }, [ headerRef, bodyRef, daysCount, options, verticalSpacesRef, mode, view, date, ]); const scrollToHour = (now: number) => { if (mode === ECalendarMode.DAILY && bodyRef.current) { if (isSafari) { if (horizontal) { scrollToHorizontal(bodyRef.current, now * oneHourWidth); scrollToVertical(bodyRef.current, 0); } else { scrollToHorizontal(bodyRef.current, 0); scrollToVertical(bodyRef.current, now * oneHourHeight); } } else { if (horizontal) { bodyRef.current.scrollTo(now * oneHourWidth, 0); } else { bodyRef.current.scrollTo(0, now * oneHourHeight); } } } }; const scrollTo = () => { const { resourceId, startTime, endTime } = options?.scrollTo || {}; const body = bodyRef?.current; if (!body) return; const resource = document.getElementById(`resource_${resourceId}`); let resourcePosition = 0; if (resource) { if (horizontal) { resourcePosition = resource.offsetTop; } else { resourcePosition = resource.offsetLeft + resource.clientWidth / 2; } } let hoursPosition = 0; if (startTime && endTime) { const date = dayjs.utc(options?.date).format('DD/MM/YYYY'); const parseFormat = 'DD/MM/YYYY HH:mm'; const start = dayjs.utc(`${date} ${startTime}`, parseFormat); let end = dayjs.utc(`${date} ${endTime}`, parseFormat); if (dayjs.utc(start).diff(end, 'millisecond') >= 0) { end = end.add(1, 'day'); } const { top, height } = getTopAndHeight( start.toDate().getTime(), end.toDate().getTime(), horizontal ); hoursPosition = top + height / 2; if (!horizontal) hoursPosition += height / 2; } const halfBodyWidth = body.clientWidth / 2; const halfHoursHeight = body.clientHeight / 2; if (isSafari) { if (horizontal) { if (hoursPosition) { scrollToHorizontal(body, hoursPosition - halfBodyWidth); } if (resourcePosition) { scrollToVertical(body, resourcePosition - halfHoursHeight); } } else { if (resourcePosition) { scrollToHorizontal(body, resourcePosition - halfBodyWidth); } if (hoursPosition) { scrollToVertical(body, hoursPosition - halfHoursHeight); } } } else { if (horizontal) { if (hoursPosition && resourcePosition) { body.scrollTo( hoursPosition - halfBodyWidth, resourcePosition - halfHoursHeight ); } else if (hoursPosition) { body.scrollTo(hoursPosition - halfBodyWidth, body.scrollTop); } else if (resourcePosition) { body.scrollTo(body.scrollLeft, resourcePosition - halfHoursHeight); } } else { if (hoursPosition && resourcePosition) { body.scrollTo( resourcePosition - halfBodyWidth, hoursPosition - halfHoursHeight ); } else if (resourcePosition) { body.scrollTo(resourcePosition - halfBodyWidth, body.scrollTop); } else if (hoursPosition) { body.scrollTo(body.scrollLeft, hoursPosition - halfHoursHeight); } } } }; useEffect(() => { scrollTo(); }, [options?.scrollTo]); useEffect(() => { if (options?.mode === ECalendarMode.DAILY) { const now = Number(dayjs.utc().format('HH')); scrollToHour(now); } }, [options?.mode, options?.view]); const bodyContainerHeight = getBodyContainerHeight(spaces, mode, horizontal); return (
{mode === ECalendarMode.DAILY && (horizontal ? ( ) : ( ))} {mode === ECalendarMode.WEEKLY && ( )} {mode === ECalendarMode.MONTHLY && ( )}
); }; export const SACalendar = (props: ICalendarProps) => ( );