From 0ce54c6ec47647359d62351af60d64823c65a454 Mon Sep 17 00:00:00 2001 From: Pedro Roque Date: Thu, 12 Sep 2024 15:50:49 +0100 Subject: [PATCH 1/9] Converted files to TypeScript. --- src/classComponents/DnDContext.ts | 235 +++ src/classComponents/DnDSource.ts | 191 ++ src/classComponents/EventItem.tsx | 827 +++++++++ src/classComponents/ResourceEvents.tsx | 552 ++++++ src/components/AddMore.jsx | 25 - src/components/AddMore.tsx | 45 + src/components/AddMorePopover.jsx | 80 - src/components/AddMorePopover.tsx | 104 ++ src/components/AgendaEventItem.jsx | 67 - src/components/AgendaEventItem.tsx | 133 ++ src/components/AgendaResourceEvents.jsx | 74 - src/components/AgendaResourceEvents.tsx | 137 ++ src/components/AgendaView.jsx | 46 - src/components/AgendaView.tsx | 72 + src/components/{BodyView.jsx => BodyView.tsx} | 21 +- src/components/DnDContext.js | 138 -- src/components/DnDSource.js | 103 -- src/components/EventItem.jsx | 560 ------ src/components/EventItemPopover.jsx | 122 -- src/components/EventItemPopover.tsx | 175 ++ src/components/HeaderView.jsx | 102 -- src/components/HeaderView.tsx | 154 ++ src/components/ResourceEvents.jsx | 368 ---- src/components/ResourceView.jsx | 96 - src/components/ResourceView.tsx | 149 ++ src/components/Scheduler.tsx | 723 ++++++++ src/components/SchedulerData.js | 1207 ------------- src/components/SchedulerData.ts | 1581 +++++++++++++++++ src/components/SchedulerHeader.jsx | 117 -- src/components/SchedulerHeader.tsx | 161 ++ src/components/SelectedArea.jsx | 24 - src/components/SelectedArea.tsx | 31 + src/components/Summary.jsx | 40 - src/components/Summary.tsx | 52 + .../{WrapperFun.jsx => WrapperFun.tsx} | 7 +- src/components/index.jsx | 448 ----- src/config/default.js | 11 - src/config/default.ts | 35 + src/config/scheduler.js | 96 - src/config/scheduler.ts | 131 ++ src/css/style.css | 1 + src/helper/behaviors.js | 85 - src/helper/behaviors.ts | 151 ++ src/helper/{utility.js => utility.ts} | 2 + src/{index.js => index.ts} | 2 +- src/{main.jsx => main.tsx} | 0 src/types/baseType.ts | 314 ++++ src/types/moreTypes.ts | 301 ++++ typing/index.d.ts | 378 ---- 49 files changed, 6277 insertions(+), 4197 deletions(-) create mode 100644 src/classComponents/DnDContext.ts create mode 100644 src/classComponents/DnDSource.ts create mode 100644 src/classComponents/EventItem.tsx create mode 100644 src/classComponents/ResourceEvents.tsx delete mode 100644 src/components/AddMore.jsx create mode 100644 src/components/AddMore.tsx delete mode 100644 src/components/AddMorePopover.jsx create mode 100644 src/components/AddMorePopover.tsx delete mode 100644 src/components/AgendaEventItem.jsx create mode 100644 src/components/AgendaEventItem.tsx delete mode 100644 src/components/AgendaResourceEvents.jsx create mode 100644 src/components/AgendaResourceEvents.tsx delete mode 100644 src/components/AgendaView.jsx create mode 100644 src/components/AgendaView.tsx rename src/components/{BodyView.jsx => BodyView.tsx} (71%) delete mode 100644 src/components/DnDContext.js delete mode 100644 src/components/DnDSource.js delete mode 100644 src/components/EventItem.jsx delete mode 100644 src/components/EventItemPopover.jsx create mode 100644 src/components/EventItemPopover.tsx delete mode 100644 src/components/HeaderView.jsx create mode 100644 src/components/HeaderView.tsx delete mode 100644 src/components/ResourceEvents.jsx delete mode 100644 src/components/ResourceView.jsx create mode 100644 src/components/ResourceView.tsx create mode 100644 src/components/Scheduler.tsx delete mode 100644 src/components/SchedulerData.js create mode 100644 src/components/SchedulerData.ts delete mode 100644 src/components/SchedulerHeader.jsx create mode 100644 src/components/SchedulerHeader.tsx delete mode 100644 src/components/SelectedArea.jsx create mode 100644 src/components/SelectedArea.tsx delete mode 100644 src/components/Summary.jsx create mode 100644 src/components/Summary.tsx rename src/components/{WrapperFun.jsx => WrapperFun.tsx} (65%) delete mode 100644 src/components/index.jsx delete mode 100644 src/config/default.js create mode 100644 src/config/default.ts delete mode 100644 src/config/scheduler.js create mode 100644 src/config/scheduler.ts delete mode 100644 src/helper/behaviors.js create mode 100644 src/helper/behaviors.ts rename src/helper/{utility.js => utility.ts} (95%) rename src/{index.js => index.ts} (84%) rename src/{main.jsx => main.tsx} (100%) create mode 100644 src/types/baseType.ts create mode 100644 src/types/moreTypes.ts delete mode 100644 typing/index.d.ts diff --git a/src/classComponents/DnDContext.ts b/src/classComponents/DnDContext.ts new file mode 100644 index 0000000..343bc2f --- /dev/null +++ b/src/classComponents/DnDContext.ts @@ -0,0 +1,235 @@ +import { DropTarget, DropTargetMonitor, DropTargetConnector } from "react-dnd"; +import { + DnDTypes, + CellUnit, + DATETIME_FORMAT, + ViewType, +} from "../config/default"; +import { getPos } from "../helper/utility"; +import dayjs, { Dayjs } from "dayjs"; +import { SchedulerData } from "../components/Scheduler"; +import { EventItemType, RenderDataItem, ResourceEvent } from "../types/baseType"; +import { DnDSource } from "./DnDSource"; +// Types in this in this file have been generated by AI and are not accurate. Please replace them with the correct types. + +interface DnDContextProps { + schedulerData: SchedulerData; + resourceEvents: RenderDataItem; + movingEvent?: ( + schedulerData: SchedulerData, + slotId: string, + slotName: string, + newStart: Dayjs, + newEnd: Dayjs, + action: string, + type: DnDTypes, + item: EventItemType + ) => void; +} + +export class DnDContext { + private sourceMap: Map; + private DecoratedComponent: React.ComponentType; + private config: any; + + constructor( + sources: DnDSource[], + DecoratedComponent: React.ComponentType + ) { + this.sourceMap = new Map(); + sources.forEach((item) => { + this.sourceMap.set(item.dndType, item); + }); + this.DecoratedComponent = DecoratedComponent; + } + + extractInitialTimes = ( + monitor: DropTargetMonitor, + pos: { x: number; y: number }, + cellWidth: number, + resourceEvents: RenderDataItem, + cellUnit: CellUnit, + localeDayjs: typeof dayjs + ) => { + const initialPoint = monitor.getInitialClientOffset(); + let initialStart = localeDayjs(); + let initialEnd = localeDayjs(); + if (!initialPoint) return { initialStart, initialEnd }; + const initialLeftIndex = Math.floor((initialPoint.x - pos.x) / cellWidth); + + if (resourceEvents.headerItems[initialLeftIndex]?.start) { + initialStart = resourceEvents.headerItems[initialLeftIndex].start; + } + if (resourceEvents.headerItems[initialLeftIndex]?.end) { + let initialEnd = resourceEvents.headerItems[initialLeftIndex]?.end; + if (cellUnit !== CellUnit.Hour) { + var end = initialStart.hour(23).minute(59).second(59); + initialEnd = end; + } + } + return { initialStart, initialEnd }; + }; + + getDropSpec = () => ({ + drop: ( + props: DnDContextProps, + monitor: DropTargetMonitor, + component: any + ) => { + const { schedulerData, resourceEvents } = props; + const { cellUnit, localeDayjs } = schedulerData; + const type = monitor.getItemType(); + const pos = getPos(component.eventContainer); + const cellWidth = schedulerData.getContentCellWidth(); + let initialStartTime = null; + let initialEndTime = null; + if (type === DnDTypes.EVENT) { + const { initialStart, initialEnd } = this.extractInitialTimes( + monitor, + pos, + cellWidth, + resourceEvents, + cellUnit, + localeDayjs + ); + initialStartTime = initialStart; + initialEndTime = initialEnd; + } + const point = monitor.getClientOffset(); + if (!point) return null; + const leftIndex = Math.floor((point.x - pos.x) / cellWidth); + const startTime = + resourceEvents.headerItems[leftIndex]?.start || localeDayjs(); + let endTime = resourceEvents.headerItems[leftIndex]?.end || localeDayjs(); + if (cellUnit !== CellUnit.Hour) { + endTime = ( + resourceEvents.headerItems[leftIndex]?.start || localeDayjs() + ) + .hour(23) + .minute(59) + .second(59); + } + + return { + slotId: resourceEvents.slotId, + slotName: resourceEvents.slotName, + start: startTime, + end: endTime, + initialStart: initialStartTime, + initialEnd: initialEndTime, + }; + }, + + hover: ( + props: DnDContextProps, + monitor: DropTargetMonitor, + component: any + ) => { + const { schedulerData, resourceEvents, movingEvent } = props; + const { cellUnit, config, viewType, localeDayjs } = schedulerData; + this.config = config; + const item: EventItemType = monitor.getItem(); + const type = monitor.getItemType() as DnDTypes; + const pos = getPos(component.eventContainer); + const cellWidth = schedulerData.getContentCellWidth(); + let initialStart = null; + if (type === DnDTypes.EVENT) { + const { initialStart: iStart } = this.extractInitialTimes( + monitor, + pos, + cellWidth, + resourceEvents, + cellUnit, + localeDayjs + ); + initialStart = iStart; + } + + const point = monitor.getClientOffset(); + if (!point) return; + const leftIndex = Math.floor((point.x - pos.x) / cellWidth); + if (!resourceEvents.headerItems[leftIndex]) { + return; + } + let newStart = resourceEvents.headerItems[leftIndex].start; + let newEnd = resourceEvents.headerItems[leftIndex].end; + if (cellUnit !== CellUnit.Hour) { + newEnd = (resourceEvents.headerItems[leftIndex].start || localeDayjs()) + .hour(23) + .minute(59) + .second(59); + } + let { slotId, slotName } = resourceEvents; + let action = "New"; + const isEvent = type === DnDTypes.EVENT; + if (isEvent) { + const event = item; + if (config.relativeMove) { + newStart = event.start.add(newStart.diff(initialStart), "ms"); + } else if (viewType !== ViewType.Day) { + const tmpDayjs = localeDayjs(newStart); + newStart = event.start + .year(tmpDayjs.year()) + .month(tmpDayjs.month()) + .date(tmpDayjs.date()); + } + newEnd = localeDayjs(newStart).add(event.end.diff(event.start), "ms"); + + if (config.crossResourceMove === false) { + slotId = schedulerData._getEventSlotId(item); + // @ts-ignore TODO: Fix this type + slotName = undefined; + const slot = schedulerData.getSlotById(slotId); + if (slot) slotName = slot.name; + } + + action = "Move"; + } + + if (movingEvent) { + movingEvent( + schedulerData, + slotId, + slotName, + newStart, + newEnd, + action, + type, + item + ); + } + }, + + canDrop: (props: DnDContextProps, monitor: DropTargetMonitor) => { + const { schedulerData, resourceEvents } = props; + const item: EventItemType = monitor.getItem(); + if (schedulerData._isResizing()) return false; + const { config } = schedulerData; + return ( + config.movable && + !resourceEvents.groupOnly && + (item.movable === undefined || item.movable !== false) + ); + }, + }); + + getDropCollect = ( + connect: DropTargetConnector, + monitor: DropTargetMonitor + ) => ({ + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver(), + }); + + getDropTarget = (dragAndDropEnabled: boolean) => + dragAndDropEnabled + ? DropTarget( + [...this.sourceMap.keys()], + this.getDropSpec(), + this.getDropCollect + )(this.DecoratedComponent) + : this.DecoratedComponent; + + getDndSource = (dndType: string = DnDTypes.EVENT) => + this.sourceMap.get(dndType); +} diff --git a/src/classComponents/DnDSource.ts b/src/classComponents/DnDSource.ts new file mode 100644 index 0000000..bc5e3e7 --- /dev/null +++ b/src/classComponents/DnDSource.ts @@ -0,0 +1,191 @@ +import { DragSource, DragSourceConnector, DragSourceMonitor } from "react-dnd"; +import { DnDTypes, ViewType, DATETIME_FORMAT } from "../config/default"; +import dayjs from "dayjs"; +import { SchedulerData } from "../components/SchedulerData"; +import { EventItemType } from "../types/baseType"; + +interface DnDSourceProps { + schedulerData: SchedulerData; + moveEvent?: ( + schedulerData: SchedulerData, + eventItem: EventItemType, + slotId: string, + slotName: string, + newStart: string, + newEnd: string + ) => void; + newEvent?: ( + schedulerData: SchedulerData, + slotId: string, + slotName: string, + newStart: string, + newEnd: string, + type: string, + item: any + ) => void; + conflictOccurred?: ( + schedulerData: SchedulerData, + action: string, + item: any, + type: string, + slotId: string, + slotName: string, + newStart: string, + newEnd: string + ) => void; + resourceEvents?: { groupOnly: boolean }; +} + +interface DragObject { + type: string; + item: any; +} + +export class DnDSource { + private resolveDragObjFunc: (props: any) => any; + private DecoratedComponent: React.ComponentType; + public dndType: string; + private dragSource: React.ComponentType; + + constructor( + resolveDragObjFunc: (props: any) => any, + DecoratedComponent: React.ComponentType, + DnDEnabled: boolean, + dndType: string = DnDTypes.EVENT + ) { + this.resolveDragObjFunc = resolveDragObjFunc; + this.DecoratedComponent = DecoratedComponent; + this.dndType = dndType; + this.dragSource = DnDEnabled + ? DragSource( + this.dndType, + this.getDragSpec(), + this.getDragCollect + )(this.DecoratedComponent) + : this.DecoratedComponent; + } + + getDragSpec = () => ({ + beginDrag: (props: DnDSourceProps) => this.resolveDragObjFunc(props), + endDrag: (props: DnDSourceProps, monitor: DragSourceMonitor) => { + if (!monitor.didDrop()) return; + + const { moveEvent, newEvent, schedulerData } = props; + const { events, config, viewType, localeDayjs } = schedulerData; + const item = monitor.getItem() as any; + const type = monitor.getItemType() as string; + const dropResult = monitor.getDropResult() as any; + let { + slotId, + slotName, + start: newStart, + end: newEnd, + initialStart, + } = dropResult; + let action = "New"; + + const isEvent = type === DnDTypes.EVENT; + if (isEvent) { + const event = item; + if (config.relativeMove) { + newStart = localeDayjs(event.start) + .add( + localeDayjs(newStart).diff(localeDayjs(new Date(initialStart))), + "ms" + ) + .format(DATETIME_FORMAT); + } else if (viewType !== ViewType.Day) { + const tmpDayjs = localeDayjs(newStart); + newStart = localeDayjs(event.start) + .year(tmpDayjs.year()) + .month(tmpDayjs.month()) + .date(tmpDayjs.date()) + .format(DATETIME_FORMAT); + } + newEnd = localeDayjs(newStart) + .add(localeDayjs(event.end).diff(localeDayjs(event.start)), "ms") + .format(DATETIME_FORMAT); + + if (config.crossResourceMove === false) { + slotId = schedulerData._getEventSlotId(item); + slotName = undefined; + const slot = schedulerData.getSlotById(slotId); + if (slot) slotName = slot.name; + } + + action = "Move"; + } + + let hasConflict = false; + if (config.checkConflict) { + const start = localeDayjs(newStart); + const end = localeDayjs(newEnd); + + events.forEach((e) => { + if ( + schedulerData._getEventSlotId(e) === slotId && + (!isEvent || e.id !== item.id) + ) { + const eStart = localeDayjs(e.start); + const eEnd = localeDayjs(e.end); + if ( + (start >= eStart && start < eEnd) || + (end > eStart && end <= eEnd) || + (eStart >= start && eStart < end) || + (eEnd > start && eEnd <= end) + ) + hasConflict = true; + } + }); + } + + if (hasConflict) { + const { conflictOccurred } = props; + if (conflictOccurred !== undefined) { + conflictOccurred( + schedulerData, + action, + item, + type, + slotId, + slotName, + newStart, + newEnd + ); + } else { + console.log( + "Conflict occurred, set conflictOccurred func in Scheduler to handle it" + ); + } + } else if (isEvent) { + if (moveEvent !== undefined) { + moveEvent(schedulerData, item, slotId, slotName, newStart, newEnd); + } + } else if (newEvent !== undefined) + newEvent(schedulerData, slotId, slotName, newStart, newEnd, type, item); + }, + + canDrag: (props: DnDSourceProps) => { + const { schedulerData, resourceEvents } = props; + const item = this.resolveDragObjFunc(props); + if (schedulerData._isResizing()) return false; + const { config } = schedulerData; + return ( + config.movable && + (resourceEvents === undefined || !resourceEvents.groupOnly) && + (item.movable === undefined || item.movable !== false) + ); + }, + }); + + getDragCollect = ( + connect: DragSourceConnector, + monitor: DragSourceMonitor + ) => ({ + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging(), + connectDragPreview: connect.dragPreview(), + }); + + getDragSource = () => this.dragSource; +} diff --git a/src/classComponents/EventItem.tsx b/src/classComponents/EventItem.tsx new file mode 100644 index 0000000..c82ddc9 --- /dev/null +++ b/src/classComponents/EventItem.tsx @@ -0,0 +1,827 @@ +//@ts-nocheck + +/* eslint-disable no-return-assign */ +import React, { Component, createRef } from "react"; +import { Popover } from "antd"; +import EventItemPopover from "../components/EventItemPopover"; +import { DnDTypes, CellUnit, DATETIME_FORMAT } from "../config/default"; +import dayjs, { Dayjs } from "dayjs"; +import { EventItemType } from "../types/baseType"; +import { SchedulerData } from "../components/SchedulerData"; +import { + ConflictOccurredFunc, + EventItemClickFunc, + EventItemTemplateResolverFunc, + MoveEventFunc, + SubtitleGetterFunc, + UpdateEventEndFunc, + UpdateEventStartFunc, + ViewEvent2ClickFunc, + ViewEventClickFunc, +} from "../types/moreTypes"; +import { ConnectDragPreview, ConnectDragSource } from "react-dnd"; + +interface StopDragHelperParams { + count: number; + cellUnit: CellUnit; + config: { displayWeekend: boolean }; + dragtype: string; + eventItem: EventItemType; + localeDayjs: typeof dayjs; + value: string; +} + +const stopDragHelper = ({ + count, + cellUnit, + config, + dragtype, + eventItem, + localeDayjs, + value, +}: StopDragHelperParams): Promise => { + const whileTrue = true; + let tCount = 0; + let i = 0; + let result = value; + return new Promise((resolve) => { + if ( + count !== 0 && + cellUnit !== CellUnit.Hour && + config.displayWeekend === false + ) { + while (whileTrue) { + i = count > 0 ? i + 1 : i - 1; + const date = localeDayjs(new Date(eventItem[dragtype])).add(i, "days"); + const dayOfWeek = date.weekday(); + + if (dayOfWeek !== 0 && dayOfWeek !== 6) { + tCount = count > 0 ? tCount + 1 : tCount - 1; + if (tCount === count) { + result = date; + break; + } + } + } + } + resolve(result); + }); +}; + +interface ResizableParams { + eventItem: EventItemType; + isInPopover: boolean; + schedulerData: SchedulerData; +} + +const startResizable = ({ + eventItem, + isInPopover, + schedulerData, +}: ResizableParams): boolean => + schedulerData.config.startResizable === true && + isInPopover === false && + (eventItem.resizable === undefined || eventItem.resizable !== false) && + (eventItem.startResizable === undefined || + eventItem.startResizable !== false); + +const endResizable = ({ + eventItem, + isInPopover, + schedulerData, +}: ResizableParams): boolean => + schedulerData.config.endResizable === true && + isInPopover === false && + (eventItem.resizable === undefined || eventItem.resizable !== false) && + (eventItem.endResizable === undefined || eventItem.endResizable !== false); + +interface EventItemProps { + schedulerData: SchedulerData; + eventItem: EventItemType; + isStart: boolean; + isEnd: boolean; + left: number; + width: number; + top: number; + isInPopover: boolean; + leftIndex: number; + rightIndex: number; + isDragging?: boolean; + connectDragSource?: ConnectDragSource; + connectDragPreview?: ConnectDragPreview; + updateEventStart?: UpdateEventStartFunc; + updateEventEnd?: UpdateEventEndFunc; + moveEvent?: MoveEventFunc; + subtitleGetter?: SubtitleGetterFunc; + eventItemClick?: EventItemClickFunc; + viewEventClick?: ViewEventClickFunc; + viewEventText?: string; + viewEvent2Click?: ViewEvent2ClickFunc; + viewEvent2Text?: string; + conflictOccurred?: ConflictOccurredFunc; + eventItemTemplateResolver?: EventItemTemplateResolverFunc; +} + +interface EventItemState { + left: number; + top: number; + width: number; + contentMousePosX: number; + eventItemLeftRect: number; + eventItemRightRect: number; + startX?: number; + endX?: number; +} + +export class EventItemComponent extends Component< + EventItemProps, + EventItemState +> { + private startResizer: HTMLDivElement | undefined; + private endResizer: HTMLDivElement | undefined; + private supportTouch: boolean; + private eventItemRef: React.RefObject; + private _isMounted: boolean; + + constructor(props: EventItemProps) { + super(props); + + const { left, top, width } = props; + this.state = { + left, + top, + width, + contentMousePosX: 0, + eventItemLeftRect: 0, + eventItemRightRect: 0, + }; + this.startResizer = undefined; + this.endResizer = undefined; + + this.supportTouch = false; // 'ontouchstart' in window; + + this.eventItemRef = createRef(); + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + this.supportTouch = "ontouchstart" in window; + this.subscribeResizeEvent(this.props); + } + + componentDidUpdate(prevProps: EventItemProps) { + if (prevProps !== this.props) { + const { left, top, width } = this.props; + this.setState({ left, top, width }); + + this.subscribeResizeEvent(this.props); + } + } + + resizerHelper = ( + dragtype: "start" | "end", + eventType: "addEventListener" | "removeEventListener" + ) => { + const resizer = dragtype === "start" ? this.startResizer : this.endResizer; + const doDrag = dragtype === "start" ? this.doStartDrag : this.doEndDrag; + const stopDrag = + dragtype === "start" ? this.stopStartDrag : this.stopEndDrag; + const cancelDrag = + dragtype === "start" ? this.cancelStartDrag : this.cancelEndDrag; + if (this.supportTouch) { + resizer?.[eventType]("touchmove", doDrag, false); + resizer?.[eventType]("touchend", stopDrag, false); + resizer?.[eventType]("touchcancel", cancelDrag, false); + } else { + document.documentElement[eventType]("mousemove", doDrag, false); + document.documentElement[eventType]("mouseup", stopDrag, false); + } + }; + + initDragHelper = (ev: MouseEvent | TouchEvent, dragtype: "start" | "end") => { + const { schedulerData, eventItem } = this.props; + const slotId = schedulerData._getEventSlotId(eventItem); + const slot = schedulerData.getSlotById(slotId); + if (!!slot && !!slot.groupOnly) return; + if (schedulerData._isResizing()) return; + + ev.stopPropagation(); + let clientX = 0; + if (this.supportTouch) { + if ((ev as TouchEvent).changedTouches.length === 0) return; + const touch = (ev as TouchEvent).changedTouches[0]; + clientX = touch.pageX; + } else { + if ( + (ev as MouseEvent).buttons !== undefined && + (ev as MouseEvent).buttons !== 1 + ) + return; + clientX = (ev as MouseEvent).clientX; + } + this.setState({ + [dragtype === "start" ? "startX" : "endX"]: clientX, + } as Pick); + + schedulerData._startResizing(); + this.resizerHelper(dragtype, "addEventListener"); + document.onselectstart = () => false; + document.ondragstart = () => false; + }; + + initStartDrag = (ev: MouseEvent | TouchEvent) => { + this.initDragHelper(ev, "start"); + }; + + doStartDrag = (ev: MouseEvent | TouchEvent) => { + ev.stopPropagation(); + + let clientX = 0; + if (this.supportTouch) { + if ((ev as TouchEvent).changedTouches.length === 0) return; + const touch = (ev as TouchEvent).changedTouches[0]; + clientX = touch.pageX; + } else { + clientX = (ev as MouseEvent).clientX; + } + const { left, width, leftIndex, rightIndex, schedulerData } = this.props; + const cellWidth = schedulerData.getContentCellWidth(); + const offset = leftIndex > 0 ? 5 : 6; + const minWidth = cellWidth - offset; + const maxWidth = rightIndex * cellWidth - offset; + const { startX } = this.state; + let newLeft = left + clientX - startX!; + let newWidth = width + startX! - clientX; + if (newWidth < minWidth) { + newWidth = minWidth; + newLeft = (rightIndex - 1) * cellWidth + (rightIndex - 1 > 0 ? 2 : 3); + } else if (newWidth > maxWidth) { + newWidth = maxWidth; + newLeft = 3; + } + + this.setState({ left: newLeft, width: newWidth }); + }; + + stopStartDrag = async (ev: MouseEvent | TouchEvent) => { + ev.stopPropagation(); + this.resizerHelper("start", "removeEventListener"); + document.onselectstart = null; + document.ondragstart = null; + const { + width, + left, + top, + leftIndex, + rightIndex, + schedulerData, + eventItem, + updateEventStart, + conflictOccurred, + } = this.props; + schedulerData._stopResizing(); + const { width: stateWidth } = this.state; + if (stateWidth === width) return; + + let clientX = 0; + if (this.supportTouch) { + if ((ev as TouchEvent).changedTouches.length === 0) { + this.setState({ left, top, width }); + return; + } + const touch = (ev as TouchEvent).changedTouches[0]; + clientX = touch.pageX; + } else { + clientX = (ev as MouseEvent).clientX; + } + const { cellUnit, events, config, localeDayjs } = schedulerData; + const cellWidth = schedulerData.getContentCellWidth(); + const offset = leftIndex > 0 ? 5 : 6; + const minWidth = cellWidth - offset; + const maxWidth = rightIndex * cellWidth - offset; + const { startX } = this.state; + const newWidth = width + startX! - clientX; + const deltaX = clientX - startX!; + let sign = 1; + if (deltaX < 0) { + sign = -1; + } else if (deltaX === 0) { + sign = 0; + } + let count = + (sign > 0 + ? Math.floor(Math.abs(deltaX) / cellWidth) + : Math.ceil(Math.abs(deltaX) / cellWidth)) * sign; + if (newWidth < minWidth) count = rightIndex - leftIndex - 1; + else if (newWidth > maxWidth) count = -leftIndex; + let newStart = eventItem.start.add( + cellUnit === CellUnit.Hour ? count * config.minuteStep : count, + cellUnit === CellUnit.Hour ? "minutes" : "days" + ); + + newStart = await stopDragHelper({ + count, + cellUnit, + config, + eventItem, + localeDayjs, + dragtype: "start", + value: newStart, + }); + + let hasConflict = false; + const slotId = schedulerData._getEventSlotId(eventItem); + let slotName; + const slot = schedulerData.getSlotById(slotId); + if (slot) slotName = slot.name; + if (config.checkConflict) { + const start = newStart; + const end = eventItem.end; + + events.forEach((e: any) => { + if ( + schedulerData._getEventSlotId(e) === slotId && + e.id !== eventItem.id + ) { + const eStart = e.start; + const eEnd = e.end; + if ( + ((start.isAfter(eStart) || start.isSame(eStart)) && + start.isBefore(eEnd)) || + (end.isAfter(eStart) && (end.isBefore(eEnd) || end.isSame(eEnd))) || + ((eStart.isAfter(start) || eStart.isSame(start)) && + eStart.isBefore(end)) || + (eEnd.isAfter(start) && (eEnd.isBefore(end) || eEnd.isSame(end))) + ) + hasConflict = true; + } + }); + } + + if (hasConflict) { + this.setState({ left, top, width }); + + if (conflictOccurred !== undefined) { + conflictOccurred( + schedulerData, + "StartResize", + eventItem, + DnDTypes.EVENT, + slotId, + slotName, + newStart, + eventItem.end + ); + } else { + console.log( + "Conflict occurred, set conflictOccurred func in Scheduler to handle it" + ); + } + this.subscribeResizeEvent(this.props); + } else if (updateEventStart !== undefined) + updateEventStart(schedulerData, eventItem, newStart); + }; + + cancelStartDrag = (ev: MouseEvent | TouchEvent) => { + ev.stopPropagation(); + + this.startResizer?.removeEventListener( + "touchmove", + this.doStartDrag, + false + ); + this.startResizer?.removeEventListener( + "touchend", + this.stopStartDrag, + false + ); + this.startResizer?.removeEventListener( + "touchcancel", + this.cancelStartDrag, + false + ); + document.onselectstart = null; + document.ondragstart = null; + const { schedulerData, left, top, width } = this.props; + schedulerData._stopResizing(); + this.setState({ left, top, width }); + }; + + initEndDrag = (ev: MouseEvent | TouchEvent) => { + this.initDragHelper(ev, "end"); + }; + + doEndDrag = (ev: MouseEvent | TouchEvent) => { + ev.stopPropagation(); + let clientX = 0; + if (this.supportTouch) { + if ((ev as TouchEvent).changedTouches.length === 0) return; + const touch = (ev as TouchEvent).changedTouches[0]; + clientX = touch.pageX; + } else { + clientX = (ev as MouseEvent).clientX; + } + const { width, leftIndex, schedulerData } = this.props; + const { headers } = schedulerData; + const cellWidth = schedulerData.getContentCellWidth(); + const offset = leftIndex > 0 ? 5 : 6; + const minWidth = cellWidth - offset; + const maxWidth = (headers.length - leftIndex) * cellWidth - offset; + const { endX } = this.state; + + let newWidth = width + clientX - endX!; + if (newWidth < minWidth) newWidth = minWidth; + else if (newWidth > maxWidth) newWidth = maxWidth; + + this.setState({ width: newWidth }); + }; + + stopEndDrag = async (ev: MouseEvent | TouchEvent) => { + ev.stopPropagation(); + this.resizerHelper("end", "removeEventListener"); + + document.onselectstart = null; + document.ondragstart = null; + + const { + left, + top, + width, + leftIndex, + rightIndex, + schedulerData, + eventItem, + updateEventEnd, + conflictOccurred, + } = this.props; + + schedulerData._stopResizing(); + const { width: stateWidth } = this.state; + + if (stateWidth === width) return; + + let clientX = 0; + if (this.supportTouch) { + if ((ev as TouchEvent).changedTouches.length === 0) { + this.setState({ left, top, width }); + return; + } + const touch = (ev as TouchEvent).changedTouches[0]; + clientX = touch.pageX; + } else { + clientX = (ev as MouseEvent).clientX; + } + const { headers, cellUnit, events, config, localeDayjs } = schedulerData; + + const cellWidth = schedulerData.getContentCellWidth(); + const offset = leftIndex > 0 ? 5 : 6; + const minWidth = cellWidth - offset; + const maxWidth = (headers.length - leftIndex) * cellWidth - offset; + const { endX } = this.state; + + const newWidth = width + clientX - endX!; + const deltaX = newWidth - width; + let sign = 1; + if (deltaX < 0) { + sign = -1; + } else if (deltaX === 0) { + sign = 0; + } + + let count = + (sign < 0 + ? Math.floor(Math.abs(deltaX) / cellWidth) + : Math.ceil(Math.abs(deltaX) / cellWidth)) * sign; + if (newWidth < minWidth) count = leftIndex - rightIndex + 1; + else if (newWidth > maxWidth) count = headers.length - rightIndex; + let newEnd = eventItem.end.add( + cellUnit === CellUnit.Hour ? count * config.minuteStep : count, + cellUnit === CellUnit.Hour ? "minutes" : "days" + ); + newEnd = await stopDragHelper({ + dragtype: "start", + cellUnit, + config, + count, + value: newEnd, + eventItem, + localeDayjs, + }); + + let hasConflict = false; + const slotId = schedulerData._getEventSlotId(eventItem); + const slot = schedulerData.getSlotById(slotId); + + if (config.checkConflict) { + const start = eventItem.start; + const end = newEnd; + + events.forEach((e: any) => { + if ( + schedulerData._getEventSlotId(e) === slotId && + e.id !== eventItem.id + ) { + const eStart = e.start; + const eEnd = e.end; + if ( + ((start.isAfter(eStart) || start.isSame(eStart)) && + start.isBefore(eEnd)) || + (end.isAfter(eStart) && (end.isBefore(eEnd) || end.isSame(eEnd))) || + ((eStart.isAfter(start) || eStart.isSame(start)) && + eStart.isBefore(end)) || + (eEnd.isAfter(start) && (eEnd.isBefore(end) || eEnd.isSame(end))) + ) { + hasConflict = true; + } + } + }); + } + + if (hasConflict) { + this.setState({ left, top, width }); + + if (conflictOccurred !== undefined) { + conflictOccurred( + schedulerData, + "EndResize", + eventItem, + DnDTypes.EVENT, + slotId, + slot ? slot.name : null, + eventItem.start, + newEnd + ); + } else { + console.log( + "Conflict occurred, set conflictOccurred func in Scheduler to handle it" + ); + } + this.subscribeResizeEvent(this.props); + } else if (updateEventEnd !== undefined) { + updateEventEnd(schedulerData, eventItem, newEnd); + } + }; + + cancelEndDrag = (ev: MouseEvent | TouchEvent) => { + ev.stopPropagation(); + + this.endResizer?.removeEventListener("touchmove", this.doEndDrag, false); + this.endResizer?.removeEventListener("touchend", this.stopEndDrag, false); + this.endResizer?.removeEventListener( + "touchcancel", + this.cancelEndDrag, + false + ); + document.onselectstart = null; + document.ondragstart = null; + const { schedulerData, left, top, width } = this.props; + schedulerData._stopResizing(); + this.setState({ left, top, width }); + }; + + handleMouseMove = (event: MouseEvent) => { + const rect = this.eventItemRef.current!.getBoundingClientRect(); + this.setState({ + contentMousePosX: event.clientX, + eventItemLeftRect: rect.left, + eventItemRightRect: rect.right, + }); + }; + subscribeResizeEvent = (props: EventItemProps) => { + if (this.startResizer !== undefined && this.startResizer !== null) { + if (this.supportTouch) { + // this.startResizer.removeEventListener('touchstart', this.initStartDrag, false); + // if (startResizable(props)) + // this.startResizer.addEventListener('touchstart', this.initStartDrag, false); + } else { + this.startResizer.removeEventListener( + "mousedown", + this.initStartDrag, + false + ); + if (startResizable(props)) + this.startResizer.addEventListener( + "mousedown", + this.initStartDrag, + false + ); + } + } + if (this.endResizer !== undefined && this.endResizer !== null) { + if (this.supportTouch) { + // this.endResizer.removeEventListener('touchstart', this.initEndDrag, false); + // if (endResizable(props)) + // this.endResizer.addEventListener('touchstart', this.initEndDrag, false); + } else { + this.endResizer.removeEventListener( + "mousedown", + this.initEndDrag, + false + ); + if (endResizable(props)) + this.endResizer.addEventListener( + "mousedown", + this.initEndDrag, + false + ); + } + } + }; + + render() { + const { + eventItem, + isStart, + isEnd, + isInPopover, + eventItemClick, + schedulerData, + isDragging, + connectDragSource, + connectDragPreview, + eventItemTemplateResolver, + } = this.props; + const { config, localeDayjs } = schedulerData; + const { left, width, top } = this.state; + let roundCls; + const popoverPlacement = config.eventItemPopoverPlacement; + const isPopoverPlacementMousePosition = + /(top|bottom)(Right|Left)MousePosition/.test(popoverPlacement); + + if (isStart) { + roundCls = isEnd ? "round-all" : "round-head"; + } else { + roundCls = isEnd ? "round-tail" : "round-none"; + } + let bgColor = config.defaultEventBgColor; + + if (eventItem.bgColor) bgColor = eventItem.bgColor; + + const titleText = schedulerData.behaviors.getEventTextFunc( + schedulerData, + eventItem + ); + const content = ( + + ); + + const start = eventItem.start; + const eventTitle = isInPopover + ? `${start.format("HH:mm")} ${titleText}` + : titleText; + let startResizeDiv =
; + if (startResizable(this.props)) + startResizeDiv = ( +
(this.startResizer = ref)} + /> + ); + let endResizeDiv =
; + if (endResizable(this.props)) + endResizeDiv = ( +
(this.endResizer = ref)} + /> + ); + + let eventItemTemplate: React.ReactNode = ( +
+ + {eventTitle} + +
+ ); + if (eventItemTemplateResolver !== undefined) { + eventItemTemplate = eventItemTemplateResolver( + schedulerData, + eventItem, + bgColor, + isStart, + isEnd, + "event-item", + config.eventItemHeight, + undefined, + roundCls + ); + } + + const a = ( + { + if (eventItemClick) eventItemClick(schedulerData, eventItem); + }} + > + {eventItemTemplate} + {startResizeDiv} + {endResizeDiv} + + ); + + const getMousePositionOptionsData = () => { + let popoverOffsetX = 0; + let mousePositionPlacement = ""; + + if (isPopoverPlacementMousePosition) { + const isMousePositionPlacementLeft = popoverPlacement.includes("Left"); + const { contentMousePosX } = this.state; + const popoverWidth = config.eventItemPopoverWidth; + const { eventItemLeftRect } = this.state; + const { eventItemRightRect } = this.state; + let eventItemMousePosX = isMousePositionPlacementLeft + ? eventItemLeftRect + : eventItemRightRect; + let posAdjustControl = isMousePositionPlacementLeft ? 1 : -1; + + mousePositionPlacement = popoverPlacement.replace("MousePosition", ""); + + const distance = 10; + + if (isMousePositionPlacementLeft && this._isMounted) { + if (contentMousePosX + popoverWidth + distance > window.innerWidth) { + mousePositionPlacement = `${popoverPlacement.replace(/(Right|Left).*/, "")}Right`; + eventItemMousePosX = eventItemRightRect; + posAdjustControl = -1; + } + } else if (contentMousePosX - popoverWidth - distance < 0) { + mousePositionPlacement = `${popoverPlacement.replace(/(Right|Left).*/, "")}Left`; + eventItemMousePosX = eventItemLeftRect; + posAdjustControl = 1; + } + + popoverOffsetX = + contentMousePosX - eventItemMousePosX - 20 * posAdjustControl; + } + + return { popoverOffsetX, mousePositionPlacement }; + }; + + const { popoverOffsetX, mousePositionPlacement } = + getMousePositionOptionsData(); + + const aItem = config.dragAndDropEnabled + ? connectDragPreview!(connectDragSource!(a)) + : a; + + if ( + isDragging + ? null + : schedulerData._isResizing() || + config.eventItemPopoverEnabled === false || + eventItem.showPopover === false + ) { + return
{aItem}
; + } + + return ( + + {aItem} + + ); + } +} diff --git a/src/classComponents/ResourceEvents.tsx b/src/classComponents/ResourceEvents.tsx new file mode 100644 index 0000000..7010137 --- /dev/null +++ b/src/classComponents/ResourceEvents.tsx @@ -0,0 +1,552 @@ +import React, { Component, ReactNode } from "react"; +import { DragSourceMonitor, DropTargetMonitor } from "react-dnd"; +import AddMore from "../components/AddMore"; +import Summary from "../components/Summary"; +import SelectedArea from "../components/SelectedArea"; +import { + CellUnit, + DATETIME_FORMAT, + SummaryPos, + DnDTypes, +} from "../config/default"; +import { getPos } from "../helper/utility"; +import { SchedulerData } from "../components/SchedulerData"; +import { RenderDataItem } from "../types/baseType"; +import { Dayjs } from "dayjs"; +import { DnDSource } from "./DnDSource"; + +interface ResourceEventsProps { + resourceEvents: RenderDataItem; + schedulerData: SchedulerData; + dndSource: DnDSource; + onSetAddMoreState?: (state: any) => void; + updateEventStart?: ( + schedulerData: SchedulerData, + event: any, + newStart: string + ) => void; + updateEventEnd?: ( + schedulerData: SchedulerData, + event: any, + newEnd: string + ) => void; + moveEvent?: ( + schedulerData: SchedulerData, + event: any, + slotId: string, + slotName: string, + start: Dayjs, + end: Dayjs + ) => void; + movingEvent?: ( + schedulerData: SchedulerData, + slotId: string, + slotName: string, + newStart: Dayjs, + newEnd: Dayjs + ) => void; + conflictOccurred?: ( + schedulerData: SchedulerData, + action: string, + event: any, + type: string, + slotId: string, + slotName: string, + start: Dayjs, + end: Dayjs + ) => void; + subtitleGetter?: (schedulerData: SchedulerData, event: any) => ReactNode; + eventItemClick?: (schedulerData: SchedulerData, event: any) => void; + viewEventClick?: (schedulerData: SchedulerData, event: any) => void; + viewEventText?: string; + viewEvent2Click?: (schedulerData: SchedulerData, event: any) => void; + viewEvent2Text?: string; + newEvent?: ( + schedulerData: SchedulerData, + slotId: string, + slotName: string, + start: Dayjs, + end: Dayjs + ) => void; + eventItemTemplateResolver?: (schedulerData: any, event: any) => ReactNode; + connectDropTarget?: (element: ReactNode) => ReactNode; +} + +interface ResourceEventsState { + isSelecting: boolean; + left: number; + width: number; + startX?: number; + leftIndex?: number; + rightIndex?: number; +} + +export class ResourceEventsComponent extends Component< + ResourceEventsProps, + ResourceEventsState +> { + private eventContainer: HTMLDivElement | null = null; + private supportTouch: boolean = false; + + constructor(props: ResourceEventsProps) { + super(props); + + this.state = { + isSelecting: false, + left: 0, + width: 0, + }; + } + + componentDidMount() { + const { schedulerData } = this.props; + const { config } = schedulerData; + this.supportTouch = "ontouchstart" in window; + + if (config.creatable === true) { + this.supportTouchHelper(); + } + } + + componentDidUpdate(prevProps: ResourceEventsProps) { + if (prevProps !== this.props) { + this.supportTouchHelper("remove"); + if (this.props.schedulerData.config.creatable) { + this.supportTouchHelper(); + } + } + } + + supportTouchHelper = (evType: "add" | "remove" = "add") => { + const ev = + evType === "add" + ? this.eventContainer?.addEventListener + : this.eventContainer?.removeEventListener; + if (this.supportTouch) { + // ev('touchstart', this.initDrag, false); + } else { + ev?.("mousedown", this.initDrag, false); + } + }; + + initDrag = (ev: MouseEvent | TouchEvent) => { + const { isSelecting } = this.state; + if (isSelecting) return; + if ((ev.target as HTMLElement) !== this.eventContainer) return; + + ev.stopPropagation(); + + const { resourceEvents } = this.props; + if (resourceEvents.groupOnly) return; + const [clientX, toReturn] = this.dragHelper(ev, "init"); + + if (toReturn) { + return; + } + + const { schedulerData } = this.props; + const cellWidth = schedulerData.getContentCellWidth(); + const pos = getPos(this.eventContainer!); + const startX = clientX - pos.x; + const leftIndex = Math.floor(startX / cellWidth); + const left = leftIndex * cellWidth; + const rightIndex = Math.ceil(startX / cellWidth); + const width = (rightIndex - leftIndex) * cellWidth; + + this.setState({ + startX, + left, + leftIndex, + width, + rightIndex, + isSelecting: true, + }); + + if (this.supportTouch) { + document.documentElement.addEventListener( + "touchmove", + this.doDrag, + false + ); + document.documentElement.addEventListener( + "touchend", + this.stopDrag, + false + ); + document.documentElement.addEventListener( + "touchcancel", + this.cancelDrag, + false + ); + } else { + document.documentElement.addEventListener( + "mousemove", + this.doDrag, + false + ); + document.documentElement.addEventListener( + "mouseup", + this.stopDrag, + false + ); + } + document.onselectstart = () => false; + document.ondragstart = () => false; + }; + + doDrag = (ev: MouseEvent | TouchEvent) => { + ev.stopPropagation(); + + const [clientX, toReturn] = this.dragHelper(ev, "do"); + + if (toReturn) { + return; + } + const { startX } = this.state; + const { schedulerData } = this.props; + const { headers } = schedulerData; + const cellWidth = schedulerData.getContentCellWidth(); + const pos = getPos(this.eventContainer!); + const currentX = clientX - pos.x; + let leftIndex = Math.floor(Math.min(startX!, currentX) / cellWidth); + leftIndex = leftIndex < 0 ? 0 : leftIndex; + const left = leftIndex * cellWidth; + let rightIndex = Math.ceil(Math.max(startX!, currentX) / cellWidth); + rightIndex = rightIndex > headers.length ? headers.length : rightIndex; + const width = (rightIndex - leftIndex) * cellWidth; + + this.setState({ leftIndex, left, rightIndex, width, isSelecting: true }); + }; + + dragHelper = ( + ev: MouseEvent | TouchEvent, + dragType: "init" | "do" + ): [number, boolean] => { + let clientX = 0; + if (this.supportTouch) { + if ((ev as TouchEvent).changedTouches.length === 0) + return [clientX, true]; + const touch = (ev as TouchEvent).changedTouches[0]; + clientX = touch!.pageX; + } else if (dragType === "init") { + if ( + (ev as MouseEvent).buttons !== undefined && + (ev as MouseEvent).buttons !== 1 + ) + return [clientX, true]; + clientX = (ev as MouseEvent).clientX; + } else { + clientX = (ev as MouseEvent).clientX; + } + return [clientX, false]; + }; + + stopDrag = (ev: MouseEvent | TouchEvent) => { + ev.stopPropagation(); + + const { schedulerData, newEvent, resourceEvents } = this.props; + const { headers, events, config, cellUnit, localeDayjs } = schedulerData; + const { leftIndex, rightIndex } = this.state; + if (this.supportTouch) { + document.documentElement.removeEventListener( + "touchmove", + this.doDrag, + false + ); + document.documentElement.removeEventListener( + "touchend", + this.stopDrag, + false + ); + document.documentElement.removeEventListener( + "touchcancel", + this.cancelDrag, + false + ); + } else { + document.documentElement.removeEventListener( + "mousemove", + this.doDrag, + false + ); + document.documentElement.removeEventListener( + "mouseup", + this.stopDrag, + false + ); + } + document.onselectstart = null; + document.ondragstart = null; + + const startTime = localeDayjs( + headers[leftIndex!]?.time + ? // @ts-ignore + new Date(headers[leftIndex!]?.time) + : new Date() + ); + let endTime = + resourceEvents.headerItems[rightIndex! - 1]?.end || localeDayjs(); + if (cellUnit !== CellUnit.Hour) { + endTime = ( + resourceEvents.headerItems[rightIndex! - 1]?.start || localeDayjs() + ) + .hour(23) + .minute(59) + .second(59); + } + const { slotId } = resourceEvents; + const { slotName } = resourceEvents; + + this.setState({ + startX: 0, + leftIndex: 0, + left: 0, + rightIndex: 0, + width: 0, + isSelecting: false, + }); + + let hasConflict = false; + if (config.checkConflict) { + const start = startTime; + const end = endTime; + + events.forEach((e: any) => { + if (schedulerData._getEventSlotId(e) === slotId) { + const eStart = e.start; + const eEnd = e.end; + if ( + ((start.isAfter(eStart) || start.isSame(eStart)) && + start.isBefore(eEnd)) || + (end.isAfter(eStart) && (end.isBefore(eEnd) || end.isSame(eEnd))) || + ((eStart.isAfter(start) || eStart.isSame(start)) && + eStart.isBefore(end)) || + (eEnd.isAfter(start) && (eEnd.isBefore(end) || eEnd.isSame(end))) + ) + hasConflict = true; + } + }); + } + + if (hasConflict) { + const { conflictOccurred } = this.props; + if (conflictOccurred !== undefined) { + conflictOccurred( + schedulerData, + "New", + { + id: undefined, + start: startTime, + end: endTime, + slotId, + slotName, + title: undefined, + }, + DnDTypes.EVENT, + slotId, + slotName, + startTime, + endTime + ); + } else { + console.log( + "Conflict occurred, set conflictOccurred func in Scheduler to handle it" + ); + } + } else if (newEvent !== undefined) + newEvent(schedulerData, slotId, slotName, startTime, endTime); + }; + + cancelDrag = (ev: MouseEvent | TouchEvent) => { + ev.stopPropagation(); + + const { isSelecting } = this.state; + if (isSelecting) { + document.documentElement.removeEventListener( + "touchmove", + this.doDrag, + false + ); + document.documentElement.removeEventListener( + "touchend", + this.stopDrag, + false + ); + document.documentElement.removeEventListener( + "touchcancel", + this.cancelDrag, + false + ); + document.onselectstart = null; + document.ondragstart = null; + this.setState({ + startX: 0, + leftIndex: 0, + left: 0, + rightIndex: 0, + width: 0, + isSelecting: false, + }); + } + }; + + onAddMoreClick = (headerItem: any) => { + const { onSetAddMoreState, resourceEvents, schedulerData } = this.props; + if (onSetAddMoreState) { + const { config } = schedulerData; + const cellWidth = schedulerData.getContentCellWidth(); + const index = resourceEvents.headerItems.indexOf(headerItem); + if (index !== -1) { + let left = index * (cellWidth - 1); + const pos = getPos(this.eventContainer!); + left += pos.x; + const top = pos.y; + const height = (headerItem.count + 1) * config.eventItemLineHeight + 20; + + onSetAddMoreState({ + headerItem, + left, + top, + height, + }); + } + } + }; + + eventContainerRef = (element: HTMLDivElement) => { + this.eventContainer = element; + }; + + render() { + const { resourceEvents, schedulerData, connectDropTarget, dndSource } = + this.props; + const { cellUnit, startDate, endDate, config, localeDayjs } = schedulerData; + const { isSelecting, left, width } = this.state; + const cellWidth = schedulerData.getContentCellWidth(); + const cellMaxEvents = schedulerData.getCellMaxEvents(); + const rowWidth = schedulerData.getContentTableWidth(); + const DnDEventItem = dndSource.getDragSource(); + + const selectedArea = isSelecting ? ( + + ) : ( +
+ ); + + const eventList: ReactNode[] = []; + resourceEvents.headerItems.forEach((headerItem, index) => { + if (headerItem.count > 0 || headerItem.summary !== undefined) { + const isTop = + config.summaryPos === SummaryPos.TopRight || + config.summaryPos === SummaryPos.Top || + config.summaryPos === SummaryPos.TopLeft; + const marginTop = + resourceEvents.hasSummary && isTop + ? 1 + config.eventItemLineHeight + : 1 + config.marginTop; + const renderEventsMaxIndex = + headerItem.addMore === 0 ? cellMaxEvents : headerItem.addMoreIndex; + + headerItem.events.forEach((evt: any, idx: number) => { + if (idx < renderEventsMaxIndex && evt !== undefined && evt.render) { + let durationStart = startDate; + let durationEnd = localeDayjs(endDate); + if (cellUnit === CellUnit.Hour) { + durationStart = startDate.add(config.dayStartFrom, "hours"); + durationEnd = endDate.add(config.dayStopTo + 1, "hours"); + } + const eventStart = evt.eventItem.start; + const eventEnd = evt.eventItem.end; + const isStart = + eventStart.isAfter(durationStart) || + eventStart.isSame(durationStart); + const isEnd = + eventEnd.isBefore(durationEnd) || eventEnd.isSame(durationEnd); + const left = index * cellWidth + (index > 0 ? 2 : 3); + const width = + evt.span * cellWidth - (index > 0 ? 5 : 6) > 0 + ? evt.span * cellWidth - (index > 0 ? 5 : 6) + : 0; + const top = marginTop + idx * config.eventItemLineHeight; + const eventItem = ( + + ); + eventList.push(eventItem); + } + }); + + if (headerItem.addMore > 0) { + const left = index * cellWidth + (index > 0 ? 2 : 3); + const width = cellWidth - (index > 0 ? 5 : 6); + const top = + marginTop + headerItem.addMoreIndex * config.eventItemLineHeight; + const addMoreItem = ( + + ); + eventList.push(addMoreItem); + } + + if (headerItem.summary !== undefined) { + const top = isTop + ? 1 + : resourceEvents.rowHeight - config.eventItemLineHeight + 1; + const left = index * cellWidth + (index > 0 ? 2 : 3); + const width = cellWidth - (index > 0 ? 5 : 6); + const key = `${resourceEvents.slotId}_${headerItem.time}`; + const summary = ( + + ); + eventList.push(summary); + } + } + }); + + const eventContainer = ( +
+ {selectedArea} + {eventList} +
+ ); + return ( + + + {config.dragAndDropEnabled + ? connectDropTarget!(eventContainer) + : eventContainer} + + + ); + } +} diff --git a/src/components/AddMore.jsx b/src/components/AddMore.jsx deleted file mode 100644 index c6b4890..0000000 --- a/src/components/AddMore.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -function AddMore({ schedulerData, number, left, width, top, clickAction, headerItem }) { - const { config } = schedulerData; - const content = `+${number} more`; - - return ( - - ); -} - -AddMore.propTypes = { - schedulerData: PropTypes.object.isRequired, - number: PropTypes.number.isRequired, - left: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - top: PropTypes.number.isRequired, - clickAction: PropTypes.func.isRequired, - headerItem: PropTypes.object.isRequired, -}; - -export default AddMore; diff --git a/src/components/AddMore.tsx b/src/components/AddMore.tsx new file mode 100644 index 0000000..18fa723 --- /dev/null +++ b/src/components/AddMore.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import { SchedulerData } from "./SchedulerData"; +import { HeaderEventsType } from "../types/baseType"; + +function AddMore({ + schedulerData, + number, + left, + width, + top, + clickAction, + headerItem, +}: { + schedulerData: SchedulerData; + number: number; + left: number; + width: number; + top: number; + clickAction: (headerItem: HeaderEventsType) => void; + headerItem: HeaderEventsType; +}) { + const { config } = schedulerData; + const content = `+${number} more`; + + return ( + + ); +} + +export default AddMore; diff --git a/src/components/AddMorePopover.jsx b/src/components/AddMorePopover.jsx deleted file mode 100644 index 810b3a3..0000000 --- a/src/components/AddMorePopover.jsx +++ /dev/null @@ -1,80 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import { Col, Row } from 'antd'; -import { CloseOutlined } from '@ant-design/icons'; -import EventItem from './EventItem'; -import DnDSource from './DnDSource'; - -function AddMorePopover(props) { - const { schedulerData, headerItem, left, top, height, closeAction } = props; - const { config, localeDayjs } = schedulerData; - const { time, start, end, events } = headerItem; - - const [dndSource] = useState(new DnDSource(p => p.eventItem, EventItem, schedulerData.config.dragAndDropEnabled)); - - const header = localeDayjs(new Date(time)).format(config.addMorePopoverHeaderFormat); - const durationStart = localeDayjs(new Date(start)); - const durationEnd = localeDayjs(end); - const DnDEventItem = dndSource.getDragSource(); - const eventList = events.map((evt, i) => { - if (evt !== undefined) { - const eventStart = localeDayjs(evt.eventItem.start); - const eventEnd = localeDayjs(evt.eventItem.end); - const isStart = eventStart >= durationStart; - const isEnd = eventEnd < durationEnd; - const eventItemTop = 12 + (i + 1) * config.eventItemLineHeight; - - return ( - - ); - } - return null; - }); - - return ( -
- - - {header} - - - - - - {eventList?.filter(Boolean)} -
- ); -} - -AddMorePopover.propTypes = { - schedulerData: PropTypes.object.isRequired, - headerItem: PropTypes.object.isRequired, - left: PropTypes.number.isRequired, - top: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - closeAction: PropTypes.func.isRequired, - subtitleGetter: PropTypes.func, - moveEvent: PropTypes.func, - eventItemClick: PropTypes.func, - viewEventClick: PropTypes.func, - viewEventText: PropTypes.string, - viewEvent2Click: PropTypes.func, - viewEvent2Text: PropTypes.string, - eventItemTemplateResolver: PropTypes.func, -}; - -export default AddMorePopover; diff --git a/src/components/AddMorePopover.tsx b/src/components/AddMorePopover.tsx new file mode 100644 index 0000000..ce30017 --- /dev/null +++ b/src/components/AddMorePopover.tsx @@ -0,0 +1,104 @@ +import React, { useState } from "react"; +import { Col, Row } from "antd"; +import { CloseOutlined } from "@ant-design/icons"; +import { EventItemComponent } from "../classComponents/EventItem"; +import { DnDSource } from "../classComponents/DnDSource"; +import { SchedulerData } from "./SchedulerData"; +import { EventItemType, HeaderEventsType } from "../types/baseType"; +import { + CloseActionFunc, + EventItemClickFunc, + EventItemTemplateResolverFunc, + MoveEventFunc, + SubtitleGetterFunc, + ViewEvent2ClickFunc, + ViewEventClickFunc, +} from "../types/moreTypes"; + +interface AddMorePopoverProps { + schedulerData: SchedulerData; + headerItem: HeaderEventsType; + left: number; + top: number; + height: number; + closeAction: CloseActionFunc; + + subtitleGetter?: SubtitleGetterFunc; + moveEvent?: MoveEventFunc; + eventItemClick?: EventItemClickFunc; + viewEventClick?: ViewEventClickFunc; + viewEventText?: string; + viewEvent2Click?: ViewEvent2ClickFunc; + viewEvent2Text?: string; + eventItemTemplateResolver?: EventItemTemplateResolverFunc; +} + +function AddMorePopover(props: AddMorePopoverProps) { + const { schedulerData, headerItem, left, top, height, closeAction } = props; + const { config, localeDayjs } = schedulerData; + const { time, start, end, events } = headerItem; + + const [dndSource] = useState( + new DnDSource( + (dndProps) => dndProps.eventItem, + EventItemComponent, + schedulerData.config.dragAndDropEnabled + ) + ); + + const header = localeDayjs(new Date(time)).format( + config.addMorePopoverHeaderFormat + ); + const durationStart = start.clone(); + const durationEnd = localeDayjs(end); + const DnDEventItem = dndSource.getDragSource(); + const eventList = events.map((evt, i) => { + const eventItem = evt.eventItem; + + if (evt !== undefined) { + const eventStart = localeDayjs(eventItem.start); + const eventEnd = localeDayjs(eventItem.end); + const isStart = eventStart >= durationStart; + const isEnd = eventEnd < durationEnd; + const eventItemTop = 12 + (i + 1) * config.eventItemLineHeight; + + return ( + + ); + } + return null; + }); + + return ( +
+ + + {header} + + + + + + {eventList?.filter(Boolean)} +
+ ); +} + +export default AddMorePopover; diff --git a/src/components/AgendaEventItem.jsx b/src/components/AgendaEventItem.jsx deleted file mode 100644 index 2393a48..0000000 --- a/src/components/AgendaEventItem.jsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Popover } from 'antd'; -import EventItemPopover from './EventItemPopover'; - -function AgendaEventItem(props) { - const { eventItem, isStart, isEnd, eventItemClick, schedulerData, eventItemTemplateResolver } = props; - const { config, behaviors } = schedulerData; - - let roundCls = 'round-none'; - if (isStart && isEnd) { - roundCls = 'round-all'; - } else if (isStart) { - roundCls = 'round-head'; - } else if (isEnd) { - roundCls = 'round-tail'; - } - - const backgroundColor = eventItem.bgColor || config.defaultEventBgColor; - const titleText = behaviors.getEventTextFunc(schedulerData, eventItem); - - const eventItemStyle = { height: config.eventItemHeight, maxWidth: config.agendaMaxEventWidth, backgroundColor }; - - let eventItemTemplate = ( -
- {titleText} -
- ); - - if (eventItemTemplateResolver) { - eventItemTemplate = eventItemTemplateResolver(schedulerData, eventItem, backgroundColor, isStart, isEnd, 'event-item', config.eventItemHeight, config.agendaMaxEventWidth); - } - - const handleClick = () => eventItemClick?.(schedulerData, eventItem); - - const eventLink = ( - - ); - - const content = ; - - return config.eventItemPopoverEnabled ? ( - - {eventLink} - - ) : ( - {eventLink} - ); -} - -AgendaEventItem.propTypes = { - schedulerData: PropTypes.object.isRequired, - eventItem: PropTypes.object.isRequired, - isStart: PropTypes.bool.isRequired, - isEnd: PropTypes.bool.isRequired, - subtitleGetter: PropTypes.func, - eventItemClick: PropTypes.func, - viewEventClick: PropTypes.func, - viewEventText: PropTypes.string, - viewEvent2Click: PropTypes.func, - viewEvent2Text: PropTypes.string, - eventItemTemplateResolver: PropTypes.func, -}; - -export default AgendaEventItem; diff --git a/src/components/AgendaEventItem.tsx b/src/components/AgendaEventItem.tsx new file mode 100644 index 0000000..fa3106e --- /dev/null +++ b/src/components/AgendaEventItem.tsx @@ -0,0 +1,133 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Popover } from "antd"; +import EventItemPopover from "./EventItemPopover"; +import { SchedulerData } from "./SchedulerData"; +import { EventItemType } from "../types/baseType"; +import { + EventItemClickFunc, + EventItemPopoverTemplateResolverFunc, + EventItemTemplateResolverFunc, + SubtitleGetterFunc, + ViewEvent2ClickFunc, +} from "../types/moreTypes"; + +interface AgendaEventItemProps { + schedulerData: SchedulerData; + eventItem: EventItemType; + isStart: boolean; + isEnd: boolean; + subtitleGetter?: SubtitleGetterFunc; + eventItemClick?: EventItemClickFunc; + viewEventClick?: ViewEvent2ClickFunc; + viewEventText?: string; + viewEvent2Click?: ViewEvent2ClickFunc; + viewEvent2Text?: string; + eventItemTemplateResolver?: EventItemTemplateResolverFunc; + eventItemPopoverTemplateResolver?: EventItemPopoverTemplateResolverFunc; +} + +function AgendaEventItem(props: AgendaEventItemProps) { + const { + eventItem, + isStart, + isEnd, + eventItemClick, + schedulerData, + eventItemTemplateResolver, + } = props; + const { config, behaviors } = schedulerData; + + let roundCls = "round-none"; + if (isStart && isEnd) { + roundCls = "round-all"; + } else if (isStart) { + roundCls = "round-head"; + } else if (isEnd) { + roundCls = "round-tail"; + } + + const backgroundColor = eventItem.bgColor || config.defaultEventBgColor; + const titleText = behaviors.getEventTextFunc(schedulerData, eventItem); + + const eventItemStyle = { + height: config.eventItemHeight, + maxWidth: config.agendaMaxEventWidth, + backgroundColor, + }; + + let eventItemTemplate: React.ReactNode = ( +
+ + {titleText} + +
+ ); + + if (eventItemTemplateResolver) { + eventItemTemplate = eventItemTemplateResolver( + schedulerData, + eventItem, + backgroundColor, + isStart, + isEnd, + "event-item", + config.eventItemHeight, + config.agendaMaxEventWidth, + roundCls + ); + } + + const handleClick = () => eventItemClick?.(schedulerData, eventItem); + + const eventLink = ( + + ); + + const content = ( + + ); + + return config.eventItemPopoverEnabled ? ( + + {eventLink} + + ) : ( + {eventLink} + ); +} + +export default AgendaEventItem; diff --git a/src/components/AgendaResourceEvents.jsx b/src/components/AgendaResourceEvents.jsx deleted file mode 100644 index 425410a..0000000 --- a/src/components/AgendaResourceEvents.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import AgendaEventItem from './AgendaEventItem'; - -function AgendaResourceEvents(props) { - const { schedulerData, resourceEvents, slotClickedFunc, slotItemTemplateResolver } = props; - const { startDate, endDate, config, localeDayjs } = schedulerData; - const width = schedulerData.getResourceTableWidth() - 2; - - const events = resourceEvents.headerItems.flatMap(item => { - const start = localeDayjs(new Date(startDate)); - const end = localeDayjs(endDate).add(1, 'days'); - const headerStart = localeDayjs(new Date(item.start)); - const headerEnd = localeDayjs(new Date(item.end)); - if (start === headerStart && end === headerEnd) { - return item.events.map(evt => { - const durationStart = localeDayjs(new Date(startDate)); - const durationEnd = localeDayjs(endDate).add(1, 'days'); - const eventStart = localeDayjs(evt.eventItem.start); - const eventEnd = localeDayjs(evt.eventItem.end); - const isStart = eventStart >= durationStart; - const isEnd = eventEnd < durationEnd; - return ; - }); - } - return []; - }); - - const slotItemContent = slotClickedFunc ? ( - - ) : ( - {resourceEvents.slotName} - ); - - let slotItem = ( -
- {slotItemContent} -
- ); - - if (slotItemTemplateResolver) { - const temp = slotItemTemplateResolver(schedulerData, resourceEvents, slotClickedFunc, width, 'overflow-text header2-text'); - - if (temp) { - slotItem = temp; - } - } - - return ( - - {slotItem} - -
{events}
- - - ); -} - -AgendaResourceEvents.propTypes = { - schedulerData: PropTypes.object.isRequired, - resourceEvents: PropTypes.object.isRequired, - subtitleGetter: PropTypes.func, - eventItemClick: PropTypes.func, - viewEventClick: PropTypes.func, - viewEventText: PropTypes.string, - viewEvent2Click: PropTypes.func, - viewEvent2Text: PropTypes.string, - slotClickedFunc: PropTypes.func, - slotItemTemplateResolver: PropTypes.func, -}; - -export default AgendaResourceEvents; diff --git a/src/components/AgendaResourceEvents.tsx b/src/components/AgendaResourceEvents.tsx new file mode 100644 index 0000000..b38ff3d --- /dev/null +++ b/src/components/AgendaResourceEvents.tsx @@ -0,0 +1,137 @@ +import React from "react"; +import PropTypes from "prop-types"; +import AgendaEventItem from "./AgendaEventItem"; +import { + EventItemClickFunc, + EventItemTemplateResolverFunc, + SlotClickedFunc, + SlotItemTemplateResolverFunc, + SubtitleGetterFunc, + ViewEvent2ClickFunc, + ViewEventClickFunc, +} from "../types/moreTypes"; +import { SchedulerData } from "./SchedulerData"; +import { EventItemType, RenderDataItem } from "../types/baseType"; + +interface AgendaResourceEventsProps { + schedulerData: SchedulerData; + renderItem: RenderDataItem; //dont know type + subtitleGetter?: SubtitleGetterFunc; + eventItemClick?: EventItemClickFunc; + viewEventClick?: ViewEventClickFunc; + viewEventText?: string; + viewEvent2Click?: ViewEvent2ClickFunc; + viewEvent2Text?: string; + slotClickedFunc?: SlotClickedFunc; + slotItemTemplateResolver?: SlotItemTemplateResolverFunc; + eventItemTemplateResolver?: EventItemTemplateResolverFunc; +} + +function AgendaResourceEvents(props: AgendaResourceEventsProps) { + const { + schedulerData, + renderItem, + slotClickedFunc, + slotItemTemplateResolver, + } = props; + const { startDate, endDate, config, localeDayjs } = schedulerData; + const width = schedulerData.getResourceTableWidth() - 2; + + const events = (renderItem?.headerItems || []).flatMap((item) => { + const start = startDate.clone(); + const end = endDate.clone().add(1, "days"); + const headerStart = item.start.clone(); + const headerEnd = item.end.clone(); + if (start.isSame(headerStart) && end.isSame(headerEnd)) { + const eventsNodes: React.ReactNode[] = item.events.map((evt) => { + const eventItem = evt.eventItem + + const durationStart = startDate.clone(); + const durationEnd = endDate.clone().add(1, "days"); + const eventStart = localeDayjs(eventItem.start); + const eventEnd = localeDayjs(eventItem.end); + const isStart = eventStart >= durationStart; + const isEnd = eventEnd < durationEnd; + + return ( + + ); + }); + return eventsNodes + } + return []; + }); + + const slotItemContent = slotClickedFunc ? ( + + ) : ( + {renderItem.slotName} + ); + + let slotItem: React.ReactNode = ( +
+ {slotItemContent} +
+ ); + + if (slotItemTemplateResolver) { + const temp = slotItemTemplateResolver( + schedulerData, + renderItem, + slotClickedFunc, + width, + "overflow-text header2-text" + ); + + if (temp) { + slotItem = temp; + } + } + + return ( + + {slotItem} + +
{events}
+ + + ); +} + +// AgendaResourceEvents.propTypes = { +// schedulerData: PropTypes.object.isRequired, +// resourceEvents: PropTypes.object.isRequired, +// subtitleGetter: PropTypes.func, +// eventItemClick: PropTypes.func, +// viewEventClick: PropTypes.func, +// viewEventText: PropTypes.string, +// viewEvent2Click: PropTypes.func, +// viewEvent2Text: PropTypes.string, +// slotClickedFunc: PropTypes.func, +// slotItemTemplateResolver: PropTypes.func, +// }; + +export default AgendaResourceEvents; diff --git a/src/components/AgendaView.jsx b/src/components/AgendaView.jsx deleted file mode 100644 index 5cc1f4d..0000000 --- a/src/components/AgendaView.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import AgendaResourceEvents from './AgendaResourceEvents'; - -function AgendaView(props) { - const { schedulerData } = props; - const { config, renderData } = schedulerData; - - const agendaResourceTableWidth = schedulerData.getResourceTableWidth(); - const tableHeaderHeight = schedulerData.getTableHeaderHeight(); - const resourceName = schedulerData.isEventPerspective ? config.taskName : config.resourceName; - const { agendaViewHeader } = config; - - const resourceEventsList = renderData.map(item => ); - - return ( - - - - - - - - - - {resourceEventsList} -
- {resourceName} - {agendaViewHeader}
- - - ); -} - -AgendaView.propTypes = { - schedulerData: PropTypes.object.isRequired, - subtitleGetter: PropTypes.func, - eventItemClick: PropTypes.func, - viewEventClick: PropTypes.func, - viewEventText: PropTypes.string, - viewEvent2Click: PropTypes.func, - viewEvent2Text: PropTypes.string, - slotClickedFunc: PropTypes.func, -}; - -export default AgendaView; diff --git a/src/components/AgendaView.tsx b/src/components/AgendaView.tsx new file mode 100644 index 0000000..293346c --- /dev/null +++ b/src/components/AgendaView.tsx @@ -0,0 +1,72 @@ +import React from "react"; +import PropTypes from "prop-types"; +import AgendaResourceEvents from "./AgendaResourceEvents"; +import { + EventItemClickFunc, + SlotClickedFunc, + SubtitleGetterFunc, + ViewEvent2ClickFunc, + ViewEventClickFunc, +} from "../types/moreTypes"; +import { SchedulerData } from "./SchedulerData"; + +interface AgendaViewProps { + schedulerData: SchedulerData; + subtitleGetter?: SubtitleGetterFunc; + eventItemClick?: EventItemClickFunc; + viewEventClick?: ViewEventClickFunc; + viewEventText?: string; + viewEvent2Click?: ViewEvent2ClickFunc; + viewEvent2Text?: string; + slotClickedFunc?: SlotClickedFunc; +} + +function AgendaView(props: AgendaViewProps) { + const { schedulerData } = props; + const { config, renderData } = schedulerData; + + const agendaResourceTableWidth = schedulerData.getResourceTableWidth(); + const tableHeaderHeight = schedulerData.getTableHeaderHeight(); + const resourceName = schedulerData.isEventPerspective + ? config.taskName + : config.resourceName; + const { agendaViewHeader } = config; + + const resourceEventsList = renderData.map((item) => ( + + )); + + return ( + + + + + + + + + + {resourceEventsList} +
+ {resourceName} + {agendaViewHeader}
+ + + ); +} + +// AgendaView.propTypes = { +// schedulerData: PropTypes.object.isRequired, +// subtitleGetter: PropTypes.func, +// eventItemClick: PropTypes.func, +// viewEventClick: PropTypes.func, +// viewEventText: PropTypes.string, +// viewEvent2Click: PropTypes.func, +// viewEvent2Text: PropTypes.string, +// slotClickedFunc: PropTypes.func, +// }; + +export default AgendaView; diff --git a/src/components/BodyView.jsx b/src/components/BodyView.tsx similarity index 71% rename from src/components/BodyView.jsx rename to src/components/BodyView.tsx index ba27fbe..6ce558c 100644 --- a/src/components/BodyView.jsx +++ b/src/components/BodyView.tsx @@ -1,16 +1,21 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import React, {CSSProperties} from "react"; +import PropTypes from "prop-types"; +import { SchedulerData } from "./SchedulerData"; -function BodyView({ schedulerData }) { +interface BodyViewProps { + schedulerData: SchedulerData; +} + +function BodyView({ schedulerData }: BodyViewProps) { const { renderData, headers, config, behaviors } = schedulerData; const width = schedulerData.getContentCellWidth(); const tableRows = renderData - .filter(o => o.render) + .filter((o) => o.render) .map(({ slotId, groupOnly, rowHeight }) => { const rowCells = headers.map((header, index) => { const key = `${slotId}_${header.time}`; - const style = index === headers.length - 1 ? {} : { width }; + const style: CSSProperties = index === headers.length - 1 ? {} : { width }; if (header.nonWorkingTime) { style.backgroundColor = config.nonWorkingTimeBodyBgColor; } @@ -18,7 +23,11 @@ function BodyView({ schedulerData }) { style.backgroundColor = config.groupOnlySlotColor; } if (behaviors.getNonAgendaViewBodyCellBgColorFunc) { - const cellBgColor = behaviors.getNonAgendaViewBodyCellBgColorFunc(schedulerData, slotId, header); + const cellBgColor = behaviors.getNonAgendaViewBodyCellBgColorFunc( + schedulerData, + slotId, + header + ); if (cellBgColor) { style.backgroundColor = cellBgColor; } diff --git a/src/components/DnDContext.js b/src/components/DnDContext.js deleted file mode 100644 index bc4e758..0000000 --- a/src/components/DnDContext.js +++ /dev/null @@ -1,138 +0,0 @@ -import { DropTarget } from 'react-dnd'; -import { DnDTypes, CellUnit, DATETIME_FORMAT, ViewType } from '../config/default'; -import { getPos } from '../helper/utility'; - -export default class DnDContext { - constructor(sources, DecoratedComponent) { - this.sourceMap = new Map(); - sources.forEach(item => { - this.sourceMap.set(item.dndType, item); - }); - this.DecoratedComponent = DecoratedComponent; - } - - extractInitialTimes = (monitor, pos, cellWidth, resourceEvents, cellUnit, localeDayjs) => { - const initialPoint = monitor.getInitialClientOffset(); - const initialLeftIndex = Math.floor((initialPoint.x - pos.x) / cellWidth); - const initialStart = resourceEvents.headerItems[initialLeftIndex].start; - let initialEnd = resourceEvents.headerItems[initialLeftIndex].end; - if (cellUnit !== CellUnit.Hour) { - const end = localeDayjs(new Date(initialStart)).hour(23).minute(59).second(59); - initialEnd = end.format(DATETIME_FORMAT); - } - return { initialStart, initialEnd }; - }; - - getDropSpec = () => ({ - drop: (props, monitor, component) => { - const { schedulerData, resourceEvents } = props; - const { cellUnit, localeDayjs } = schedulerData; - const type = monitor.getItemType(); - const pos = getPos(component.eventContainer); - const cellWidth = schedulerData.getContentCellWidth(); - let initialStartTime = null; - let initialEndTime = null; - if (type === DnDTypes.EVENT) { - const { initialStart, initialEnd } = this.extractInitialTimes(monitor, pos, cellWidth, resourceEvents, cellUnit, localeDayjs); - initialStartTime = initialStart; - initialEndTime = initialEnd; - } - const point = monitor.getClientOffset(); - const leftIndex = Math.floor((point.x - pos.x) / cellWidth); - const startTime = resourceEvents.headerItems[leftIndex].start; - let endTime = resourceEvents.headerItems[leftIndex].end; - if (cellUnit !== CellUnit.Hour) { - endTime = localeDayjs(new Date(resourceEvents.headerItems[leftIndex].start)).hour(23).minute(59).second(59) - .format(DATETIME_FORMAT); - } - - return { - slotId: resourceEvents.slotId, - slotName: resourceEvents.slotName, - start: startTime, - end: endTime, - initialStart: initialStartTime, - initialEnd: initialEndTime, - }; - }, - - hover: (props, monitor, component) => { - const { schedulerData, resourceEvents, movingEvent } = props; - const { cellUnit, config, viewType, localeDayjs } = schedulerData; - this.config = config; - const item = monitor.getItem(); - const type = monitor.getItemType(); - const pos = getPos(component.eventContainer); - const cellWidth = schedulerData.getContentCellWidth(); - let initialStart = null; - // let initialEnd = null; - if (type === DnDTypes.EVENT) { - // const { initialStart: iStart, initialEnd: iEnd } = this.extractInitialTimes(monitor, pos, cellWidth, resourceEvents, cellUnit, localeDayjs); - const { initialStart: iStart } = this.extractInitialTimes(monitor, pos, cellWidth, resourceEvents, cellUnit, localeDayjs); - initialStart = iStart; - // initialEnd = iEnd; - } - - const point = monitor.getClientOffset(); - const leftIndex = Math.floor((point.x - pos.x) / cellWidth); - if (!resourceEvents.headerItems[leftIndex]) { - return; - } - let newStart = resourceEvents.headerItems[leftIndex].start; - let newEnd = resourceEvents.headerItems[leftIndex].end; - if (cellUnit !== CellUnit.Hour) { - newEnd = localeDayjs(new Date(resourceEvents.headerItems[leftIndex].start)).hour(23).minute(59).second(59) - .format(DATETIME_FORMAT); - } - let { slotId, slotName } = resourceEvents; - let action = 'New'; - const isEvent = type === DnDTypes.EVENT; - if (isEvent) { - const event = item; - if (config.relativeMove) { - newStart = localeDayjs(event.start) - .add(localeDayjs(newStart).diff(localeDayjs(new Date(initialStart))), 'ms') - .format(DATETIME_FORMAT); - } else if (viewType !== ViewType.Day) { - const tmpDayjs = localeDayjs(newStart); - newStart = localeDayjs(event.start).year(tmpDayjs.year()).month(tmpDayjs.month()).date(tmpDayjs.date()) - .format(DATETIME_FORMAT); - } - newEnd = localeDayjs(newStart) - .add(localeDayjs(event.end).diff(localeDayjs(event.start)), 'ms') - .format(DATETIME_FORMAT); - - // if crossResourceMove disabled, slot returns old value - if (config.crossResourceMove === false) { - slotId = schedulerData._getEventSlotId(item); - slotName = undefined; - const slot = schedulerData.getSlotById(slotId); - if (slot) slotName = slot.name; - } - - action = 'Move'; - } - - if (movingEvent) { - movingEvent(schedulerData, slotId, slotName, newStart, newEnd, action, type, item); - } - }, - - canDrop: (props, monitor) => { - const { schedulerData, resourceEvents } = props; - const item = monitor.getItem(); - if (schedulerData._isResizing()) return false; - const { config } = schedulerData; - return config.movable && !resourceEvents.groupOnly && (item.movable === undefined || item.movable !== false); - }, - }); - - getDropCollect = (connect, monitor) => ({ - connectDropTarget: connect.dropTarget(), - isOver: monitor.isOver(), - }); - - getDropTarget = dragAndDropEnabled => (dragAndDropEnabled ? DropTarget([...this.sourceMap.keys()], this.getDropSpec(), this.getDropCollect)(this.DecoratedComponent) : this.DecoratedComponent); - - getDndSource = (dndType = DnDTypes.EVENT) => this.sourceMap.get(dndType); -} diff --git a/src/components/DnDSource.js b/src/components/DnDSource.js deleted file mode 100644 index abf9d8c..0000000 --- a/src/components/DnDSource.js +++ /dev/null @@ -1,103 +0,0 @@ -import { DragSource } from 'react-dnd'; -import { DnDTypes, ViewType, DATETIME_FORMAT } from '../config/default'; - -export default class DnDSource { - constructor(resolveDragObjFunc, DecoratedComponent, DnDEnabled, dndType = DnDTypes.EVENT) { - this.resolveDragObjFunc = resolveDragObjFunc; - this.DecoratedComponent = DecoratedComponent; - this.dndType = dndType; - this.dragSource = DnDEnabled ? DragSource(this.dndType, this.getDragSpec(), this.getDragCollect)(this.DecoratedComponent) : this.DecoratedComponent; - } - - getDragSpec = () => ({ - // beginDrag: (props, monitor, component) => this.resolveDragObjFunc(props), - beginDrag: props => this.resolveDragObjFunc(props), - // endDrag: (props, monitor, component) => { - endDrag: (props, monitor) => { - if (!monitor.didDrop()) return; - - const { moveEvent, newEvent, schedulerData } = props; - const { events, config, viewType, localeDayjs } = schedulerData; - const item = monitor.getItem(); - const type = monitor.getItemType(); - const dropResult = monitor.getDropResult(); - let { slotId } = dropResult; - let { slotName } = dropResult; - let newStart = dropResult.start; - let newEnd = dropResult.end; - const { initialStart } = dropResult; - // const { initialEnd } = dropResult; - let action = 'New'; - - const isEvent = type === DnDTypes.EVENT; - if (isEvent) { - const event = item; - if (config.relativeMove) { - newStart = localeDayjs(event.start) - .add(localeDayjs(newStart).diff(localeDayjs(new Date(initialStart))), 'ms') - .format(DATETIME_FORMAT); - } else if (viewType !== ViewType.Day) { - const tmpDayjs = localeDayjs(newStart); - newStart = localeDayjs(event.start).year(tmpDayjs.year()).month(tmpDayjs.month()).date(tmpDayjs.date()) - .format(DATETIME_FORMAT); - } - newEnd = localeDayjs(newStart) - .add(localeDayjs(event.end).diff(localeDayjs(event.start)), 'ms') - .format(DATETIME_FORMAT); - - // if crossResourceMove disabled, slot returns old value - if (config.crossResourceMove === false) { - slotId = schedulerData._getEventSlotId(item); - slotName = undefined; - const slot = schedulerData.getSlotById(slotId); - if (slot) slotName = slot.name; - } - - action = 'Move'; - } - - let hasConflict = false; - if (config.checkConflict) { - const start = localeDayjs(newStart); - const end = localeDayjs(newEnd); - - events.forEach(e => { - if (schedulerData._getEventSlotId(e) === slotId && (!isEvent || e.id !== item.id)) { - const eStart = localeDayjs(e.start); - const eEnd = localeDayjs(e.end); - if ((start >= eStart && start < eEnd) || (end > eStart && end <= eEnd) || (eStart >= start && eStart < end) || (eEnd > start && eEnd <= end)) hasConflict = true; - } - }); - } - - if (hasConflict) { - const { conflictOccurred } = props; - if (conflictOccurred !== undefined) { - conflictOccurred(schedulerData, action, item, type, slotId, slotName, newStart, newEnd); - } else { - console.log('Conflict occurred, set conflictOccurred func in Scheduler to handle it'); - } - } else if (isEvent) { - if (moveEvent !== undefined) { - moveEvent(schedulerData, item, slotId, slotName, newStart, newEnd); - } - } else if (newEvent !== undefined) newEvent(schedulerData, slotId, slotName, newStart, newEnd, type, item); - }, - - canDrag: props => { - const { schedulerData, resourceEvents } = props; - const item = this.resolveDragObjFunc(props); - if (schedulerData._isResizing()) return false; - const { config } = schedulerData; - return config.movable && (resourceEvents === undefined || !resourceEvents.groupOnly) && (item.movable === undefined || item.movable !== false); - }, - }); - - getDragCollect = (connect, monitor) => ({ - connectDragSource: connect.dragSource(), - isDragging: monitor.isDragging(), - connectDragPreview: connect.dragPreview(), - }); - - getDragSource = () => this.dragSource; -} diff --git a/src/components/EventItem.jsx b/src/components/EventItem.jsx deleted file mode 100644 index 71834bc..0000000 --- a/src/components/EventItem.jsx +++ /dev/null @@ -1,560 +0,0 @@ -/* eslint-disable no-return-assign */ -import React, { Component } from 'react'; -import { PropTypes } from 'prop-types'; -import { Popover } from 'antd'; -import EventItemPopover from './EventItemPopover'; -import { DnDTypes, CellUnit, DATETIME_FORMAT } from '../config/default'; - -const stopDragHelper = ({ count, cellUnit, config, dragtype, eventItem, localeDayjs, value }) => { - const whileTrue = true; - let tCount = 0; - let i = 0; - let result = value; - return new Promise(resolve => { - if (count !== 0 && cellUnit !== CellUnit.Hour && config.displayWeekend === false) { - while (whileTrue) { - i = count > 0 ? i + 1 : i - 1; - const date = localeDayjs(new Date(eventItem[dragtype])).add(i, 'days'); - const dayOfWeek = date.weekday(); - - if (dayOfWeek !== 0 && dayOfWeek !== 6) { - tCount = count > 0 ? tCount + 1 : tCount - 1; - if (tCount === count) { - result = date.format(DATETIME_FORMAT); - break; - } - } - } - } - resolve(result); - }); -}; - -const startResizable = ({ eventItem, isInPopover, schedulerData }) => schedulerData.config.startResizable === true - && isInPopover === false - && (eventItem.resizable === undefined || eventItem.resizable !== false) - && (eventItem.startResizable === undefined || eventItem.startResizable !== false); - -const endResizable = ({ eventItem, isInPopover, schedulerData }) => schedulerData.config.endResizable === true - && isInPopover === false - && (eventItem.resizable === undefined || eventItem.resizable !== false) - && (eventItem.endResizable === undefined || eventItem.endResizable !== false); - -class EventItem extends Component { - constructor(props) { - super(props); - - const { left, top, width } = props; - this.state = { left, top, width, contentMousePosX: 0, eventItemLeftRect: 0, eventItemRightRect: 0 }; - this.startResizer = undefined; - this.endResizer = undefined; - - this.supportTouch = false; // 'ontouchstart' in window; - - this.eventItemRef = React.createRef(); - this._isMounted = false; - } - - componentDidMount() { - this._isMounted = true; - this.supportTouch = 'ontouchstart' in window; - this.subscribeResizeEvent(this.props); - } - - componentDidUpdate(prevProps) { - if (prevProps !== this.props) { - const { left, top, width } = this.props; - this.setState({ left, top, width }); - - this.subscribeResizeEvent(this.props); - } - } - - resizerHelper = (dragtype, eventType = 'addEventListener') => { - const resizer = dragtype === 'start' ? this.startResizer : this.endResizer; - const doDrag = dragtype === 'start' ? this.doStartDrag : this.doEndDrag; - const stopDrag = dragtype === 'start' ? this.stopStartDrag : this.stopEndDrag; - const cancelDrag = dragtype === 'start' ? this.cancelStartDrag : this.cancelEndDrag; - if (this.supportTouch) { - resizer[eventType]('touchmove', doDrag, false); - resizer[eventType]('touchend', stopDrag, false); - resizer[eventType]('touchcancel', cancelDrag, false); - } else { - document.documentElement[eventType]('mousemove', doDrag, false); - document.documentElement[eventType]('mouseup', stopDrag, false); - } - }; - - initDragHelper = (ev, dragtype) => { - const { schedulerData, eventItem } = this.props; - const slotId = schedulerData._getEventSlotId(eventItem); - const slot = schedulerData.getSlotById(slotId); - if (!!slot && !!slot.groupOnly) return; - if (schedulerData._isResizing()) return; - - ev.stopPropagation(); - let clientX = 0; - if (this.supportTouch) { - if (ev.changedTouches.length === 0) return; - const touch = ev.changedTouches[0]; - clientX = touch.pageX; - } else { - if (ev.buttons !== undefined && ev.buttons !== 1) return; - clientX = ev.clientX; - } - this.setState({ [dragtype === 'start' ? 'startX' : 'endX']: clientX }); - - schedulerData._startResizing(); - this.resizerHelper(dragtype, 'addEventListener'); - document.onselectstart = () => false; - document.ondragstart = () => false; - }; - - initStartDrag = ev => { - this.initDragHelper(ev, 'start'); - }; - - doStartDrag = ev => { - ev.stopPropagation(); - - let clientX = 0; - if (this.supportTouch) { - if (ev.changedTouches.length === 0) return; - const touch = ev.changedTouches[0]; - clientX = touch.pageX; - } else { - clientX = ev.clientX; - } - const { left, width, leftIndex, rightIndex, schedulerData } = this.props; - const cellWidth = schedulerData.getContentCellWidth(); - const offset = leftIndex > 0 ? 5 : 6; - const minWidth = cellWidth - offset; - const maxWidth = rightIndex * cellWidth - offset; - const { startX } = this.state; - let newLeft = left + clientX - startX; - let newWidth = width + startX - clientX; - if (newWidth < minWidth) { - newWidth = minWidth; - newLeft = (rightIndex - 1) * cellWidth + (rightIndex - 1 > 0 ? 2 : 3); - } else if (newWidth > maxWidth) { - newWidth = maxWidth; - newLeft = 3; - } - - this.setState({ left: newLeft, width: newWidth }); - }; - - stopStartDrag = async ev => { - ev.stopPropagation(); - this.resizerHelper('start', 'removeEventListener'); - document.onselectstart = null; - document.ondragstart = null; - const { width, left, top, leftIndex, rightIndex, schedulerData, eventItem, updateEventStart, conflictOccurred } = this.props; - schedulerData._stopResizing(); - const { width: stateWidth } = this.state; - if (stateWidth === width) return; - - let clientX = 0; - if (this.supportTouch) { - if (ev.changedTouches.length === 0) { - this.setState({ left, top, width }); - return; - } - const touch = ev.changedTouches[0]; - clientX = touch.pageX; - } else { - clientX = ev.clientX; - } - const { cellUnit, events, config, localeDayjs } = schedulerData; - const cellWidth = schedulerData.getContentCellWidth(); - const offset = leftIndex > 0 ? 5 : 6; - const minWidth = cellWidth - offset; - const maxWidth = rightIndex * cellWidth - offset; - const { startX } = this.state; - const newWidth = width + startX - clientX; - const deltaX = clientX - startX; - let sign = 1; - if (deltaX < 0) { - sign = -1; - } else if (deltaX === 0) { - sign = 0; - } - let count = (sign > 0 ? Math.floor(Math.abs(deltaX) / cellWidth) : Math.ceil(Math.abs(deltaX) / cellWidth)) * sign; - if (newWidth < minWidth) count = rightIndex - leftIndex - 1; - else if (newWidth > maxWidth) count = -leftIndex; - let newStart = localeDayjs(new Date(eventItem.start)) - .add(cellUnit === CellUnit.Hour ? count * config.minuteStep : count, cellUnit === CellUnit.Hour ? 'minutes' : 'days') - .format(DATETIME_FORMAT); - - newStart = await stopDragHelper({ - count, - cellUnit, - config, - eventItem, - localeDayjs, - dragtype: 'start', - value: newStart, - }); - - let hasConflict = false; - const slotId = schedulerData._getEventSlotId(eventItem); - let slotName; - const slot = schedulerData.getSlotById(slotId); - if (slot) slotName = slot.name; - if (config.checkConflict) { - const start = localeDayjs(new Date(newStart)); - const end = localeDayjs(new Date(eventItem.end)); - - events.forEach(e => { - if (schedulerData._getEventSlotId(e) === slotId && e.id !== eventItem.id) { - const eStart = localeDayjs(new Date(e.start)); - const eEnd = localeDayjs(new Date(e.end)); - if ((start >= eStart && start < eEnd) || (end > eStart && end <= eEnd) || (eStart >= start && eStart < end) || (eEnd > start && eEnd <= end)) hasConflict = true; - } - }); - } - - if (hasConflict) { - this.setState({ left, top, width }); - - if (conflictOccurred !== undefined) { - conflictOccurred(schedulerData, 'StartResize', eventItem, DnDTypes.EVENT, slotId, slotName, newStart, eventItem.end); - } else { - console.log('Conflict occurred, set conflictOccurred func in Scheduler to handle it'); - } - this.subscribeResizeEvent(this.props); - } else if (updateEventStart !== undefined) updateEventStart(schedulerData, eventItem, newStart); - }; - - cancelStartDrag = ev => { - ev.stopPropagation(); - - this.startResizer.removeEventListener('touchmove', this.doStartDrag, false); - this.startResizer.removeEventListener('touchend', this.stopStartDrag, false); - this.startResizer.removeEventListener('touchcancel', this.cancelStartDrag, false); - document.onselectstart = null; - document.ondragstart = null; - const { schedulerData, left, top, width } = this.props; - schedulerData._stopResizing(); - this.setState({ left, top, width }); - }; - - initEndDrag = ev => { - this.initDragHelper(ev, 'end'); - }; - - doEndDrag = ev => { - ev.stopPropagation(); - let clientX = 0; - if (this.supportTouch) { - if (ev.changedTouches.length === 0) return; - const touch = ev.changedTouches[0]; - clientX = touch.pageX; - } else { - clientX = ev.clientX; - } - const { width, leftIndex, schedulerData } = this.props; - const { headers } = schedulerData; - const cellWidth = schedulerData.getContentCellWidth(); - const offset = leftIndex > 0 ? 5 : 6; - const minWidth = cellWidth - offset; - const maxWidth = (headers.length - leftIndex) * cellWidth - offset; - const { endX } = this.state; - - let newWidth = width + clientX - endX; - if (newWidth < minWidth) newWidth = minWidth; - else if (newWidth > maxWidth) newWidth = maxWidth; - - this.setState({ width: newWidth }); - }; - - stopEndDrag = async ev => { - ev.stopPropagation(); - this.resizerHelper('end', 'removeEventListener'); - - document.onselectstart = null; - document.ondragstart = null; - - const { left, top, width, leftIndex, rightIndex, schedulerData, eventItem, updateEventEnd, conflictOccurred } = this.props; - - schedulerData._stopResizing(); - const { width: stateWidth } = this.state; - - if (stateWidth === width) return; - - let clientX = 0; - if (this.supportTouch) { - if (ev.changedTouches.length === 0) { - this.setState({ left, top, width }); - return; - } - const touch = ev.changedTouches[0]; - clientX = touch.pageX; - } else { - clientX = ev.clientX; - } - const { headers, cellUnit, events, config, localeDayjs } = schedulerData; - - const cellWidth = schedulerData.getContentCellWidth(); - const offset = leftIndex > 0 ? 5 : 6; - const minWidth = cellWidth - offset; - const maxWidth = (headers.length - leftIndex) * cellWidth - offset; - const { endX } = this.state; - - const newWidth = width + clientX - endX; - const deltaX = newWidth - width; - let sign = 1; - if (deltaX < 0) { - sign = -1; - } else if (deltaX === 0) { - sign = 0; - } - - let count = (sign < 0 ? Math.floor(Math.abs(deltaX) / cellWidth) : Math.ceil(Math.abs(deltaX) / cellWidth)) * sign; - if (newWidth < minWidth) count = leftIndex - rightIndex + 1; - else if (newWidth > maxWidth) count = headers.length - rightIndex; - let newEnd = localeDayjs(new Date(eventItem.end)) - .add(cellUnit === CellUnit.Hour ? count * config.minuteStep : count, cellUnit === CellUnit.Hour ? 'minutes' : 'days') - .format(DATETIME_FORMAT); - newEnd = await stopDragHelper({ - dragtype: 'start', - cellUnit, - config, - count, - value: newEnd, - eventItem, - localeDayjs, - }); - - let hasConflict = false; - const slotId = schedulerData._getEventSlotId(eventItem); - const slot = schedulerData.getSlotById(slotId); - - if (config.checkConflict) { - const start = localeDayjs(new Date(eventItem.start)); - const end = localeDayjs(new Date(newEnd)); - - events.forEach(e => { - if (schedulerData._getEventSlotId(e) === slotId && e.id !== eventItem.id) { - const eStart = localeDayjs(new Date(e.start)); - const eEnd = localeDayjs(new Date(e.end)); - if ((start >= eStart && start < eEnd) || (end > eStart && end <= eEnd) || (eStart >= start && eStart < end) || (eEnd > start && eEnd <= end)) { - hasConflict = true; - } - } - }); - } - - if (hasConflict) { - this.setState({ left, top, width }); - - if (conflictOccurred !== undefined) { - conflictOccurred(schedulerData, 'EndResize', eventItem, DnDTypes.EVENT, slotId, slot ? slot.name : null, eventItem.start, newEnd); - } else { - console.log('Conflict occurred, set conflictOccurred func in Scheduler to handle it'); - } - this.subscribeResizeEvent(this.props); - } else if (updateEventEnd !== undefined) { - updateEventEnd(schedulerData, eventItem, newEnd); - } - }; - - cancelEndDrag = ev => { - ev.stopPropagation(); - - this.endResizer.removeEventListener('touchmove', this.doEndDrag, false); - this.endResizer.removeEventListener('touchend', this.stopEndDrag, false); - this.endResizer.removeEventListener('touchcancel', this.cancelEndDrag, false); - document.onselectstart = null; - document.ondragstart = null; - const { schedulerData, left, top, width } = this.props; - schedulerData._stopResizing(); - this.setState({ left, top, width }); - }; - - handleMouseMove = event => { - const rect = this.eventItemRef.current.getBoundingClientRect(); - this.setState({ - contentMousePosX: event.clientX, - eventItemLeftRect: rect.left, - eventItemRightRect: rect.right, - }); - }; - - subscribeResizeEvent = props => { - if (this.startResizer !== undefined && this.startResizer !== null) { - if (this.supportTouch) { - // this.startResizer.removeEventListener('touchstart', this.initStartDrag, false); - // if (startResizable(props)) - // this.startResizer.addEventListener('touchstart', this.initStartDrag, false); - } else { - this.startResizer.removeEventListener('mousedown', this.initStartDrag, false); - if (startResizable(props)) this.startResizer.addEventListener('mousedown', this.initStartDrag, false); - } - } - if (this.endResizer !== undefined && this.endResizer !== null) { - if (this.supportTouch) { - // this.endResizer.removeEventListener('touchstart', this.initEndDrag, false); - // if (endResizable(props)) - // this.endResizer.addEventListener('touchstart', this.initEndDrag, false); - } else { - this.endResizer.removeEventListener('mousedown', this.initEndDrag, false); - if (endResizable(props)) this.endResizer.addEventListener('mousedown', this.initEndDrag, false); - } - } - }; - - render() { - const { eventItem, isStart, isEnd, isInPopover, eventItemClick, schedulerData, isDragging, connectDragSource, connectDragPreview, eventItemTemplateResolver } = this.props; - const { config, localeDayjs } = schedulerData; - const { left, width, top } = this.state; - let roundCls; - const popoverPlacement = config.eventItemPopoverPlacement; - const isPopoverPlacementMousePosition = /(top|bottom)(Right|Left)MousePosition/.test(popoverPlacement); - - if (isStart) { - roundCls = isEnd ? 'round-all' : 'round-head'; - } else { - roundCls = isEnd ? 'round-tail' : 'round-none'; - } - let bgColor = config.defaultEventBgColor; - - if (eventItem.bgColor) bgColor = eventItem.bgColor; - - const titleText = schedulerData.behaviors.getEventTextFunc(schedulerData, eventItem); - const content = ; - - const start = localeDayjs(new Date(eventItem.start)); - const eventTitle = isInPopover ? `${start.format('HH:mm')} ${titleText}` : titleText; - let startResizeDiv =
; - if (startResizable(this.props)) startResizeDiv =
(this.startResizer = ref)} />; - let endResizeDiv =
; - if (endResizable(this.props)) endResizeDiv =
(this.endResizer = ref)} />; - - let eventItemTemplate = ( -
- {eventTitle} -
- ); - if (eventItemTemplateResolver !== undefined) { - eventItemTemplate = eventItemTemplateResolver(schedulerData, eventItem, bgColor, isStart, isEnd, 'event-item', config.eventItemHeight, undefined); - } - - const a = ( - { - if (eventItemClick) eventItemClick(schedulerData, eventItem); - }} - > - {eventItemTemplate} - {startResizeDiv} - {endResizeDiv} - - ); - - const getMousePositionOptionsData = () => { - let popoverOffsetX = 0; - let mousePositionPlacement = ''; - - if (isPopoverPlacementMousePosition) { - const isMousePositionPlacementLeft = popoverPlacement.includes('Left'); - const { contentMousePosX } = this.state; - const popoverWidth = config.eventItemPopoverWidth; - const { eventItemLeftRect } = this.state; - const { eventItemRightRect } = this.state; - let eventItemMousePosX = isMousePositionPlacementLeft ? eventItemLeftRect : eventItemRightRect; - let posAdjustControl = isMousePositionPlacementLeft ? 1 : -1; - - mousePositionPlacement = popoverPlacement.replace('MousePosition', ''); - - const distance = 10; - - if (isMousePositionPlacementLeft && this._isMounted) { - if (contentMousePosX + popoverWidth + distance > window.innerWidth) { - mousePositionPlacement = `${popoverPlacement.replace(/(Right|Left).*/, '')}Right`; - eventItemMousePosX = eventItemRightRect; - posAdjustControl = -1; - } - } else if (contentMousePosX - popoverWidth - distance < 0) { - mousePositionPlacement = `${popoverPlacement.replace(/(Right|Left).*/, '')}Left`; - eventItemMousePosX = eventItemLeftRect; - posAdjustControl = 1; - } - - popoverOffsetX = contentMousePosX - eventItemMousePosX - 20 * posAdjustControl; - } - - return { popoverOffsetX, mousePositionPlacement }; - }; - - const { popoverOffsetX, mousePositionPlacement } = getMousePositionOptionsData(); - - const aItem = config.dragAndDropEnabled ? connectDragPreview(connectDragSource(a)) : a; - - if (isDragging ? null : schedulerData._isResizing() || config.eventItemPopoverEnabled === false || eventItem.showPopover === false) { - return
{aItem}
; - } - - return ( - - {aItem} - - ); - } -} - -export default EventItem; -EventItem.propTypes = { - schedulerData: PropTypes.object.isRequired, - eventItem: PropTypes.object.isRequired, - isStart: PropTypes.bool.isRequired, - isEnd: PropTypes.bool.isRequired, - left: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - top: PropTypes.number.isRequired, - isInPopover: PropTypes.bool.isRequired, - leftIndex: PropTypes.number.isRequired, - rightIndex: PropTypes.number.isRequired, - isDragging: PropTypes.bool, - connectDragSource: PropTypes.func, - connectDragPreview: PropTypes.func, - updateEventStart: PropTypes.func, - updateEventEnd: PropTypes.func, - moveEvent: PropTypes.func, - subtitleGetter: PropTypes.func, - eventItemClick: PropTypes.func, - viewEventClick: PropTypes.func, - viewEventText: PropTypes.string, - viewEvent2Click: PropTypes.func, - viewEvent2Text: PropTypes.string, - conflictOccurred: PropTypes.func, - eventItemTemplateResolver: PropTypes.func, -}; - -EventItem.defaultProps = { - isDragging: undefined, - connectDragSource: undefined, - connectDragPreview: undefined, - updateEventStart: undefined, - updateEventEnd: undefined, - moveEvent: undefined, - subtitleGetter: undefined, - eventItemClick: undefined, - viewEventClick: undefined, - viewEventText: undefined, - viewEvent2Click: undefined, - viewEvent2Text: undefined, - conflictOccurred: undefined, - eventItemTemplateResolver: undefined, -}; diff --git a/src/components/EventItemPopover.jsx b/src/components/EventItemPopover.jsx deleted file mode 100644 index 67fd3fb..0000000 --- a/src/components/EventItemPopover.jsx +++ /dev/null @@ -1,122 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Col, Row } from 'antd'; - -function EventItemPopover({ - schedulerData, - eventItem, - title, - startTime, - endTime, - statusColor, - subtitleGetter, - viewEventClick, - viewEventText, - viewEvent2Click, - viewEvent2Text, - eventItemPopoverTemplateResolver, -}) { - const { localeDayjs, config } = schedulerData; - const start = localeDayjs(new Date(startTime)); - const end = localeDayjs(new Date(endTime)); - - if (eventItemPopoverTemplateResolver) { - return eventItemPopoverTemplateResolver(schedulerData, eventItem, title, start, end, statusColor); - } - - const subtitle = subtitleGetter ? subtitleGetter(schedulerData, eventItem) : null; - const showViewEvent = viewEventText && viewEventClick && (eventItem.clickable1 === undefined || eventItem.clickable1); - const showViewEvent2 = viewEvent2Text && viewEvent2Click && (eventItem.clickable2 === undefined || eventItem.clickable2); - - const renderViewEvent = (text, clickHandler, marginLeft = 0) => ( - - ); - - return ( -
- - {config.eventItemPopoverShowColor && ( - -
- - )} - - - {title} - - - - {subtitle && ( - - -
- - - - {subtitle} - - - - )} - - -
- - - {start.format('HH:mm')} - {config.eventItemPopoverDateFormat && ( - - {start.format(config.eventItemPopoverDateFormat)} - - )} - - - - - - {end.format('HH:mm')} - - {config.eventItemPopoverDateFormat && ( - - {end.format(config.eventItemPopoverDateFormat)} - - )} - - - {(showViewEvent || showViewEvent2) && ( - - -
- - - {showViewEvent && renderViewEvent(viewEventText, viewEventClick)} - {showViewEvent2 && renderViewEvent(viewEvent2Text, viewEvent2Click, 16)} - - - )} -
- ); -} - -EventItemPopover.propTypes = { - schedulerData: PropTypes.object.isRequired, - eventItem: PropTypes.object.isRequired, - title: PropTypes.string.isRequired, - startTime: PropTypes.string.isRequired, - endTime: PropTypes.string.isRequired, - statusColor: PropTypes.string.isRequired, - subtitleGetter: PropTypes.func, - viewEventClick: PropTypes.func, - viewEventText: PropTypes.string, - viewEvent2Click: PropTypes.func, - viewEvent2Text: PropTypes.string, - eventItemPopoverTemplateResolver: PropTypes.func, -}; - -export default EventItemPopover; diff --git a/src/components/EventItemPopover.tsx b/src/components/EventItemPopover.tsx new file mode 100644 index 0000000..9266f04 --- /dev/null +++ b/src/components/EventItemPopover.tsx @@ -0,0 +1,175 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Col, Row } from "antd"; +import { SchedulerData } from "./SchedulerData"; +import { EventItemType } from "../types/baseType"; +import { Dayjs } from "dayjs"; +import { + EventItemPopoverTemplateResolverFunc, + SubtitleGetterFunc, + ViewEvent2ClickFunc, + ViewEventClickFunc, +} from "../types/moreTypes"; + +interface EventItemPopoverProps { + schedulerData: SchedulerData; + eventItem: EventItemType; + title: string; + startTime: Dayjs; + endTime: Dayjs; + statusColor: string; + subtitleGetter?: SubtitleGetterFunc; + viewEventClick?: ViewEventClickFunc; + viewEventText?: string; + viewEvent2Click?: ViewEvent2ClickFunc; + viewEvent2Text?: string; + eventItemPopoverTemplateResolver?: EventItemPopoverTemplateResolverFunc; +} + +function EventItemPopover({ + schedulerData, + eventItem, + title, + startTime, + endTime, + statusColor, + subtitleGetter, + viewEventClick, + viewEventText, + viewEvent2Click, + viewEvent2Text, + eventItemPopoverTemplateResolver, +}: EventItemPopoverProps) { + const { localeDayjs, config } = schedulerData; + const start = startTime.clone(); + const end = endTime.clone(); + + if (eventItemPopoverTemplateResolver) { + return eventItemPopoverTemplateResolver( + schedulerData, + eventItem, + title, + start, + end, + statusColor + ); + } + + const subtitle = subtitleGetter + ? subtitleGetter(schedulerData, eventItem) + : null; + const showViewEvent = + viewEventText && + viewEventClick && + (eventItem.clickable1 === undefined || eventItem.clickable1); + const showViewEvent2 = + viewEvent2Text && + viewEvent2Click && + (eventItem.clickable2 === undefined || eventItem.clickable2); + + const renderViewEvent = ( + text: string, + clickHandler: ViewEventClickFunc | ViewEvent2ClickFunc, + marginLeft = 0 + ) => ( + + ); + + return ( +
+ + {config.eventItemPopoverShowColor && ( + +
+ + )} + + + {title} + + + + {subtitle ? ( + <> + + +
+ + + + {subtitle} + + + + + ) : null} + + +
+ + + {start.format("HH:mm")} + {config.eventItemPopoverDateFormat && ( + + {start.format(config.eventItemPopoverDateFormat)} + + )} + + - + + + {end.format("HH:mm")} + + {config.eventItemPopoverDateFormat && ( + + {end.format(config.eventItemPopoverDateFormat)} + + )} + + + {(showViewEvent || showViewEvent2) && ( + + +
+ + + {showViewEvent && renderViewEvent(viewEventText, viewEventClick)} + {showViewEvent2 && + renderViewEvent(viewEvent2Text, viewEvent2Click, 16)} + + + )} +
+ ); +} + +// EventItemPopover.propTypes = { +// schedulerData: PropTypes.object.isRequired, +// eventItem: PropTypes.object.isRequired, +// title: PropTypes.string.isRequired, +// startTime: PropTypes.string.isRequired, +// endTime: PropTypes.string.isRequired, +// statusColor: PropTypes.string.isRequired, +// subtitleGetter: PropTypes.func, +// viewEventClick: PropTypes.func, +// viewEventText: PropTypes.string, +// viewEvent2Click: PropTypes.func, +// viewEvent2Text: PropTypes.string, +// eventItemPopoverTemplateResolver: PropTypes.func, +// }; + +export default EventItemPopover; diff --git a/src/components/HeaderView.jsx b/src/components/HeaderView.jsx deleted file mode 100644 index 4aa5507..0000000 --- a/src/components/HeaderView.jsx +++ /dev/null @@ -1,102 +0,0 @@ -/* eslint-disable no-nested-ternary */ -/* eslint-disable react/no-array-index-key */ -import React from 'react'; -import PropTypes from 'prop-types'; -import { CellUnit } from '../config/default'; - -function HeaderView({ schedulerData, nonAgendaCellHeaderTemplateResolver }) { - const { headers, cellUnit, config, localeDayjs } = schedulerData; - const headerHeight = schedulerData.getTableHeaderHeight(); - const cellWidth = schedulerData.getContentCellWidth(); - const minuteStepsInHour = schedulerData.getMinuteStepsInHour(); - - let headerList = []; - let style; - - if (cellUnit === CellUnit.Hour) { - headers.forEach((item, index) => { - if (index % minuteStepsInHour === 0) { - const datetime = localeDayjs(new Date(item.time)); - - style = item.nonWorkingTime - ? { - width: cellWidth * minuteStepsInHour, - color: config.nonWorkingTimeHeadColor, - backgroundColor: config.nonWorkingTimeHeadBgColor, - } - : { - width: cellWidth * minuteStepsInHour, - }; - - if (index === headers.length - minuteStepsInHour) { - style = item.nonWorkingTime ? { color: config.nonWorkingTimeHeadColor, backgroundColor: config.nonWorkingTimeHeadBgColor } : {}; - } - - const pFormattedList = config.nonAgendaDayCellHeaderFormat.split('|').map(pitem => datetime.format(pitem)); - let element; - - if (typeof nonAgendaCellHeaderTemplateResolver === 'function') { - element = nonAgendaCellHeaderTemplateResolver(schedulerData, item, pFormattedList, style); - } else { - const pList = pFormattedList.map((formattedItem, pIndex) =>
{formattedItem}
); - - element = ( - -
{pList}
- - ); - } - headerList.push(element); - } - }); - } else { - headerList = headers.map((item, index) => { - const datetime = localeDayjs(new Date(item.time)); - style = item.nonWorkingTime - ? { - width: cellWidth, - color: config.nonWorkingTimeHeadColor, - backgroundColor: config.nonWorkingTimeHeadBgColor, - } - : { width: cellWidth }; - if (index === headers.length - 1) style = item.nonWorkingTime ? { color: config.nonWorkingTimeHeadColor, backgroundColor: config.nonWorkingTimeHeadBgColor } : {}; - const cellFormat = cellUnit === CellUnit.Week - ? config.nonAgendaWeekCellHeaderFormat - : cellUnit === CellUnit.Month - ? config.nonAgendaMonthCellHeaderFormat - : cellUnit === CellUnit.Year - ? config.nonAgendaYearCellHeaderFormat - : config.nonAgendaOtherCellHeaderFormat; - const pFormattedList = cellFormat.split('|').map(dateFormatPart => datetime.format(dateFormatPart)); - - if (typeof nonAgendaCellHeaderTemplateResolver === 'function') { - return nonAgendaCellHeaderTemplateResolver(schedulerData, item, pFormattedList, style); - } - - const pList = pFormattedList.map((formattedItem, pIndex) =>
{formattedItem}
); - - return ( - -
{pList}
- - ); - }); - } - - return ( - - {headerList} - - ); -} - -HeaderView.propTypes = { - schedulerData: PropTypes.object.isRequired, - nonAgendaCellHeaderTemplateResolver: PropTypes.func, -}; - -HeaderView.defaultProps = { - nonAgendaCellHeaderTemplateResolver: null, -}; - -export default HeaderView; diff --git a/src/components/HeaderView.tsx b/src/components/HeaderView.tsx new file mode 100644 index 0000000..93abd88 --- /dev/null +++ b/src/components/HeaderView.tsx @@ -0,0 +1,154 @@ +/* eslint-disable no-nested-ternary */ +/* eslint-disable react/no-array-index-key */ +import React from "react"; +import PropTypes from "prop-types"; +import { CellUnit } from "../config/default"; +import { SchedulerData } from "./SchedulerData"; +import { NonAgendaCellHeaderTemplateResolverFunc } from "../types/moreTypes"; + +interface HeaderViewProps { + schedulerData: SchedulerData; + nonAgendaCellHeaderTemplateResolver?: NonAgendaCellHeaderTemplateResolverFunc | null; +} + +function HeaderView({ + schedulerData, + nonAgendaCellHeaderTemplateResolver = null, +}: HeaderViewProps) { + const { headers, cellUnit, config, localeDayjs } = schedulerData; + const headerHeight = schedulerData.getTableHeaderHeight(); + const cellWidth = schedulerData.getContentCellWidth(); + const minuteStepsInHour = schedulerData.getMinuteStepsInHour(); + + let headerList: React.ReactNode[] = []; + let style; + + if (cellUnit === CellUnit.Hour) { + headers.forEach((item, index) => { + if (index % minuteStepsInHour === 0) { + const datetime = localeDayjs(new Date(item.time)); + + style = item.nonWorkingTime + ? { + width: cellWidth * minuteStepsInHour, + color: config.nonWorkingTimeHeadColor, + backgroundColor: config.nonWorkingTimeHeadBgColor, + } + : { + width: cellWidth * minuteStepsInHour, + }; + + if (index === headers.length - minuteStepsInHour) { + style = item.nonWorkingTime + ? { + color: config.nonWorkingTimeHeadColor, + backgroundColor: config.nonWorkingTimeHeadBgColor, + } + : {}; + } + + let element; + + if (typeof nonAgendaCellHeaderTemplateResolver === "function") { + element = nonAgendaCellHeaderTemplateResolver( + schedulerData, + item, + datetime, + style + ); + } else { + const pFormattedList = config.nonAgendaDayCellHeaderFormat + .split("|") + .map((pitem) => datetime.format(pitem)); + + const pList = pFormattedList.map((formattedItem, pIndex) => ( +
{formattedItem}
+ )); + + element = ( + +
{pList}
+ + ); + } + if (element) headerList.push(element); + } + }); + } else { + const cellFormat = + cellUnit === CellUnit.Week + ? config.nonAgendaWeekCellHeaderFormat + : cellUnit === CellUnit.Month + ? config.nonAgendaMonthCellHeaderFormat + : cellUnit === CellUnit.Year + ? config.nonAgendaYearCellHeaderFormat + : config.nonAgendaOtherCellHeaderFormat; + + headerList = headers + .map((item, index) => { + const datetime = localeDayjs(new Date(item.time)); + style = item.nonWorkingTime + ? { + width: cellWidth, + color: config.nonWorkingTimeHeadColor, + backgroundColor: config.nonWorkingTimeHeadBgColor, + } + : { width: cellWidth }; + if (index === headers.length - 1) + style = item.nonWorkingTime + ? { + color: config.nonWorkingTimeHeadColor, + backgroundColor: config.nonWorkingTimeHeadBgColor, + } + : {}; + + if (typeof nonAgendaCellHeaderTemplateResolver === "function") { + return nonAgendaCellHeaderTemplateResolver( + schedulerData, + item, + datetime, + style + ); + } + const pFormattedList = cellFormat + .split("|") + .map((dateFormatPart) => datetime.format(dateFormatPart)); + + const pList = pFormattedList.map((formattedItem, pIndex) => ( +
{formattedItem}
+ )); + + return ( + +
{pList}
+ + ); + }) + .filter((element) => element !== undefined) as React.ReactNode[]; + } + + return ( + + {headerList} + + ); +} + +// HeaderView.propTypes = { +// schedulerData: PropTypes.object.isRequired, +// nonAgendaCellHeaderTemplateResolver: PropTypes.func, +// }; + +// HeaderView.defaultProps = { +// nonAgendaCellHeaderTemplateResolver: null, +// }; + +export default HeaderView; diff --git a/src/components/ResourceEvents.jsx b/src/components/ResourceEvents.jsx deleted file mode 100644 index 99f9b34..0000000 --- a/src/components/ResourceEvents.jsx +++ /dev/null @@ -1,368 +0,0 @@ -import React, { Component } from 'react'; -import { PropTypes } from 'prop-types'; -import AddMore from './AddMore'; -import Summary from './Summary'; -import SelectedArea from './SelectedArea'; -import { CellUnit, DATETIME_FORMAT, SummaryPos, DnDTypes } from '../config/default'; -import { getPos } from '../helper/utility'; - -class ResourceEvents extends Component { - constructor(props) { - super(props); - - this.state = { - isSelecting: false, - left: 0, - width: 0, - }; - this.supportTouch = false; // 'ontouchstart' in window; - } - - static propTypes = { - resourceEvents: PropTypes.object.isRequired, - schedulerData: PropTypes.object.isRequired, - dndSource: PropTypes.object.isRequired, - onSetAddMoreState: PropTypes.func, - updateEventStart: PropTypes.func, - updateEventEnd: PropTypes.func, - moveEvent: PropTypes.func, - movingEvent: PropTypes.func, - conflictOccurred: PropTypes.func, - subtitleGetter: PropTypes.func, - eventItemClick: PropTypes.func, - viewEventClick: PropTypes.func, - viewEventText: PropTypes.string, - viewEvent2Click: PropTypes.func, - viewEvent2Text: PropTypes.string, - newEvent: PropTypes.func, - eventItemTemplateResolver: PropTypes.func, - }; - - componentDidMount() { - const { schedulerData } = this.props; - const { config } = schedulerData; - this.supportTouch = 'ontouchstart' in window; - - if (config.creatable === true) { - this.supportTouchHelper(); - } - } - - componentDidUpdate(prevProps) { - if (prevProps !== this.props) { - this.supportTouchHelper('remove'); - if (this.props.schedulerData.config.creatable) { - this.supportTouchHelper(); - } - } - } - - supportTouchHelper = (evType = 'add') => { - const ev = evType === 'add' ? this.eventContainer.addEventListener : this.eventContainer.removeEventListener; - if (this.supportTouch) { - // ev('touchstart', this.initDrag, false); - } else { - ev('mousedown', this.initDrag, false); - } - }; - - initDrag = ev => { - const { isSelecting } = this.state; - if (isSelecting) return; - if ((ev.srcElement || ev.target) !== this.eventContainer) return; - - ev.stopPropagation(); - - const { resourceEvents } = this.props; - if (resourceEvents.groupOnly) return; - const [clientX, toReturn] = this.dragHelper(ev, 'init'); - - if (toReturn) { - return; - } - - const { schedulerData } = this.props; - const cellWidth = schedulerData.getContentCellWidth(); - const pos = getPos(this.eventContainer); - const startX = clientX - pos.x; - const leftIndex = Math.floor(startX / cellWidth); - const left = leftIndex * cellWidth; - const rightIndex = Math.ceil(startX / cellWidth); - const width = (rightIndex - leftIndex) * cellWidth; - - this.setState({ startX, left, leftIndex, width, rightIndex, isSelecting: true }); - - if (this.supportTouch) { - document.documentElement.addEventListener('touchmove', this.doDrag, false); - document.documentElement.addEventListener('touchend', this.stopDrag, false); - document.documentElement.addEventListener('touchcancel', this.cancelDrag, false); - } else { - document.documentElement.addEventListener('mousemove', this.doDrag, false); - document.documentElement.addEventListener('mouseup', this.stopDrag, false); - } - document.onselectstart = () => false; - document.ondragstart = () => false; - }; - - doDrag = ev => { - ev.stopPropagation(); - - const [clientX, toReturn] = this.dragHelper(ev, 'do'); - - if (toReturn) { - return; - } - const { startX } = this.state; - const { schedulerData } = this.props; - const { headers } = schedulerData; - const cellWidth = schedulerData.getContentCellWidth(); - const pos = getPos(this.eventContainer); - const currentX = clientX - pos.x; - let leftIndex = Math.floor(Math.min(startX, currentX) / cellWidth); - leftIndex = leftIndex < 0 ? 0 : leftIndex; - const left = leftIndex * cellWidth; - let rightIndex = Math.ceil(Math.max(startX, currentX) / cellWidth); - rightIndex = rightIndex > headers.length ? headers.length : rightIndex; - const width = (rightIndex - leftIndex) * cellWidth; - - this.setState({ leftIndex, left, rightIndex, width, isSelecting: true }); - }; - - dragHelper = (ev, dragType) => { - let clientX = 0; - if (this.supportTouch) { - if (ev.changedTouches.length === 0) return [clientX, true]; - const touch = ev.changedTouches[0]; - clientX = touch.pageX; - } else if (dragType === 'init') { - if (ev.buttons !== undefined && ev.buttons !== 1) return [clientX, true]; - clientX = ev.clientX; - } else { - clientX = ev.clientX; - } - return [clientX, false]; - }; - - stopDrag = ev => { - ev.stopPropagation(); - - const { schedulerData, newEvent, resourceEvents } = this.props; - const { headers, events, config, cellUnit, localeDayjs } = schedulerData; - const { leftIndex, rightIndex } = this.state; - if (this.supportTouch) { - document.documentElement.removeEventListener('touchmove', this.doDrag, false); - document.documentElement.removeEventListener('touchend', this.stopDrag, false); - document.documentElement.removeEventListener('touchcancel', this.cancelDrag, false); - } else { - document.documentElement.removeEventListener('mousemove', this.doDrag, false); - document.documentElement.removeEventListener('mouseup', this.stopDrag, false); - } - document.onselectstart = null; - document.ondragstart = null; - - const startTime = headers[leftIndex].time; - let endTime = resourceEvents.headerItems[rightIndex - 1].end; - if (cellUnit !== CellUnit.Hour) { - endTime = localeDayjs(new Date(resourceEvents.headerItems[rightIndex - 1].start)) - .hour(23) - .minute(59) - .second(59) - .format(DATETIME_FORMAT); - } - const { slotId } = resourceEvents; - const { slotName } = resourceEvents; - - this.setState({ - startX: 0, - leftIndex: 0, - left: 0, - rightIndex: 0, - width: 0, - isSelecting: false, - }); - - let hasConflict = false; - if (config.checkConflict) { - const start = localeDayjs(new Date(startTime)); - const end = localeDayjs(endTime); - - events.forEach(e => { - if (schedulerData._getEventSlotId(e) === slotId) { - const eStart = localeDayjs(e.start); - const eEnd = localeDayjs(e.end); - if ((start >= eStart && start < eEnd) || (end > eStart && end <= eEnd) || (eStart >= start && eStart < end) || (eEnd > start && eEnd <= end)) hasConflict = true; - } - }); - } - - if (hasConflict) { - const { conflictOccurred } = this.props; - if (conflictOccurred !== undefined) { - conflictOccurred( - schedulerData, - 'New', - { - id: undefined, - start: startTime, - end: endTime, - slotId, - slotName, - title: undefined, - }, - DnDTypes.EVENT, - slotId, - slotName, - startTime, - endTime, - ); - } else { - console.log('Conflict occurred, set conflictOccurred func in Scheduler to handle it'); - } - } else if (newEvent !== undefined) newEvent(schedulerData, slotId, slotName, startTime, endTime); - }; - - cancelDrag = ev => { - ev.stopPropagation(); - - const { isSelecting } = this.state; - if (isSelecting) { - document.documentElement.removeEventListener('touchmove', this.doDrag, false); - document.documentElement.removeEventListener('touchend', this.stopDrag, false); - document.documentElement.removeEventListener('touchcancel', this.cancelDrag, false); - document.onselectstart = null; - document.ondragstart = null; - this.setState({ - startX: 0, - leftIndex: 0, - left: 0, - rightIndex: 0, - width: 0, - isSelecting: false, - }); - } - }; - - onAddMoreClick = headerItem => { - const { onSetAddMoreState, resourceEvents, schedulerData } = this.props; - if (onSetAddMoreState) { - const { config } = schedulerData; - const cellWidth = schedulerData.getContentCellWidth(); - const index = resourceEvents.headerItems.indexOf(headerItem); - if (index !== -1) { - let left = index * (cellWidth - 1); - const pos = getPos(this.eventContainer); - left += pos.x; - const top = pos.y; - const height = (headerItem.count + 1) * config.eventItemLineHeight + 20; - - onSetAddMoreState({ - headerItem, - left, - top, - height, - }); - } - } - }; - - eventContainerRef = element => { - this.eventContainer = element; - }; - - render() { - const { resourceEvents, schedulerData, connectDropTarget, dndSource } = this.props; - const { cellUnit, startDate, endDate, config, localeDayjs } = schedulerData; - const { isSelecting, left, width } = this.state; - const cellWidth = schedulerData.getContentCellWidth(); - const cellMaxEvents = schedulerData.getCellMaxEvents(); - const rowWidth = schedulerData.getContentTableWidth(); - const DnDEventItem = dndSource.getDragSource(); - - const selectedArea = isSelecting ? :
; - - const eventList = []; - resourceEvents.headerItems.forEach((headerItem, index) => { - if (headerItem.count > 0 || headerItem.summary !== undefined) { - const isTop = config.summaryPos === SummaryPos.TopRight || config.summaryPos === SummaryPos.Top || config.summaryPos === SummaryPos.TopLeft; - const marginTop = resourceEvents.hasSummary && isTop ? 1 + config.eventItemLineHeight : 1; - const renderEventsMaxIndex = headerItem.addMore === 0 ? cellMaxEvents : headerItem.addMoreIndex; - - headerItem.events.forEach((evt, idx) => { - if (idx < renderEventsMaxIndex && evt !== undefined && evt.render) { - let durationStart = localeDayjs(new Date(startDate)); - let durationEnd = localeDayjs(endDate); - if (cellUnit === CellUnit.Hour) { - durationStart = localeDayjs(new Date(startDate)).add(config.dayStartFrom, 'hours'); - durationEnd = localeDayjs(endDate).add(config.dayStopTo + 1, 'hours'); - } - const eventStart = localeDayjs(evt.eventItem.start); - const eventEnd = localeDayjs(evt.eventItem.end); - const isStart = eventStart >= durationStart; - const isEnd = eventEnd <= durationEnd; - const left = index * cellWidth + (index > 0 ? 2 : 3); - const width = evt.span * cellWidth - (index > 0 ? 5 : 6) > 0 ? evt.span * cellWidth - (index > 0 ? 5 : 6) : 0; - const top = marginTop + idx * config.eventItemLineHeight; - const eventItem = ( - - ); - eventList.push(eventItem); - } - }); - - if (headerItem.addMore > 0) { - const left = index * cellWidth + (index > 0 ? 2 : 3); - const width = cellWidth - (index > 0 ? 5 : 6); - const top = marginTop + headerItem.addMoreIndex * config.eventItemLineHeight; - const addMoreItem = ( - - ); - eventList.push(addMoreItem); - } - - if (headerItem.summary !== undefined) { - const top = isTop ? 1 : resourceEvents.rowHeight - config.eventItemLineHeight + 1; - const left = index * cellWidth + (index > 0 ? 2 : 3); - const width = cellWidth - (index > 0 ? 5 : 6); - const key = `${resourceEvents.slotId}_${headerItem.time}`; - const summary = ; - eventList.push(summary); - } - } - }); - - const eventContainer = ( -
- {selectedArea} - {eventList} -
- ); - return ( - - {config.dragAndDropEnabled ? connectDropTarget(eventContainer) : eventContainer} - - ); - } -} - -export default ResourceEvents; diff --git a/src/components/ResourceView.jsx b/src/components/ResourceView.jsx deleted file mode 100644 index 0bd31d5..0000000 --- a/src/components/ResourceView.jsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { MinusSquareOutlined, PlusSquareOutlined } from '@ant-design/icons'; - -function ResourceView({ schedulerData, contentScrollbarHeight, slotClickedFunc, slotItemTemplateResolver, toggleExpandFunc }) { - const { renderData } = schedulerData; - const width = schedulerData.getResourceTableWidth() - 2; - const paddingBottom = contentScrollbarHeight; - const displayRenderData = renderData.filter(o => o.render); - - const handleToggleExpand = item => { - if (toggleExpandFunc) { - toggleExpandFunc(schedulerData, item.slotId); - } - }; - - const renderSlotItem = (item, indents) => { - let indent = ; - - const iconProps = { key: `es${item.indent}`, onClick: () => handleToggleExpand(item) }; - if (item.hasChildren) { - indent = item.expanded ? : ; - } - - indents.push(indent); - - const slotCell = slotClickedFunc ? ( - - {indents} - - - ) : ( - - {indents} - - - ); - - let slotItem = ( -
- {slotCell} -
- ); - - if (slotItemTemplateResolver) { - const resolvedTemplate = slotItemTemplateResolver(schedulerData, item, slotClickedFunc, width, 'overflow-text header2-text'); - if (resolvedTemplate) { - slotItem = resolvedTemplate; - } - } - - const tdStyle = { - height: item.rowHeight, - backgroundColor: item.groupOnly ? schedulerData.config.groupOnlySlotColor : undefined, - }; - - return ( - - - {slotItem} - - - ); - }; - - const resourceList = displayRenderData.map(item => { - const indents = []; - for (let i = 0; i < item.indent; i += 1) { - indents.push(); - } - - return renderSlotItem(item, indents); - }); - - return ( -
- - {resourceList} -
-
- ); -} - -ResourceView.propTypes = { - schedulerData: PropTypes.object.isRequired, - contentScrollbarHeight: PropTypes.number.isRequired, - slotClickedFunc: PropTypes.func, - slotItemTemplateResolver: PropTypes.func, - toggleExpandFunc: PropTypes.func, -}; - -export default ResourceView; diff --git a/src/components/ResourceView.tsx b/src/components/ResourceView.tsx new file mode 100644 index 0000000..b3947ee --- /dev/null +++ b/src/components/ResourceView.tsx @@ -0,0 +1,149 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { MinusSquareOutlined, PlusSquareOutlined } from "@ant-design/icons"; +import { + SlotClickedFunc, + SlotItemTemplateResolverFunc, +} from "../types/moreTypes"; +import { SchedulerData } from "./SchedulerData"; +import { RenderDataItem } from "../types/baseType"; + +interface ResourceViewProps { + schedulerData: SchedulerData; + contentScrollbarHeight: number; + slotClickedFunc?: SlotClickedFunc; + slotItemTemplateResolver?: SlotItemTemplateResolverFunc; + toggleExpandFunc?: any; +} + +function ResourceView({ + schedulerData, + contentScrollbarHeight, + slotClickedFunc, + slotItemTemplateResolver, + toggleExpandFunc, +}: ResourceViewProps) { + const { renderData } = schedulerData; + const width = schedulerData.getResourceTableWidth() - 2; + const paddingBottom = contentScrollbarHeight; + const displayRenderData = renderData.filter((o) => o.render); + + const handleToggleExpand = (item: RenderDataItem) => { + if (toggleExpandFunc) { + toggleExpandFunc(schedulerData, item.slotId); + } + }; + + const renderSlotItem = (item: RenderDataItem, indents: React.ReactNode[]) => { + let indent = ; + + const iconProps = { + key: `es${item.indent}`, + onClick: () => handleToggleExpand(item), + }; + if (item.hasChildren) { + indent = item.expanded ? ( + + ) : ( + + ); + } + + indents.push(indent); + + const slotCell = slotClickedFunc ? ( + + {indents} + + + ) : ( + + {indents} + + + ); + + let slotItem: React.ReactNode = ( +
+ {slotCell} +
+ ); + // console.log("suppsed RenderDataItem", item) + if (slotItemTemplateResolver) { + const resolvedTemplate = slotItemTemplateResolver( + schedulerData, + item, + slotClickedFunc, + width, + "overflow-text header2-text" + ); + if (resolvedTemplate) { + slotItem = resolvedTemplate; + } + } + + const tdStyle = { + height: item.rowHeight, + backgroundColor: item.groupOnly + ? schedulerData.config.groupOnlySlotColor + : undefined, + }; + + return ( + + + {slotItem} + + + ); + }; + + const resourceList = displayRenderData.map((item) => { + const indents = []; + for (let i = 0; i < item.indent; i += 1) { + indents.push(); + } + return renderSlotItem(item, indents); + }); + + return ( +
+ + {resourceList} +
+
+ ); +} + +// ResourceView.propTypes = { +// schedulerData: PropTypes.object.isRequired, +// contentScrollbarHeight: PropTypes.number.isRequired, +// slotClickedFunc: PropTypes.func, +// slotItemTemplateResolver: PropTypes.func, +// toggleExpandFunc: PropTypes.func, +// }; + +export default ResourceView; diff --git a/src/components/Scheduler.tsx b/src/components/Scheduler.tsx new file mode 100644 index 0000000..4cbc4d0 --- /dev/null +++ b/src/components/Scheduler.tsx @@ -0,0 +1,723 @@ +import React, { + Component, + CSSProperties, + DetailedHTMLProps, + HTMLAttributes, + RefObject, + UIEventHandler, +} from "react"; +import PropTypes from "prop-types"; + +// Col, Row and Icon do not have their own less files for styling. They use +// rules declared in antd's global css. If these styles are imported directly +// from within antd, they'll include, for instance, reset rules. These will +// affect everything on the page and in essence would leak antd's global styles +// into all projects using this library. Instead of doing that, we are using +// a hack which allows us to wrap all antd styles to target specific root. In +// this case the root id will be "RBS-Scheduler-root". This way the reset styles +// won't be applied to elements declared outside of component. +// +// For development +// This fix is implemented with webpack's NormalModuleReplacementPlugin in +// webpack/webpack-dev.config.js. +// +// +// The next components have their own specific stylesheets which we import +// separately here to avoid importing from files which have required the global +// antd styles. + +import { EventItemComponent } from "../classComponents/EventItem"; +import { DnDSource } from "../classComponents/DnDSource"; +import { DnDContext } from "../classComponents/DnDContext"; +import ResourceView from "./ResourceView"; +import HeaderView from "./HeaderView"; +import BodyView from "./BodyView"; +import { ResourceEventsComponent } from "../classComponents/ResourceEvents"; +import AgendaView from "./AgendaView"; +import AddMorePopover from "./AddMorePopover"; +import DemoData from "../sample-data/sample1"; +import SchedulerHeader from "./SchedulerHeader"; +import { + ViewType, + CellUnit, + DATETIME_FORMAT, + DATE_FORMAT, + SummaryPos, +} from "../config/default"; +import wrapperFun from "./WrapperFun"; +import { SchedulerData } from "./SchedulerData"; +import { + ConflictOccurredFunc, + EventItemClickFunc, + EventItemPopoverTemplateResolverFunc, + EventItemTemplateResolverFunc, + MoveEventFunc, + MovingEventFunc, + NewEventFunc, + NextClickFunc, + NonAgendaCellHeaderTemplateResolverFunc, + OnScrollBottomFunc, + OnScrollLeftFunc, + OnScrollRightFunc, + OnScrollTopFunc, + OnSelectDateFunc, + OnSetAddMoreStateFunc, + OnViewChangeFunc, + PrevClickFunc, + SchedulerProps, + SlotClickedFunc, + SlotItemTemplateResolverFunc, + SubtitleGetterFunc, + ToggleExpandFunc, + UpdateEventEndFunc, + UpdateEventStartFunc, + ViewEvent2ClickFunc, + ViewEventClickFunc, +} from "../types/moreTypes"; +import { Dayjs } from "dayjs"; + +interface SchedulerState { + dndContext: DnDContext; + contentScrollbarHeight: number; + contentScrollbarWidth: number; + resourceScrollbarHeight: number; + resourceScrollbarWidth: number; + documentWidth: number; + documentHeight: number; + spinning: boolean; +} + +class Scheduler extends Component { + private currentArea: number; + private scrollLeft: number; + private scrollTop: number; + private schedulerHead: HTMLDivElement | null = null; + private schedulerResource: HTMLDivElement | null = null; + private schedulerContent: HTMLDivElement | null = null; + private schedulerContentBgTable: HTMLDivElement | null = null; + private ulObserver: ResizeObserver | null = null; + + constructor(props: SchedulerProps) { + super(props); + + const { schedulerData, dndSources, parentRef } = props; + let sources: DnDSource[] = []; + sources.push( + new DnDSource( + (dndProps) => dndProps.eventItem, + EventItemComponent, + schedulerData.config.dragAndDropEnabled + ) + ); + if (dndSources !== undefined && dndSources.length > 0) { + sources = [...sources, ...dndSources]; + } + const dndContext = new DnDContext(sources, ResourceEventsComponent); + + this.currentArea = -1; + this.state = { + dndContext, + contentScrollbarHeight: 17, + contentScrollbarWidth: 17, + resourceScrollbarHeight: 17, + resourceScrollbarWidth: 17, + documentWidth: 0, + documentHeight: 0, + spinning: false, + }; + this.scrollLeft = 0; + this.scrollTop = 0; + + if ( + (schedulerData.isSchedulerResponsive() && + !schedulerData.config.responsiveByParent) || + parentRef === undefined + ) { + schedulerData._setDocumentWidth(document.documentElement.clientWidth); + window.onresize = this.onWindowResize; + } + } + + onWindowResize = (e: UIEvent) => { + const { schedulerData } = this.props; + schedulerData._setDocumentWidth(document.documentElement.clientWidth); + this.setState({ + documentWidth: document.documentElement.clientWidth, + documentHeight: document.documentElement.clientHeight, + }); + }; + + componentDidMount() { + const { schedulerData, parentRef } = this.props; + + this.resolveScrollbarSize(); + + if (parentRef !== undefined) { + if (schedulerData.config.responsiveByParent && !!parentRef.current) { + schedulerData._setDocumentWidth(parentRef.current.offsetWidth); + this.ulObserver = new ResizeObserver((entries, observer) => { + if (parentRef.current) { + const width = parentRef.current.offsetWidth; + const height = parentRef.current.offsetHeight; + console.log(); + schedulerData._setDocumentWidth(width); + this.setState({ + documentWidth: width, + documentHeight: height, + }); + } + }); + + this.ulObserver.observe(parentRef.current); + } + } + } + + componentDidUpdate() { + this.resolveScrollbarSize(); + + const { schedulerData } = this.props; + const { localeDayjs, behaviors } = schedulerData; + if ( + schedulerData.getScrollToSpecialDayjs() && + !!behaviors.getScrollSpecialDayjsFunc + ) { + if ( + !!this.schedulerContent && + this.schedulerContent.scrollWidth > this.schedulerContent.clientWidth + ) { + const start = schedulerData.startDate.startOf("day"); + const end = schedulerData.endDate.endOf("day"); + const specialDayjs = behaviors.getScrollSpecialDayjsFunc( + schedulerData + // Does not exists in the function + // start, + // end + ); + if (specialDayjs >= start && specialDayjs <= end) { + let index = 0; + schedulerData.headers.forEach((item) => { + const header = localeDayjs(new Date(item.time)); + if (specialDayjs >= header) index++; + }); + this.schedulerContent.scrollLeft = + (index - 1) * schedulerData.getContentCellWidth(); + + schedulerData.setScrollToSpecialDayjs(false); + } + } + } + } + + getSchedulerSizes() { + const { schedulerData, leftCustomHeader, rightCustomHeader } = this.props; + const { viewType, renderData, showAgenda, config } = schedulerData; + const width = schedulerData.getSchedulerWidth(); + + const resourceTableWidth = schedulerData.getResourceTableWidth(); + const schedulerContainerWidth = + width - (config.resourceViewEnabled ? resourceTableWidth : 0); + const schedulerWidth = schedulerData.getContentTableWidth() - 1; + const fullSchedulerWidth = + schedulerData.getContentTableWidth() + + (config.resourceViewEnabled ? resourceTableWidth : 0); + const unusedSpace = schedulerContainerWidth - schedulerWidth; + + return { + resourceTableWidth, + schedulerContainerWidth, + schedulerWidth, + fullSchedulerWidth, + unusedSpace, + }; + } + + render() { + const { schedulerData, leftCustomHeader, rightCustomHeader } = this.props; + const { viewType, renderData, showAgenda, config } = schedulerData; + const width = schedulerData.getSchedulerWidth(); + + let tbodyContent = ; + if (showAgenda) { + tbodyContent = ( + + ); + } else { + const { + fullSchedulerWidth, + resourceTableWidth, + schedulerContainerWidth, + schedulerWidth, + unusedSpace, + } = this.getSchedulerSizes(); + + const DndResourceEvents = this.state.dndContext.getDropTarget( + config.dragAndDropEnabled + ); + const eventDndSource = this.state.dndContext.getDndSource(); + + const displayRenderData = renderData.filter((o) => o.render); + const resourceEventsList = displayRenderData.map((item) => ( + + )); + + const { contentScrollbarHeight } = this.state; + const { contentScrollbarWidth } = this.state; + const { resourceScrollbarHeight } = this.state; + const { resourceScrollbarWidth } = this.state; + const contentHeight = config.schedulerContentHeight; + const resourcePaddingBottom = + resourceScrollbarHeight === 0 ? contentScrollbarHeight : 0; + const contentPaddingBottom = + contentScrollbarHeight === 0 ? resourceScrollbarHeight : 0; + let schedulerContentStyle: CSSProperties = { + overflowX: viewType === ViewType.Week ? "hidden" : "auto", + overflowY: "auto", + margin: "0px", + position: "relative", + height: contentHeight, + paddingBottom: contentPaddingBottom, + }; + let resourceContentStyle: CSSProperties = { + height: contentHeight, + overflowX: "hidden", + overflowY: "hidden", + scrollbarWidth: "none", // For Firefox + msOverflowStyle: "none", // For Internet Explorer and Edge + width: resourceTableWidth + resourceScrollbarWidth - 2, + marginRight: `-${contentScrollbarWidth}px`, + marginBottom: `-${contentScrollbarHeight}px`, + }; + if (config.schedulerMaxHeight > 0) { + schedulerContentStyle = { + ...schedulerContentStyle, + maxHeight: config.schedulerMaxHeight - config.tableHeaderHeight, + }; + resourceContentStyle = { + ...resourceContentStyle, + maxHeight: config.schedulerMaxHeight - config.tableHeaderHeight, + }; + } + + const resourceName = schedulerData.isEventPerspective + ? config.taskName + : config.resourceName; + tbodyContent = ( + <> + + +
+
+
+ + + + + + +
{resourceName}
+
+
+
+ +
+
+ + +
0 + ? schedulerContainerWidth - unusedSpace + : schedulerContainerWidth, + verticalAlign: "top", + }} + > +
+
+
+ + +
+
+
+
+
+
+
+ + {resourceEventsList} +
+
+
+ + +
+
+
+
+
+ + + + ); + } + + let schedulerHeader: React.ReactNode =
; + if (config.headerEnabled) { + if (this.props?.schedulerHeaderTemplateResolver) { + schedulerHeader = this.props.schedulerHeaderTemplateResolver({ + onViewChange: this.onViewChange, + schedulerData, + onSelectDate: this.onSelect, + goNext: this.goNext, + goBack: this.goBack, + rightCustomHeader, + leftCustomHeader, + }); + } else { + schedulerHeader = ( + + ); + } + } + + const { fullSchedulerWidth } = this.getSchedulerSizes(); + + return ( + + + + + + + {tbodyContent} +
+
+ {schedulerHeader} +
+
+ ); + } + + resolveScrollbarSize = (): void => { + const { schedulerData } = this.props; + let contentScrollbarHeight = 17; + let contentScrollbarWidth = 17; + let resourceScrollbarHeight = 17; + let resourceScrollbarWidth = 17; + if (this.schedulerContent) { + contentScrollbarHeight = + this.schedulerContent.offsetHeight - this.schedulerContent.clientHeight; + contentScrollbarWidth = + this.schedulerContent.offsetWidth - this.schedulerContent.clientWidth; + } + if (this.schedulerResource) { + resourceScrollbarHeight = + this.schedulerResource.offsetHeight - + this.schedulerResource.clientHeight; + resourceScrollbarWidth = + this.schedulerResource.offsetWidth - this.schedulerResource.clientWidth; + } + + let tmpState: Partial = {}; + let needSet = false; + if (contentScrollbarHeight !== this.state.contentScrollbarHeight) { + tmpState = { ...tmpState, contentScrollbarHeight }; + needSet = true; + } + if (contentScrollbarWidth !== this.state.contentScrollbarWidth) { + tmpState = { ...tmpState, contentScrollbarWidth }; + needSet = true; + } + if (resourceScrollbarHeight !== this.state.resourceScrollbarHeight) { + tmpState = { ...tmpState, resourceScrollbarHeight }; + needSet = true; + } + if (resourceScrollbarWidth !== this.state.resourceScrollbarWidth) { + tmpState = { ...tmpState, resourceScrollbarWidth }; + needSet = true; + } + if (needSet) this.setState(tmpState as SchedulerState); + }; + + schedulerHeadRef = (element: HTMLDivElement | null): void => { + this.schedulerHead = element; + }; + + onSchedulerHeadMouseOver = (): void => { + this.currentArea = 2; + }; + + onSchedulerHeadMouseOut = (): void => { + this.currentArea = -1; + }; + + onSchedulerHeadScroll = (): void => { + if ( + (this.currentArea === 2 || this.currentArea === -1) && + this.schedulerContent && + this.schedulerHead && + this.schedulerContent.scrollLeft !== this.schedulerHead.scrollLeft + ) { + this.schedulerContent.scrollLeft = this.schedulerHead.scrollLeft; + } + }; + + schedulerResourceRef = (element: HTMLDivElement | null): void => { + this.schedulerResource = element; + }; + + onSchedulerResourceMouseOver = (): void => { + this.currentArea = 1; + }; + + onSchedulerResourceMouseOut = (): void => { + this.currentArea = -1; + }; + + onSchedulerResourceScroll = (): void => { + if (this.schedulerResource && this.schedulerContent) { + if ( + (this.currentArea === 1 || this.currentArea === -1) && + this.schedulerContent.scrollTop !== this.schedulerResource.scrollTop + ) { + this.schedulerContent.scrollTop = this.schedulerResource.scrollTop; + } + } + }; + + schedulerContentRef = (element: HTMLDivElement | null): void => { + this.schedulerContent = element; + }; + + schedulerContentBgTableRef = (element: HTMLTableElement | null): void => { + this.schedulerContentBgTable = element; + }; + + onSchedulerContentMouseOver = (): void => { + this.currentArea = 0; + }; + + onSchedulerContentMouseOut = (): void => { + this.currentArea = -1; + }; + + onSchedulerContentScroll = (): void => { + if (this.schedulerResource && this.schedulerContent && this.schedulerHead) { + if (this.currentArea === 0 || this.currentArea === -1) { + if (this.schedulerHead.scrollLeft !== this.schedulerContent.scrollLeft) + this.schedulerHead.scrollLeft = this.schedulerContent.scrollLeft; + if ( + this.schedulerResource.scrollTop !== this.schedulerContent.scrollTop + ) + this.schedulerResource.scrollTop = this.schedulerContent.scrollTop; + } + } + + const { + schedulerData, + onScrollLeft, + onScrollRight, + onScrollTop, + onScrollBottom, + } = this.props; + if (this.schedulerContent) { + if (this.schedulerContent.scrollLeft !== this.scrollLeft) { + if ( + this.schedulerContent.scrollLeft === 0 && + onScrollLeft !== undefined + ) { + onScrollLeft( + schedulerData, + this.schedulerContent, + this.schedulerContent.scrollWidth - + this.schedulerContent.clientWidth + ); + } + if ( + Math.round(this.schedulerContent.scrollLeft) === + this.schedulerContent.scrollWidth - + this.schedulerContent.clientWidth && + onScrollRight !== undefined + ) { + onScrollRight( + schedulerData, + this.schedulerContent, + this.schedulerContent.scrollWidth - + this.schedulerContent.clientWidth + ); + } + } else if (this.schedulerContent.scrollTop !== this.scrollTop) { + if ( + this.schedulerContent.scrollTop === 0 && + onScrollTop !== undefined + ) { + onScrollTop( + schedulerData, + this.schedulerContent, + this.schedulerContent.scrollHeight - + this.schedulerContent.clientHeight + ); + } + if ( + Math.round(this.schedulerContent.scrollTop) === + this.schedulerContent.scrollHeight - + this.schedulerContent.clientHeight && + onScrollBottom !== undefined + ) { + onScrollBottom( + schedulerData, + this.schedulerContent, + this.schedulerContent.scrollHeight - + this.schedulerContent.clientHeight + ); + } + } + this.scrollLeft = this.schedulerContent.scrollLeft; + this.scrollTop = this.schedulerContent.scrollTop; + } + }; + + onViewChange = (e: React.ChangeEvent): void => { + const { onViewChange, schedulerData } = this.props; + // TODO: Needs to be fixed + const viewType = e.target.value as ViewType; + const showAgenda = viewType === ViewType.Week; + const isEventPerspective = viewType === ViewType.Week; + onViewChange(schedulerData, { viewType, showAgenda, isEventPerspective }); + this.setState({ ...this.state, spinning: false }); + }; + + goNext = (): void => { + const { nextClick, schedulerData } = this.props; + nextClick(schedulerData); + }; + + goBack = (): void => { + const { prevClick, schedulerData } = this.props; + prevClick(schedulerData); + }; + + onSelect = (date: Dayjs): void => { + const { onSelectDate, schedulerData } = this.props; + onSelectDate(schedulerData, date); + }; +} + +export { + DATE_FORMAT, + DATETIME_FORMAT, + Scheduler, + SchedulerData, + ViewType, + CellUnit, + SummaryPos, + DnDSource, + DnDContext, + AddMorePopover, + DemoData, + wrapperFun, +}; diff --git a/src/components/SchedulerData.js b/src/components/SchedulerData.js deleted file mode 100644 index 44089a3..0000000 --- a/src/components/SchedulerData.js +++ /dev/null @@ -1,1207 +0,0 @@ -import dayjs from 'dayjs'; -import quarterOfYear from 'dayjs/plugin/quarterOfYear'; -import utc from 'dayjs/plugin/utc'; -import weekday from 'dayjs/plugin/weekday'; -import { RRuleSet, rrulestr } from 'rrule'; -import config from '../config/scheduler'; -import behaviors from '../helper/behaviors'; -import { ViewType, CellUnit, DATE_FORMAT, DATETIME_FORMAT } from '../config/default'; - -export default class SchedulerData { - constructor(date = dayjs(), viewType = ViewType.Week, showAgenda = false, isEventPerspective = false, newConfig = undefined, newBehaviors = undefined) { - this.resources = []; - this.events = []; - this.eventGroups = []; - this.eventGroupsAutoGenerated = true; - this.viewType = viewType; - this.cellUnit = viewType === ViewType.Day ? CellUnit.Hour : CellUnit.Day; - this.showAgenda = showAgenda; - this.isEventPerspective = isEventPerspective; - this.resizing = false; - this.scrollToSpecialDayjs = false; - this.documentWidth = 0; - this._shouldReloadViewType = false; - - this.calendarPopoverLocale = undefined; - dayjs.extend(quarterOfYear); - dayjs.extend(weekday); - dayjs.extend(utc); - this.localeDayjs = dayjs; - this.config = newConfig === undefined ? config : { ...config, ...newConfig }; - this._validateMinuteStep(this.config.minuteStep); - this.behaviors = newBehaviors === undefined ? behaviors : { ...behaviors, ...newBehaviors }; - this._resolveDate(0, date); - this._createHeaders(); - this._createRenderData(); - } - - setSchedulerLocale(preset) { - if (!preset) return; - - this.localeDayjs.locale(preset); - this._shouldReloadViewType = true; - this.setViewType(this.viewType, this.showAgenda, this.isEventPerspective); - } - - setCalendarPopoverLocale(lang) { - if (lang) { - this.calendarPopoverLocale = lang; - } - } - - setResources(resources) { - this._validateResource(resources); - this.resources = Array.from(new Set(resources)); - this._createRenderData(); - this.setScrollToSpecialDayjs(true); - } - - setEventGroupsAutoGenerated(autoGenerated) { - this.eventGroupsAutoGenerated = autoGenerated; - } - - // optional - setEventGroups(eventGroups) { - this._validateEventGroups(eventGroups); - this.eventGroups = Array.from(new Set(eventGroups)); - this.eventGroupsAutoGenerated = false; - this._createRenderData(); - this.setScrollToSpecialDayjs(true); - } - - setMinuteStep(minuteStep) { - if (this.config.minuteStep !== minuteStep) { - this._validateMinuteStep(minuteStep); - this.config.minuteStep = minuteStep; - this._createHeaders(); - this._createRenderData(); - } - } - - setBesidesWidth(besidesWidth) { - if (besidesWidth >= 0) { - this.config.besidesWidth = besidesWidth; - } - } - - getMinuteStepsInHour() { - return 60 / this.config.minuteStep; - } - - addResource(resource) { - const existedResources = this.resources.filter(x => x.id === resource.id); - if (existedResources.length === 0) { - this.resources.push(resource); - this._createRenderData(); - } - } - - addEventGroup(eventGroup) { - const existedEventGroups = this.eventGroups.filter(x => x.id === eventGroup.id); - if (existedEventGroups.length === 0) { - this.eventGroups.push(eventGroup); - this._createRenderData(); - } - } - - removeEventGroupById(eventGroupId) { - let index = -1; - this.eventGroups.forEach((item, idx) => { - if (item.id === eventGroupId) index = idx; - }); - if (index !== -1) this.eventGroups.splice(index, 1); - } - - containsEventGroupId(eventGroupId) { - let index = -1; - this.eventGroups.forEach((item, idx) => { - if (item.id === eventGroupId) index = idx; - }); - return index !== -1; - } - - setEvents(events) { - this._validateEvents(events); - this.events = Array.from(events); - if (this.eventGroupsAutoGenerated) this._generateEventGroups(); - if (this.config.recurringEventsEnabled) this._handleRecurringEvents(); - - this._createRenderData(); - } - - setScrollToSpecialDayjs(scrollToSpecialDayjs) { - if (this.config.scrollToSpecialDayjsEnabled) this.scrollToSpecialDayjs = scrollToSpecialDayjs; - } - - prev() { - this._resolveDate(-1); - this.events = []; - this._createHeaders(); - this._createRenderData(); - } - - next() { - this._resolveDate(1); - this.events = []; - this._createHeaders(); - this._createRenderData(); - } - - setDate(date = dayjs(new Date())) { - this._resolveDate(0, date); - this.events = []; - this._createHeaders(); - this._createRenderData(); - } - - setViewType(viewType = ViewType.Week, showAgenda = false, isEventPerspective = false) { - this.showAgenda = showAgenda; - this.isEventPerspective = isEventPerspective; - this.cellUnit = CellUnit.Day; - - if (this.viewType !== viewType || this._shouldReloadViewType) { - let date = this.startDate; - - if (viewType === ViewType.Custom || viewType === ViewType.Custom1 || viewType === ViewType.Custom2) { - this.viewType = viewType; - this._resolveDate(0, date); - } else { - if (this.viewType < viewType) { - if (viewType === ViewType.Week) { - this.startDate = this.localeDayjs(new Date(date)).startOf('week'); - this.endDate = this.localeDayjs(new Date(this.startDate)).endOf('week'); - } else if (viewType === ViewType.Month) { - this.startDate = this.localeDayjs(new Date(date)).startOf('month'); - this.endDate = this.localeDayjs(new Date(this.startDate)).endOf('month'); - } else if (viewType === ViewType.Quarter) { - this.startDate = this.localeDayjs(new Date(date)).startOf('quarter'); - this.endDate = this.localeDayjs(new Date(this.startDate)).endOf('quarter'); - } else if (viewType === ViewType.Year) { - this.startDate = this.localeDayjs(new Date(date)).startOf('year'); - this.endDate = this.localeDayjs(new Date(this.startDate)).endOf('year'); - } - } else { - const start = this.localeDayjs(new Date(this.startDate)); - const end = this.localeDayjs(new Date(this.endDate)).add(1, 'days'); - - if (this.selectDate !== undefined) { - const selectDate = this.localeDayjs(new Date(this.selectDate)); - if (selectDate >= start && selectDate < end) { - date = this.selectDate; - } - } - - const now = this.localeDayjs(); - if (now >= start && now < end) { - date = now.startOf('day'); - } - - if (viewType === ViewType.Day) { - this.startDate = date; - this.endDate = this.startDate; - this.cellUnit = CellUnit.Hour; - } else if (viewType === ViewType.Week) { - this.startDate = this.localeDayjs(new Date(date)).startOf('week'); - this.endDate = this.localeDayjs(new Date(this.startDate)).endOf('week'); - } else if (viewType === ViewType.Month) { - this.startDate = this.localeDayjs(new Date(date)).startOf('month'); - this.endDate = this.localeDayjs(new Date(this.startDate)).endOf('month'); - } else if (viewType === ViewType.Quarter) { - this.startDate = this.localeDayjs(new Date(date)).startOf('quarter'); - this.endDate = this.localeDayjs(new Date(this.startDate)).endOf('quarter'); - } - } - - this.viewType = viewType; - } - - this._shouldReloadViewType = false; - - this.events = []; - this._createHeaders(); - this._createRenderData(); - this.setScrollToSpecialDayjs(true); - } - } - - setSchedulerMaxHeight(newSchedulerMaxHeight) { - this.config.schedulerMaxHeight = newSchedulerMaxHeight; - } - - isSchedulerResponsive() { - return !!this.config.schedulerWidth.endsWith && this.config.schedulerWidth.endsWith('%'); - } - - toggleExpandStatus(slotId) { - let slotEntered = false; - let slotIndent = -1; - let isExpanded = false; - const expandedMap = new Map(); - this.renderData.forEach(item => { - if (slotEntered === false) { - if (item.slotId === slotId && item.hasChildren) { - slotEntered = true; - - isExpanded = !item.expanded; - item.expanded = isExpanded; - slotIndent = item.indent; - expandedMap.set(item.indent, { - expanded: item.expanded, - render: item.render, - }); - } - } else if (item.indent > slotIndent) { - const expandStatus = expandedMap.get(item.indent - 1); - item.render = expandStatus.expanded && expandStatus.render; - - if (item.hasChildren) { - expandedMap.set(item.indent, { - expanded: item.expanded, - render: item.render, - }); - } - } else { - slotEntered = false; - } - }); - } - - isResourceViewResponsive() { - const resourceTableWidth = this.getResourceTableConfigWidth(); - return !!resourceTableWidth.endsWith && resourceTableWidth.endsWith('%'); - } - - isContentViewResponsive() { - const contentCellWidth = this.getContentCellConfigWidth(); - return !!contentCellWidth.endsWith && contentCellWidth.endsWith('%'); - } - - getSchedulerWidth() { - const baseWidth = this.documentWidth - this.config.besidesWidth > 0 ? this.documentWidth - this.config.besidesWidth : 0; - return this.isSchedulerResponsive() ? parseInt((baseWidth * Number(this.config.schedulerWidth.slice(0, -1))) / 100, 10) : this.config.schedulerWidth; - } - - getResourceTableWidth() { - const resourceTableConfigWidth = this.getResourceTableConfigWidth(); - const schedulerWidth = this.getSchedulerWidth(); - let resourceTableWidth = this.isResourceViewResponsive() ? parseInt((schedulerWidth * Number(resourceTableConfigWidth.slice(0, -1))) / 100, 10) : resourceTableConfigWidth; - if (this.isSchedulerResponsive() && this.getContentTableWidth() + resourceTableWidth < schedulerWidth) resourceTableWidth = schedulerWidth - this.getContentTableWidth(); - return resourceTableWidth; - } - - getContentCellWidth() { - const contentCellConfigWidth = this.getContentCellConfigWidth(); - const schedulerWidth = this.getSchedulerWidth(); - return this.isContentViewResponsive() ? parseInt((schedulerWidth * Number(contentCellConfigWidth.slice(0, -1))) / 100, 10) : contentCellConfigWidth; - } - - getContentTableWidth() { - return this.headers.length * this.getContentCellWidth(); - } - - getScrollToSpecialDayjs() { - if (this.config.scrollToSpecialDayjsEnabled) return this.scrollToSpecialDayjs; - return false; - } - - getSlots() { - return this.isEventPerspective ? this.eventGroups : this.resources; - } - - getSlotById(slotId) { - const slots = this.getSlots(); - let slot; - slots.forEach(item => { - if (item.id === slotId) slot = item; - }); - return slot; - } - - getResourceById(resourceId) { - let resource; - this.resources.forEach(item => { - if (item.id === resourceId) resource = item; - }); - return resource; - } - - getTableHeaderHeight() { - return this.config.tableHeaderHeight; - } - - getSchedulerContentDesiredHeight() { - let height = 0; - this.renderData.forEach(item => { - if (item.render) height += item.rowHeight; - }); - return height; - } - - getCellMaxEvents() { - const viewConfigMap = { - [ViewType.Week]: 'weekMaxEvents', - [ViewType.Day]: 'dayMaxEvents', - [ViewType.Month]: 'monthMaxEvents', - [ViewType.Year]: 'yearMaxEvents', - [ViewType.Quarter]: 'quarterMaxEvents', - }; - - const configProperty = viewConfigMap[this.viewType] || 'customMaxEvents'; - - return this.config[configProperty]; - } - - getCalendarPopoverLocale() { - return this.calendarPopoverLocale; - } - - getSelectedDate() { - return this.selectDate.format(DATE_FORMAT); - } - - getViewStartDate() { - return this.startDate; - } - - getViewEndDate() { - return this.endDate; - } - - getViewDates() { - return { - startDate: this.startDate, - endDate: this.endDate, - }; - } - - getDateLabel() { - const start = this.localeDayjs(new Date(this.startDate)); - const end = this.localeDayjs(new Date(this.endDate)); - let dateLabel = start.format('LL'); - - if (start !== end) dateLabel = `${start.format('LL')}-${end.format('LL')}`; - - if (this.behaviors.getDateLabelFunc) dateLabel = this.behaviors.getDateLabelFunc(this, this.viewType, this.startDate, this.endDate); - - return dateLabel; - } - - addEvent(newEvent) { - this._attachEvent(newEvent); - if (this.eventGroupsAutoGenerated) this._generateEventGroups(); - this._createRenderData(); - } - - updateEventStart(event, newStart) { - this._detachEvent(event); - event.start = newStart; - this._attachEvent(event); - this._createRenderData(); - } - - updateEventEnd(event, newEnd) { - event.end = newEnd; - this._createRenderData(); - } - - swapEvent(eventSource, eventDest) { - // Swap group or resource IDs - if (this.isEventPerspective) { - [eventSource.groupId, eventDest.groupId] = [eventDest.groupId, eventSource.groupId]; - [eventSource.groupName, eventDest.groupName] = [eventDest.groupName, eventSource.groupName]; - } else { - [eventSource.resourceId, eventDest.resourceId] = [eventDest.resourceId, eventSource.resourceId]; - } - - // Swap start and end times - [eventSource.start, eventDest.start] = [eventDest.start, eventSource.start]; - [eventSource.end, eventDest.end] = [eventDest.end, eventSource.end]; - - // Update the events - this._detachEvent(eventSource); - this._detachEvent(eventDest); - this._attachEvent(eventSource); - this._attachEvent(eventDest); - this._createRenderData(); - } - - swapEvent2(eventSource, eventDest) { - const tempEventSource = { ...eventSource }; - const tempEventDest = { ...eventDest }; - this._detachEvent(eventSource); - this._detachEvent(eventDest); - if (this.isEventPerspective) { - tempEventSource.groupId = eventDest.groupId; - tempEventSource.groupName = eventDest.groupName; - tempEventDest.groupId = eventSource.groupId; - tempEventDest.groupName = eventSource.groupName; - } else { - tempEventSource.resourceId = eventDest.resourceId; - tempEventDest.resourceId = eventSource.resourceId; - } - tempEventSource.end = eventDest.end; - tempEventSource.start = eventDest.start; - tempEventDest.end = eventSource.end; - tempEventDest.start = eventSource.start; - this._attachEvent(tempEventSource); - this._attachEvent(tempEventDest); - this._createRenderData(); - } - - moveEvent(event, newSlotId, newSlotName, newStart, newEnd) { - this._detachEvent(event); - if (this.isEventPerspective) { - event.groupId = newSlotId; - event.groupName = newSlotName; - } else event.resourceId = newSlotId; - event.end = newEnd; - event.start = newStart; - this._attachEvent(event); - this._createRenderData(); - } - - isEventInTimeWindow(eventStart, eventEnd, windowStart, windowEnd) { - return eventStart < windowEnd && eventEnd > windowStart; - } - - removeEvent(event) { - const index = this.events.indexOf(event); - if (index !== -1) { - this.events.splice(index, 1); - this._createRenderData(); - } - } - - removeEventById(eventId) { - let index = -1; - this.events.forEach((item, idx) => { - if (item.id === eventId) index = idx; - }); - if (index !== -1) { - this.events.splice(index, 1); - this._createRenderData(); - } - } - - getResourceTableConfigWidth() { - if (this.showAgenda) { - return this.config.agendaResourceTableWidth; - } - - const viewConfigMap = { - [ViewType.Week]: 'weekResourceTableWidth', - [ViewType.Day]: 'dayResourceTableWidth', - [ViewType.Month]: 'monthResourceTableWidth', - [ViewType.Year]: 'yearResourceTableWidth', - [ViewType.Quarter]: 'quarterResourceTableWidth', - }; - - const configProperty = viewConfigMap[this.viewType] || 'customResourceTableWidth'; - - return this.config[configProperty]; - } - - getContentCellConfigWidth() { - const viewConfigMap = { - [ViewType.Week]: 'weekCellWidth', - [ViewType.Day]: 'dayCellWidth', - [ViewType.Month]: 'monthCellWidth', - [ViewType.Year]: 'yearCellWidth', - [ViewType.Quarter]: 'quarterCellWidth', - }; - - const configProperty = viewConfigMap[this.viewType] || 'customCellWidth'; - - return this.config[configProperty]; - } - - _setDocumentWidth(documentWidth) { - if (documentWidth >= 0) { - this.documentWidth = documentWidth; - } - } - - _detachEvent(event) { - const index = this.events.indexOf(event); - if (index !== -1) this.events.splice(index, 1); - } - - _attachEvent(event) { - let pos = 0; - const eventStart = this.localeDayjs(new Date(event.start)); - this.events.forEach((item, index) => { - const start = this.localeDayjs(new Date(item.start)); - if (eventStart >= start) pos = index + 1; - }); - this.events.splice(pos, 0, event); - } - - _handleRecurringEvents() { - const recurringEvents = this.events.filter(x => !!x.rrule); - recurringEvents.forEach(item => { - this._detachEvent(item); - }); - - recurringEvents.forEach(item => { - const windowStart = this.startDate; - const windowEnd = this.endDate.add(1, 'days'); - const oldStart = this.localeDayjs(new Date(item.start)); - const oldEnd = this.localeDayjs(new Date(item.end)); - let rule = rrulestr(item.rrule); - let oldDtstart; - const oldUntil = rule.origOptions.until || windowEnd.toDate(); - if (rule.origOptions.dtstart) { - oldDtstart = this.localeDayjs(new Date(rule.origOptions.dtstart)); - } - // rule.origOptions.dtstart = oldStart.toDate(); - if (windowEnd < oldUntil) { - rule.origOptions.until = windowEnd.toDate(); - } - - // reload - rule = rrulestr(rule.toString()); - if (item.exdates || item.exrule) { - const rruleSet = new RRuleSet(); - rruleSet.rrule(rule); - if (item.exrule) { - rruleSet.exrule(rrulestr(item.exrule)); - } - if (item.exdates) { - item.exdates.forEach(exdate => { - rruleSet.exdate(this.localeDayjs(exdate).toDate()); - }); - } - rule = rruleSet; - } - - const all = rule.between(new Date(windowStart), new Date(windowEnd)); - all.forEach((time, index) => { - const newEvent = { - ...item, - recurringEventId: item.id, - recurringEventStart: item.start, - recurringEventEnd: item.end, - id: `${item.id}-${index}`, - start: rule.origOptions.tzid - ? this.localeDayjs.utc(time).utcOffset(this.localeDayjs(new Date().utcOffset)(), true).format(DATETIME_FORMAT) - : this.localeDayjs(new Date(time)).format(DATETIME_FORMAT), - end: rule.origOptions.tzid - ? this.localeDayjs - .utc(time) - .utcOffset(this.localeDayjs(new Date().utcOffset)(), true) - .add(oldEnd.diff(oldStart), 'ms') - .add(this.localeDayjs(new Date(oldUntil)).utcOffset() - this.localeDayjs(new Date(item.start)).utcOffset(), 'm') - .format(DATETIME_FORMAT) - : this.localeDayjs(new Date(time)).add(oldEnd.diff(oldStart), 'ms').format(DATETIME_FORMAT), - }; - - const eventStart = this.localeDayjs(newEvent.start); - const eventEnd = this.localeDayjs(newEvent.end); - if (this.isEventInTimeWindow(eventStart, eventEnd, windowStart, windowEnd) && (!oldDtstart || eventStart >= oldDtstart)) { - this._attachEvent(newEvent); - } - }); - }); - } - - _resolveDate(num, date = undefined) { - if (date !== undefined) { - this.selectDate = this.localeDayjs(date); - } - - const setStartAndEndDates = unit => { - this.startDate = date !== undefined ? this.selectDate.startOf(unit) : this.startDate.add(num, `${unit}s`); - this.endDate = this.startDate.endOf(unit); - }; - - switch (this.viewType) { - case ViewType.Week: - setStartAndEndDates('week'); - break; - - case ViewType.Day: - this.startDate = date !== undefined ? this.selectDate : this.startDate.add(num, 'days'); - this.endDate = this.startDate; - break; - - case ViewType.Month: - setStartAndEndDates('month'); - break; - - case ViewType.Quarter: - setStartAndEndDates('quarter'); - break; - - case ViewType.Year: - setStartAndEndDates('year'); - break; - - case ViewType.Custom: - case ViewType.Custom1: - case ViewType.Custom2: - if (this.behaviors.getCustomDateFunc !== undefined) { - const customDate = this.behaviors.getCustomDateFunc(this, num, date); - this.startDate = this.localeDayjs(customDate.startDate); - this.endDate = this.localeDayjs(customDate.endDate); - if (customDate.cellUnit) { - this.cellUnit = customDate.cellUnit; - } - } else { - throw new Error('This is a custom view type, set behaviors.getCustomDateFunc func to resolve the time window (startDate and endDate) yourself'); - } - break; - - default: - break; - } - } - - // Previous Code - _createHeaders() { - const headers = []; - let start = this.localeDayjs(new Date(this.startDate)); - let end = this.localeDayjs(new Date(this.endDate)); - let header = start; - - if (this.showAgenda) { - headers.push({ time: header.format(DATETIME_FORMAT), nonWorkingTime: false }); - } else if (this.cellUnit === CellUnit.Hour) { - if (start.hour() === 0) { - start = start.add(this.config.dayStartFrom, 'hours'); - } - if (end.hour() === 0) { - end = end.add(this.config.dayStopTo, 'hours'); - } - header = start; - - let prevHour = -1; - while (header >= start && header <= end) { - // prevent doubled hours on time change - if (header.hour() === prevHour) { - header = header.add(1, 'hours'); - // eslint-disable-next-line no-continue - continue; - } - prevHour = header.hour(); - const minuteSteps = this.getMinuteStepsInHour(); - for (let i = 0; i < minuteSteps; i += 1) { - const hour = header.hour(); - if (hour >= this.config.dayStartFrom && hour <= this.config.dayStopTo) { - const time = header.format(DATETIME_FORMAT); - const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc(this, time); - headers.push({ time, nonWorkingTime }); - } - - header = header.add(this.config.minuteStep, 'minutes'); - } - } - } else if (this.cellUnit === CellUnit.Day) { - while (header >= start && header <= end) { - const time = header.format(DATETIME_FORMAT); - const dayOfWeek = header.weekday(); - if (this.config.displayWeekend || (dayOfWeek !== 0 && dayOfWeek !== 6)) { - const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc(this, time); - headers.push({ time, nonWorkingTime }); - } - - header = header.add(1, 'days'); - } - } else if (this.cellUnit === CellUnit.Week) { - while (header >= start && header <= end) { - const time = header.format(DATE_FORMAT); - headers.push({ time }); - header = header.add(1, 'weeks').startOf('week'); - } - } else if (this.cellUnit === CellUnit.Month) { - while (header >= start && header <= end) { - const time = header.format(DATE_FORMAT); - headers.push({ time }); - header = header.add(1, 'months').startOf('month'); - } - } else if (this.cellUnit === CellUnit.Year) { - while (header >= start && header <= end) { - const time = header.format(DATE_FORMAT); - headers.push({ time }); - header = header.add(1, 'years').startOf('year'); - } - } - - this.headers = headers; - } - - // Fix Optimited code - // _createHeaders() { - // const headers = []; - // const start = this.localeDayjs(new Date(this.startDate)); - // const end = this.localeDayjs(new Date(this.endDate)); - - // const processHeader = (header, format, unit, incrementFn) => { - // let head = header; - // while (head >= start && head <= end) { - // const time = head.format(format); - // if (unit === CellUnit.Day) { - // const dayOfWeek = head.weekday(); - // if (this.config.displayWeekend || (dayOfWeek !== 0 && dayOfWeek !== 6)) { - // const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc(this, time); - // headers.push({ time, nonWorkingTime }); - // } - // } else { - // headers.push({ time }); - // } - // head = head.add(1, incrementFn); - // } - // }; - - // if (this.showAgenda) { - // headers.push({ time: start.format(DATETIME_FORMAT), nonWorkingTime: false }); - // } else if (this.cellUnit === CellUnit.Hour) { - // const hourIncrement = this.config.minuteStep < 60 ? 'minutes' : 'hours'; - // const minuteSteps = this.getMinuteStepsInHour(); - // let header = start.hour() === 0 ? start.add(this.config.dayStartFrom, 'hours') : start; - // while (header <= end) { - // const hour = header.hour(); - // if (hour >= this.config.dayStartFrom && hour <= this.config.dayStopTo) { - // const time = header.format(DATETIME_FORMAT); - // const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc(this, time); - // headers.push({ time, nonWorkingTime }); - // } - // header = header.add(minuteSteps, hourIncrement); - // } - // } else { - // const header = start; - // const format = this.cellUnit === CellUnit.Day ? DATETIME_FORMAT : DATE_FORMAT; - // const incrementFn = this.cellUnit === CellUnit.Day ? 'days' : `${this.cellUnit}s`; - // processHeader(header, format, this.cellUnit, incrementFn); - // } - - // this.headers = headers; - // } - - _createInitHeaderEvents(header) { - const start = this.localeDayjs(new Date(header.time)); - const startValue = start.format(DATETIME_FORMAT); - - let endValue; - if (this.showAgenda) { - const incrementUnit = { - [ViewType.Day]: 'days', - [ViewType.Week]: 'weeks', - [ViewType.Month]: 'months', - [ViewType.Year]: 'years', - [ViewType.Quarter]: 'quarters', - }[this.viewType] || 'days'; - - if (incrementUnit === 'days') { - endValue = this.localeDayjs(new Date(this.endDate)).add(1, 'days').format(DATETIME_FORMAT); - } else { - endValue = start.add(1, incrementUnit).format(DATETIME_FORMAT); - } - } else { - const incrementUnit = { - [CellUnit.Hour]: 'minutes', - [CellUnit.Week]: 'weeks', - [CellUnit.Month]: 'months', - [CellUnit.Year]: 'years', - }[this.cellUnit] || 'days'; - - endValue = start - .add(incrementUnit === 'minutes' ? this.config.minuteStep : 1, incrementUnit) - .format(this.cellUnit === CellUnit.Year || this.cellUnit === CellUnit.Month || this.cellUnit === CellUnit.Week ? DATE_FORMAT : DATETIME_FORMAT); - } - - return { - time: header.time, - nonWorkingTime: header.nonWorkingTime, - start: startValue, - end: endValue, - count: 0, - addMore: 0, - addMoreIndex: 0, - events: Array(3), - }; - } - - _createHeaderEvent(render, span, eventItem) { - return { render, span, eventItem }; - } - - _getEventSlotId(event) { - return this.isEventPerspective ? this._getEventGroupId(event) : event.resourceId; - } - - _getEventGroupId(event) { - return event.groupId ? event.groupId.toString() : event.id.toString(); - } - - _getEventGroupName(event) { - return event.groupName ? event.groupName : event.title; - } - - _generateEventGroups() { - const eventGroups = []; - const set = new Set(); - this.events.forEach(item => { - const groupId = this._getEventGroupId(item); - const groupName = this._getEventGroupName(item); - - if (!set.has(groupId)) { - eventGroups.push({ - id: groupId, - name: groupName, - state: item, - }); - set.add(groupId); - } - }); - this.eventGroups = eventGroups; - } - - _createInitRenderData(isEventPerspective, eventGroups, resources, headers) { - const slots = isEventPerspective ? eventGroups : resources; - const slotTree = []; - const slotMap = new Map(); - slots.forEach(slot => { - const headerEvents = headers.map(header => this._createInitHeaderEvents(header)); - - const slotRenderData = { - slotId: slot.id, - slotName: slot.name, - slotTitle: slot.title, - parentId: slot.parentId, - groupOnly: slot.groupOnly, - hasSummary: false, - rowMaxCount: 0, - rowHeight: this.config.nonAgendaSlotMinHeight !== 0 ? this.config.nonAgendaSlotMinHeight : this.config.eventItemLineHeight + 2, - headerItems: headerEvents, - indent: 0, - hasChildren: false, - expanded: true, - render: true, - }; - const { id } = slot; - let value; - if (slotMap.has(id)) { - value = slotMap.get(id); - value.data = slotRenderData; - } else { - value = { - data: slotRenderData, - children: [], - }; - slotMap.set(id, value); - } - - const { parentId } = slot; - if (!parentId || parentId === id) { - slotTree.push(value); - } else { - let parentValue; - if (slotMap.has(parentId)) { - parentValue = slotMap.get(parentId); - } else { - parentValue = { - data: undefined, - children: [], - }; - slotMap.set(parentId, parentValue); - } - - parentValue.children.push(value); - } - }); - - const slotStack = []; - let i; - for (i = slotTree.length - 1; i >= 0; i -= 1) { - slotStack.push(slotTree[i]); - } - const initRenderData = []; - let currentNode; - while (slotStack.length > 0) { - currentNode = slotStack.pop(); - if (currentNode.data.indent > 0) { - currentNode.data.render = this.config.defaultExpanded; - } - if (currentNode.children.length > 0) { - currentNode.data.hasChildren = true; - currentNode.data.expanded = this.config.defaultExpanded; - } - initRenderData.push(currentNode.data); - - for (i = currentNode.children.length - 1; i >= 0; i -= 1) { - currentNode.children[i].data.indent = currentNode.data.indent + 1; - slotStack.push(currentNode.children[i]); - } - } - - return initRenderData; - } - - _getSpan(startTime, endTime, headers) { - if (this.showAgenda) return 1; - - // function startOfWeek(date) { - // const day = date.getDay(); - // const diff = date.getDate() - day; - // return new Date(date.getFullYear(), date.getMonth(), diff); - // } - - const timeBetween = (date1, date2, timeIn) => { - if (timeIn === 'days' || timeIn === 'day') { - if (date1.getDate() === date2.getDate() && date1.getMonth() === date2.getMonth()) { - return 1; - } - } - - let one; - switch (timeIn) { - case 'days': - case 'day': - one = 1000 * 60 * 60 * 24; - break; - case 'minutes': - case 'minute': - one = 1000 * 60; - break; - default: - return 0; - } - - const date1Ms = date1.getTime(); - const date2Ms = date2.getTime(); - - const diff = (date2Ms - date1Ms) / one; - return diff < 0 ? 0 : diff; - }; - - const eventStart = new Date(startTime); - const eventEnd = new Date(endTime); - let span = 0; - const windowStart = new Date(this.startDate); - const windowEnd = new Date(this.endDate); - - windowStart.setHours(0, 0, 0, 0); - windowEnd.setHours(23, 59, 59); - - if (this.viewType === ViewType.Day) { - if (headers.length > 0) { - const day = new Date(headers[0].time); - if (day.getDate() > eventStart.getDate() && day.getDate() < eventEnd.getDate()) { - span = 1440 / this.config.minuteStep; - } else if (day.getDate() > eventStart.getDate() && day.getDate() === eventEnd.getDate()) { - span = Math.ceil(timeBetween(day, eventEnd, 'minutes') / this.config.minuteStep); - } else if (day.getDate() === eventStart.getDate() && day.getDate() < eventEnd.getDate()) { - day.setHours(23, 59, 59); - span = Math.ceil(timeBetween(eventStart, day, 'minutes') / this.config.minuteStep); - } else if ((day.getDate() === eventStart.getDate() && day.getDate() === eventEnd.getDate()) || eventEnd.getDate() === eventStart.getDate()) { - span = Math.ceil(timeBetween(eventStart, eventEnd, 'minutes') / this.config.minuteStep); - } - } - } else if (this.viewType === ViewType.Week || this.viewType === ViewType.Month || this.viewType === ViewType.Quarter || this.viewType === ViewType.Year) { - const startDate = windowStart < eventStart ? eventStart : windowStart; - const endDate = windowEnd > eventEnd ? eventEnd : windowEnd; - span = Math.ceil(timeBetween(startDate, endDate, 'days')); - } else { - if (this.cellUnit === CellUnit.Day) { - eventEnd.setHours(23, 59, 59); - eventStart.setHours(0, 0, 0, 0); - } - - const timeIn = this.cellUnit === CellUnit.Day ? 'days' : 'minutes'; - const dividedBy = this.cellUnit === CellUnit.Day ? 1 : this.config.minuteStep; - - if (windowStart >= eventStart && eventEnd <= windowEnd) { - span = Math.ceil(timeBetween(windowStart, eventEnd, timeIn) / dividedBy); - } else if (windowStart > eventStart && eventEnd > windowEnd) { - span = Math.ceil(timeBetween(windowStart, windowEnd, timeIn) / dividedBy); - } else if (windowStart <= eventStart && eventEnd >= windowEnd) { - span = Math.ceil(timeBetween(eventStart, windowEnd, timeIn) / dividedBy); - } else { - span = Math.ceil(timeBetween(eventStart, eventEnd, timeIn) / dividedBy); - } - } - - return span; - } - - _validateResource(resources) { - if (Object.prototype.toString.call(resources) !== '[object Array]') { - throw new Error('Resources should be Array object'); - } - - resources.forEach((item, index) => { - if (item === undefined) { - console.error(`Resource undefined: ${index}`); - throw new Error(`Resource undefined: ${index}`); - } - if (item.id === undefined || item.name === undefined) { - console.error('Resource property missed', index, item); - throw new Error(`Resource property undefined: ${index}`); - } - }); - } - - _validateEventGroups(eventGroups) { - if (Object.prototype.toString.call(eventGroups) !== '[object Array]') { - throw new Error('Event groups should be Array object'); - } - - eventGroups.forEach((item, index) => { - if (item === undefined) { - console.error(`Event group undefined: ${index}`); - throw new Error(`Event group undefined: ${index}`); - } - if (item.id === undefined || item.name === undefined) { - console.error('Event group property missed', index, item); - throw new Error(`Event group property undefined: ${index}`); - } - }); - } - - _validateEvents(events) { - if (Object.prototype.toString.call(events) !== '[object Array]') { - throw new Error('Events should be Array object'); - } - - events.forEach((e, index) => { - if (e === undefined) { - console.error(`Event undefined: ${index}`); - throw new Error(`Event undefined: ${index}`); - } - if (e.id === undefined || e.resourceId === undefined || e.title === undefined || e.start === undefined || e.end === undefined) { - console.error('Event property missed', index, e); - throw new Error(`Event property undefined: ${index}`); - } - }); - } - - _validateMinuteStep(minuteStep) { - if (60 % minuteStep !== 0) { - console.error('Minute step is not set properly - 60 minutes must be divisible without remainder by this number'); - throw new Error('Minute step is not set properly - 60 minutes must be divisible without remainder by this number'); - } - } - - _compare(event1, event2) { - const start1 = this.localeDayjs(event1.start); - const start2 = this.localeDayjs(event2.start); - if (start1 !== start2) return start1 < start2 ? -1 : 1; - - const end1 = this.localeDayjs(event1.end); - const end2 = this.localeDayjs(event2.end); - if (end1 !== end2) return end1 < end2 ? -1 : 1; - - return event1.id < event2.id ? -1 : 1; - } - - _createRenderData() { - const initRenderData = this._createInitRenderData(this.isEventPerspective, this.eventGroups, this.resources, this.headers); - // this.events.sort(this._compare); - const cellMaxEventsCount = this.getCellMaxEvents(); - const cellMaxEventsCountValue = 30; - - this.events.forEach(item => { - const resourceEventsList = initRenderData.filter(x => x.slotId === this._getEventSlotId(item)); - if (resourceEventsList.length > 0) { - const resourceEvents = resourceEventsList[0]; - const span = this._getSpan(item.start, item.end, this.headers); - const eventStart = new Date(item.start); - const eventEnd = new Date(item.end); - let pos = -1; - - resourceEvents.headerItems.forEach((header, index) => { - const headerStart = new Date(header.start); - const headerEnd = new Date(header.end); - if (headerEnd > eventStart && headerStart < eventEnd) { - header.count += 1; - if (header.count > resourceEvents.rowMaxCount) { - resourceEvents.rowMaxCount = header.count; - const rowsCount = cellMaxEventsCount <= cellMaxEventsCountValue && resourceEvents.rowMaxCount > cellMaxEventsCount ? cellMaxEventsCount : resourceEvents.rowMaxCount; - const newRowHeight = rowsCount * this.config.eventItemLineHeight + (this.config.creatable && this.config.checkConflict === false ? 20 : 2); - if (newRowHeight > resourceEvents.rowHeight) resourceEvents.rowHeight = newRowHeight; - } - - if (pos === -1) { - let tmp = 0; - while (header.events[tmp] !== undefined) tmp += 1; - - pos = tmp; - } - let render = headerStart <= eventStart || index === 0; - if (render === false) { - const previousHeader = resourceEvents.headerItems[index - 1]; - const previousHeaderStart = new Date(previousHeader.start); - const previousHeaderEnd = new Date(previousHeader.end); - if (previousHeaderEnd <= eventStart || previousHeaderStart >= eventEnd) render = true; - } - // console.log(`span: ${span}`) - header.events[pos] = this._createHeaderEvent(render, span, item); - } - }); - } - }); - - if (cellMaxEventsCount <= cellMaxEventsCountValue || this.behaviors.getSummaryFunc !== undefined) { - initRenderData.forEach(resourceEvents => { - let hasSummary = false; - - resourceEvents.headerItems.forEach(headerItem => { - if (cellMaxEventsCount <= cellMaxEventsCountValue) { - let renderItemsCount = 0; - let addMoreIndex = 0; - let index = 0; - while (index < cellMaxEventsCount - 1) { - if (headerItem.events[index] !== undefined) { - renderItemsCount += 1; - addMoreIndex = index + 1; - } - - index += 1; - } - - if (headerItem.events[index] !== undefined) { - if (renderItemsCount + 1 < headerItem.count) { - headerItem.addMore = headerItem.count - renderItemsCount; - headerItem.addMoreIndex = addMoreIndex; - } - } else if (renderItemsCount < headerItem.count) { - headerItem.addMore = headerItem.count - renderItemsCount; - headerItem.addMoreIndex = addMoreIndex; - } - } - - if (this.behaviors.getSummaryFunc !== undefined) { - const events = []; - headerItem.events.forEach(e => { - if (!!e && !!e.eventItem) events.push(e.eventItem); - }); - - headerItem.summary = this.behaviors.getSummaryFunc(this, events, resourceEvents.slotId, resourceEvents.slotName, headerItem.start, headerItem.end); - if (!!headerItem.summary && headerItem.summary.text !== undefined) hasSummary = true; - } - }); - - resourceEvents.hasSummary = hasSummary; - if (hasSummary) { - const rowsCount = cellMaxEventsCount <= cellMaxEventsCountValue && resourceEvents.rowMaxCount > cellMaxEventsCount ? cellMaxEventsCount : resourceEvents.rowMaxCount; - const newRowHeight = (rowsCount + 1) * this.config.eventItemLineHeight + (this.config.creatable && this.config.checkConflict === false ? 20 : 2); - if (newRowHeight > resourceEvents.rowHeight) resourceEvents.rowHeight = newRowHeight; - } - }); - } - - this.renderData = initRenderData; - } - - _startResizing() { - this.resizing = true; - } - - _stopResizing() { - this.resizing = false; - } - - _isResizing() { - return this.resizing; - } -} diff --git a/src/components/SchedulerData.ts b/src/components/SchedulerData.ts new file mode 100644 index 0000000..78196b7 --- /dev/null +++ b/src/components/SchedulerData.ts @@ -0,0 +1,1581 @@ +import dayjs, { ManipulateType } from "dayjs"; +import { Dayjs } from "dayjs"; + +import quarterOfYear from "dayjs/plugin/quarterOfYear"; +import utc from "dayjs/plugin/utc"; +import weekday from "dayjs/plugin/weekday"; +import { RRuleSet, rrulestr } from "rrule"; +import { config } from "../config/scheduler"; +import behaviors, { Behaviors } from "../helper/behaviors"; +import { + ViewType, + CellUnit, + DATE_FORMAT, + DATETIME_FORMAT, +} from "../config/default"; +import { + EventGroup, + SchedulerDataConfig, + Resource, + HeaderEvent, + HeadersType, + HeaderEventsType, + RenderDataItem, + SlotMapItem, + HeaderItem, + EventItemType, + SchedulerDataConfigOptional, +} from "../types/baseType"; + +export class SchedulerData { + // localeDayjs(date?: ConfigType): Dayjs; + // localeDayjs(date?: ConfigType, format?: OptionType, strict?: boolean): Dayjs; + // localeDayjs(date?: ConfigType, format?: OptionType, locale?: string, strict?: boolean): Dayjs; + + cellUnit: CellUnit; + viewType: ViewType; + config: SchedulerDataConfig; + resources: Resource[]; + events: EventItemType[]; + eventGroups: EventGroup[]; + eventGroupsAutoGenerated: boolean; + showAgenda: boolean; + isEventPerspective: boolean; + resizing: boolean; + scrollToSpecialDayjs: boolean; + documentWidth: number; + behaviors: Behaviors; + _shouldReloadViewType: boolean; + calendarPopoverLocale: string | undefined; + localeDayjs: typeof dayjs; + // startDate?: string | number | Date; + startDate: Dayjs = dayjs(); + endDate: Dayjs = dayjs(); + selectDate?: Dayjs; + headers: HeadersType[] = []; + renderData: RenderDataItem[] = []; + + constructor( + date: Dayjs = dayjs(), + viewType: ViewType = ViewType.Week, + showAgenda: boolean = false, + isEventPerspective: boolean = false, + newConfig?: SchedulerDataConfigOptional, + newBehaviors?: Behaviors + ) { + this.resources = []; + this.events = []; + this.eventGroups = []; + this.eventGroupsAutoGenerated = true; + this.viewType = viewType; + this.cellUnit = viewType === ViewType.Day ? CellUnit.Hour : CellUnit.Day; + this.showAgenda = showAgenda; + this.isEventPerspective = isEventPerspective; + this.resizing = false; + this.scrollToSpecialDayjs = false; + this.documentWidth = 0; + this._shouldReloadViewType = false; + + this.calendarPopoverLocale = undefined; + dayjs.extend(quarterOfYear); + dayjs.extend(weekday); + dayjs.extend(utc); + this.localeDayjs = dayjs; + this.config = + newConfig === undefined ? config : { ...config, ...newConfig }; + this._validateMinuteStep(this.config.minuteStep); + this.behaviors = + newBehaviors === undefined + ? behaviors + : { ...behaviors, ...newBehaviors }; + this._resolveDate(0, date); + this._createHeaders(); + this._createRenderData(); + } + + setSchedulerLocale(preset: string | ILocale) { + if (!preset) return; + + this.localeDayjs.locale(preset); + this._shouldReloadViewType = true; + this.setViewType(this.viewType, this.showAgenda, this.isEventPerspective); + } + + setCalendarPopoverLocale(lang: string) { + if (lang) { + this.calendarPopoverLocale = lang; + } + } + + setResources(resources: Resource[]) { + this._validateResource(resources); + this.resources = Array.from(new Set(resources)); + this._createRenderData(); + this.setScrollToSpecialDayjs(true); + } + + setEventGroupsAutoGenerated(autoGenerated: boolean) { + this.eventGroupsAutoGenerated = autoGenerated; + } + + // optional + setEventGroups(eventGroups: EventGroup[]) { + this._validateEventGroups(eventGroups); + + this.eventGroups = Array.from(new Set(eventGroups)); + console.log("eventGroup _generateEventGroups", this.eventGroups); + + this.eventGroupsAutoGenerated = false; + this._createRenderData(); + this.setScrollToSpecialDayjs(true); + } + + setMinuteStep(minuteStep: number) { + if (this.config.minuteStep !== minuteStep) { + this._validateMinuteStep(minuteStep); + this.config.minuteStep = minuteStep; + this._createHeaders(); + this._createRenderData(); + } + } + + setBesidesWidth(besidesWidth: number) { + if (besidesWidth >= 0) { + this.config.besidesWidth = besidesWidth; + } + } + + getMinuteStepsInHour() { + return 60 / this.config.minuteStep; + } + + addResource(resource: Resource) { + const existedResources = this.resources.filter((x) => x.id === resource.id); + if (existedResources.length === 0) { + this.resources.push(resource); + this._createRenderData(); + } + } + + addEventGroup(eventGroup: EventGroup) { + const existedEventGroups = this.eventGroups.filter( + (x) => x.id === eventGroup.id + ); + if (existedEventGroups.length === 0) { + this.eventGroups.push(eventGroup); + this._createRenderData(); + } + } + + removeEventGroupById(eventGroupId: string) { + let index = -1; + this.eventGroups.forEach((item, idx) => { + if (item.id === eventGroupId) index = idx; + }); + if (index !== -1) this.eventGroups.splice(index, 1); + } + + containsEventGroupId(eventGroupId: string) { + let index = -1; + this.eventGroups.forEach((item, idx) => { + if (item.id === eventGroupId) index = idx; + }); + return index !== -1; + } + + setEvents(events: EventItemType[]) { + console.log("events", events); + this._validateEvents(events); + this.events = Array.from(events); + if (this.eventGroupsAutoGenerated) this._generateEventGroups(); + if (this.config.recurringEventsEnabled) this._handleRecurringEvents(); + + this._createRenderData(); + } + + setScrollToSpecialDayjs(scrollToSpecialDayjs: boolean) { + if (this.config.scrollToSpecialDayjsEnabled) + this.scrollToSpecialDayjs = scrollToSpecialDayjs; + } + + prev() { + this._resolveDate(-1); + this.events = []; + this._createHeaders(); + this._createRenderData(); + } + + next() { + this._resolveDate(1); + this.events = []; + this._createHeaders(); + this._createRenderData(); + } + + setDate(date: Dayjs = dayjs(new Date())) { + this._resolveDate(0, date); + this.events = []; + this._createHeaders(); + this._createRenderData(); + } + + setViewType( + viewType = ViewType.Week, + showAgenda = false, + isEventPerspective = false + ) { + this.showAgenda = showAgenda; + this.isEventPerspective = isEventPerspective; + this.cellUnit = CellUnit.Day; + + if (this.viewType !== viewType || this._shouldReloadViewType) { + let date = this.startDate.clone(); + + if ( + viewType === ViewType.Custom || + viewType === ViewType.Custom1 || + viewType === ViewType.Custom2 + ) { + this.viewType = viewType; + this._resolveDate(0, date); + } else { + if (this.viewType < viewType) { + if (viewType === ViewType.Week) { + this.startDate = date.startOf("week"); + this.endDate = this.startDate.endOf("week"); + } else if (viewType === ViewType.Month) { + this.startDate = date.startOf("month"); + this.endDate = this.startDate.endOf("month"); + } else if (viewType === ViewType.Quarter) { + this.startDate = date.startOf("quarter"); + this.endDate = this.startDate.endOf("quarter"); + } else if (viewType === ViewType.Year) { + this.startDate = date.startOf("year"); + this.endDate = this.startDate.endOf("year"); + } + } else { + const start = this.startDate; + const end = this.endDate.add(1, "days"); + + if (this.selectDate !== undefined) { + const selectDate = this.selectDate.clone(); + if ( + selectDate.isAfter(start) || + (selectDate.isSame(start) && selectDate.isBefore(end)) + ) { + date = this.selectDate; + } + } + + const now = this.localeDayjs(); + if (now >= start && now < end) { + date = now.startOf("day"); + } + + if (viewType === ViewType.Day) { + this.startDate = date; + this.endDate = this.startDate; + this.cellUnit = CellUnit.Hour; + } else if (viewType === ViewType.Week) { + this.startDate = date.startOf("week"); + this.endDate = this.startDate.endOf("week"); + } else if (viewType === ViewType.Month) { + this.startDate = date.startOf("month"); + this.endDate = this.startDate.endOf("month"); + } else if (viewType === ViewType.Quarter) { + this.startDate = date.startOf("quarter"); + this.endDate = this.startDate.endOf("quarter"); + } + } + + this.viewType = viewType; + } + + this._shouldReloadViewType = false; + + this.events = []; + this._createHeaders(); + this._createRenderData(); + this.setScrollToSpecialDayjs(true); + } + } + + setSchedulerMaxHeight(newSchedulerMaxHeight: number) { + this.config.schedulerMaxHeight = newSchedulerMaxHeight; + } + + isSchedulerResponsive() { + return ( + !!this.config.schedulerWidth.endsWith && + this.config.schedulerWidth.endsWith("%") + ); + } + + toggleExpandStatus(slotId: string) { + let slotEntered = false; + let slotIndent = -1; + let isExpanded = false; + const expandedMap = new Map(); + this.renderData.forEach((item) => { + if (slotEntered === false) { + if (item.slotId === slotId && item.hasChildren) { + slotEntered = true; + + isExpanded = !item.expanded; + item.expanded = isExpanded; + slotIndent = item.indent; + expandedMap.set(item.indent, { + expanded: item.expanded, + render: item.render, + }); + } + } else if (item.indent > slotIndent) { + const expandStatus = expandedMap.get(item.indent - 1); + item.render = expandStatus.expanded && expandStatus.render; + + if (item.hasChildren) { + expandedMap.set(item.indent, { + expanded: item.expanded, + render: item.render, + }); + } + } else { + slotEntered = false; + } + }); + } + + isResourceViewResponsive() { + const resourceTableWidth = this.getResourceTableConfigWidth(); + return ( + typeof resourceTableWidth === "string" && resourceTableWidth.endsWith("%") + ); + } + + isContentViewResponsive() { + const contentCellWidth = this.getContentCellConfigWidth(); + return ( + typeof contentCellWidth === "string" && contentCellWidth.endsWith("%") + ); + } + + getSchedulerWidth() { + const baseWidth = + this.documentWidth - this.config.besidesWidth > 0 + ? this.documentWidth - this.config.besidesWidth + : 0; + + if (this.isSchedulerResponsive()) { + const value = + (baseWidth * Number(this.config.schedulerWidth.slice(0, -1))) / 100; + // TODO: this string could be removed + return parseInt(String(value), 10); + } else { + + return Number(this.config.schedulerWidth); + } + } + + getResourceTableWidth() { + const resourceTableConfigWidth = this.getResourceTableConfigWidth(); + + const schedulerWidth = this.getSchedulerWidth(); + + if ( + this.config.fixedResourceTableWidth && + !this.isResourceViewResponsive() + ) { + return parseInt("" + resourceTableConfigWidth); + } + + let resourceTableWidth: number = (() => { + if (this.isResourceViewResponsive()) { + const value = + (schedulerWidth * + Number(String(resourceTableConfigWidth).slice(0, -1))) / + 100; + + // TODO: this string could be removed + return parseInt(String(value), 10); + } else { + return Number(resourceTableConfigWidth); + } + })(); + + if ( + this.isSchedulerResponsive() && + this.getContentTableWidth() + resourceTableWidth < schedulerWidth + ) + resourceTableWidth = schedulerWidth - this.getContentTableWidth(); + + return resourceTableWidth; + } + + getContentCellWidth() { + const contentCellConfigWidth = this.getContentCellConfigWidth(); + const schedulerWidth = this.getSchedulerWidth(); + + if (this.isContentViewResponsive()) { + const value = + (schedulerWidth * Number(String(contentCellConfigWidth).slice(0, -1))) / + 100; + // TODO: this string could be removed + return parseInt(String(value), 10); + } else { + return Number(contentCellConfigWidth); + } + } + + getContentTableWidth() { + return this.headers.length * this.getContentCellWidth(); + } + + getScrollToSpecialDayjs() { + if (this.config.scrollToSpecialDayjsEnabled) + return this.scrollToSpecialDayjs; + return false; + } + + getSlots() { + return this.isEventPerspective ? this.eventGroups : this.resources; + } + + getSlotById(slotId: string) { + const slots = this.getSlots(); + let slot: Resource | EventGroup | undefined; + slots.forEach((item) => { + if (item.id === slotId) slot = item; + }); + return slot; + } + + getResourceById(resourceId: string) { + let resource; + this.resources.forEach((item) => { + if (item.id === resourceId) resource = item; + }); + return resource; + } + + getTableHeaderHeight() { + return this.config.tableHeaderHeight; + } + + getSchedulerContentDesiredHeight() { + let height = 0; + this.renderData.forEach((item) => { + if (item.render) height += item.rowHeight; + }); + return height; + } + + getCellMaxEvents() { + const viewConfigMap = { + [ViewType.Week]: "weekMaxEvents", + [ViewType.Day]: "dayMaxEvents", + [ViewType.Month]: "monthMaxEvents", + [ViewType.Year]: "yearMaxEvents", + [ViewType.Quarter]: "quarterMaxEvents", + [ViewType.Custom]: "customMaxEvents", + [ViewType.Custom1]: "customMaxEvents", + [ViewType.Custom2]: "customMaxEvents", + } as const; + + const configProperty = viewConfigMap[this.viewType] || "customMaxEvents"; + return this.config[configProperty]; + } + + getCalendarPopoverLocale() { + return this.calendarPopoverLocale; + } + + getSelectedDate() { + return this.selectDate?.format(DATE_FORMAT) || ""; + } + + getViewStartDate() { + return this.startDate; + } + + getViewEndDate() { + return this.endDate; + } + + getViewDates() { + return { + startDate: this.startDate, + endDate: this.endDate, + }; + } + + getDateLabel() { + const start = this.startDate.clone(); + const end = this.endDate.clone(); + let dateLabel = start.format("LL"); + + if (start !== end) dateLabel = `${start.format("LL")}-${end.format("LL")}`; + + if (this.behaviors.getDateLabelFunc) + dateLabel = this.behaviors.getDateLabelFunc( + this, + this.viewType, + this.startDate, + this.endDate + ); + + return dateLabel; + } + + addEvent(newEvent: EventItemType) { + this._attachEvent(newEvent); + if (this.eventGroupsAutoGenerated) this._generateEventGroups(); + this._createRenderData(); + } + + updateEventStart(event: EventItemType, newStart: Dayjs) { + this._detachEvent(event); + event.start = newStart; + this._attachEvent(event); + this._createRenderData(); + } + + updateEventEnd(event: EventItemType, newEnd: Dayjs) { + event.end = newEnd; + this._createRenderData(); + } + + swapEvent(eventSource: EventItemType, eventDest: EventItemType) { + // Swap group or resource IDs + if (this.isEventPerspective) { + [eventSource.groupId, eventDest.groupId] = [ + eventDest.groupId, + eventSource.groupId, + ]; + [eventSource.groupName, eventDest.groupName] = [ + eventDest.groupName, + eventSource.groupName, + ]; + } else { + [eventSource.resourceId, eventDest.resourceId] = [ + eventDest.resourceId, + eventSource.resourceId, + ]; + } + + // Swap start and end times + [eventSource.start, eventDest.start] = [eventDest.start, eventSource.start]; + [eventSource.end, eventDest.end] = [eventDest.end, eventSource.end]; + + // Update the events + this._detachEvent(eventSource); + this._detachEvent(eventDest); + this._attachEvent(eventSource); + this._attachEvent(eventDest); + this._createRenderData(); + } + + swapEvent2(eventSource: EventItemType, eventDest: EventItemType) { + const tempEventSource = { ...eventSource }; + const tempEventDest = { ...eventDest }; + this._detachEvent(eventSource); + this._detachEvent(eventDest); + if (this.isEventPerspective) { + tempEventSource.groupId = eventDest.groupId; + tempEventSource.groupName = eventDest.groupName; + tempEventDest.groupId = eventSource.groupId; + tempEventDest.groupName = eventSource.groupName; + } else { + tempEventSource.resourceId = eventDest.resourceId; + tempEventDest.resourceId = eventSource.resourceId; + } + tempEventSource.end = eventDest.end; + tempEventSource.start = eventDest.start; + tempEventDest.end = eventSource.end; + tempEventDest.start = eventSource.start; + this._attachEvent(tempEventSource); + this._attachEvent(tempEventDest); + this._createRenderData(); + } + + moveEvent( + event: EventItemType, + newSlotId: string, + newSlotName: string, + newStart: Dayjs, + newEnd: Dayjs + ) { + this._detachEvent(event); + if (this.isEventPerspective) { + event.groupId = newSlotId; + event.groupName = newSlotName; + } else event.resourceId = newSlotId; + event.end = newEnd; + event.start = newStart; + this._attachEvent(event); + this._createRenderData(); + } + + isEventInTimeWindow( + eventStart: Dayjs, + eventEnd: Dayjs, + windowStart: Dayjs, + windowEnd: Dayjs + ) { + return eventStart.isBefore(windowEnd) && eventEnd.isAfter(windowStart); + } + + removeEvent(event: EventItemType) { + const index = this.events.indexOf(event); + if (index !== -1) { + this.events.splice(index, 1); + this._createRenderData(); + } + } + + removeEventById(eventId: string) { + let index = -1; + this.events.forEach((item, idx) => { + if (item.id === eventId) index = idx; + }); + if (index !== -1) { + this.events.splice(index, 1); + this._createRenderData(); + } + } + + getResourceTableConfigWidth() { + if (this.showAgenda) { + return this.config.agendaResourceTableWidth; + } + + const viewConfigMap = { + [ViewType.Week]: "weekResourceTableWidth", + [ViewType.Day]: "dayResourceTableWidth", + [ViewType.Month]: "monthResourceTableWidth", + [ViewType.Year]: "yearResourceTableWidth", + [ViewType.Quarter]: "quarterResourceTableWidth", + [ViewType.Custom]: "customResourceTableWidth", + [ViewType.Custom1]: "customResourceTableWidth", + [ViewType.Custom2]: "customResourceTableWidth", + } as const; + + const configProperty = + viewConfigMap[this.viewType] || "customResourceTableWidth"; + + return this.config[configProperty]; + } + + getContentCellConfigWidth() { + const viewConfigMap = { + [ViewType.Week]: "weekCellWidth", + [ViewType.Day]: "dayCellWidth", + [ViewType.Month]: "monthCellWidth", + [ViewType.Year]: "yearCellWidth", + [ViewType.Quarter]: "quarterCellWidth", + [ViewType.Custom]: "customCellWidth", + [ViewType.Custom1]: "customCellWidth", + [ViewType.Custom2]: "customCellWidth", + } as const; + + const configProperty = viewConfigMap[this.viewType] || "customCellWidth"; + + return this.config[configProperty]; + } + + _setDocumentWidth(documentWidth: number) { + if (documentWidth >= 0) { + this.documentWidth = documentWidth; + } + } + + _detachEvent(event: EventItemType) { + const index = this.events.indexOf(event); + if (index !== -1) this.events.splice(index, 1); + } + + _attachEvent(event: EventItemType) { + let pos = 0; + const eventStart = event.start; + this.events.forEach((item, index) => { + const start = item.start; + if (eventStart >= start) pos = index + 1; + }); + this.events.splice(pos, 0, event); + } + + _handleRecurringEvents() { + const recurringEvents = this.events.filter((x) => !!x.rrule); + recurringEvents.forEach((item) => { + this._detachEvent(item); + }); + + recurringEvents.forEach((item) => { + const windowStart = this.startDate; + const windowStartDate = windowStart.toDate(); + const windowEnd = this.endDate.add(1, "days"); + const windowEndDate = windowEnd.toDate(); + const oldStart = item.start; + const oldEnd = item.end; + if (item.rrule) { + let rule = rrulestr(item.rrule); + let oldDtstart: Dayjs | undefined; + const oldUntil = rule.origOptions.until || windowEndDate; + if (rule.origOptions.dtstart) { + oldDtstart = this.localeDayjs(new Date(rule.origOptions.dtstart)); + } + // rule.origOptions.dtstart = oldStart.toDate(); + if (windowEndDate < oldUntil) { + rule.origOptions.until = windowEndDate; + } + + // reload + rule = rrulestr(rule.toString()); + + if (item.exdates || item.exrule) { + const rruleSet = new RRuleSet(); + rruleSet.rrule(rule); + if (item.exrule) { + rruleSet.exrule(rrulestr(item.exrule)); + } + if (item.exdates) { + item.exdates.forEach((exdate) => { + rruleSet.exdate(this.localeDayjs(exdate).toDate()); + }); + } + rule = rruleSet; + } + + const all = rule.between(windowStartDate, windowEndDate); + all.forEach((time, index) => { + const newEvent = { + ...item, + recurringEventId: item.id, + recurringEventStart: item.start, + recurringEventEnd: item.end, + id: `${item.id}-${index}`, + start: rule.origOptions.tzid + ? this.localeDayjs + .utc(time) + // TODO: Something is wrong here + .utcOffset(this.localeDayjs(new Date()).utcOffset(), true) + : this.localeDayjs(new Date(time)), + end: rule.origOptions.tzid + ? this.localeDayjs + .utc(time) + // TODO: Something is wrong here + .utcOffset(this.localeDayjs(new Date()).utcOffset(), true) + .add(oldEnd.diff(oldStart), "ms") + .add( + this.localeDayjs(new Date(oldUntil)).utcOffset() - + item.start.utcOffset(), + "m" + ) + : this.localeDayjs(new Date(time)).add( + oldEnd.diff(oldStart), + "ms" + ), + }; + + const eventStart = newEvent.start.clone(); + const eventEnd = newEvent.end.clone(); + if ( + this.isEventInTimeWindow( + eventStart, + eventEnd, + windowStart, + windowEnd + ) && + (!oldDtstart || eventStart >= oldDtstart) + ) { + this._attachEvent(newEvent); + } + }); + } + }); + } + + _resolveDate(num: number, date?: Dayjs) { + if (date !== undefined) { + this.selectDate = date.clone(); + } + + const setStartAndEndDates = ( + unit: "week" | "month" | "quarter" | "year" + ) => { + const unitWithType = `${unit}s` as ManipulateType; + this.startDate = + date !== undefined + ? this.selectDate?.startOf(unit) ?? + this.startDate.add(num, unitWithType) + : this.startDate.add(num, unitWithType); + this.endDate = this.startDate.endOf(unit); + }; + + switch (this.viewType) { + case ViewType.Week: + setStartAndEndDates("week"); + break; + + case ViewType.Day: + this.startDate = + date !== undefined + ? this.selectDate ?? this.startDate + : this.startDate.add(num, "days"); + this.endDate = this.startDate; + break; + + case ViewType.Month: + setStartAndEndDates("month"); + break; + + case ViewType.Quarter: + setStartAndEndDates("quarter"); + break; + + case ViewType.Year: + setStartAndEndDates("year"); + break; + + case ViewType.Custom: + case ViewType.Custom1: + case ViewType.Custom2: + if (this.behaviors.getCustomDateFunc !== undefined) { + const customDate = this.behaviors.getCustomDateFunc(this, num, date); + this.startDate = customDate.startDate.clone(); + this.endDate = customDate.endDate.clone(); + if (customDate.cellUnit) { + this.cellUnit = customDate.cellUnit; + } + } else { + throw new Error( + "This is a custom view type, set behaviors.getCustomDateFunc func to resolve the time window (startDate and endDate) yourself" + ); + } + break; + + default: + break; + } + } + + // Previous Code + _createHeaders() { + const headers = []; + let start = this.startDate; + let end = this.endDate; + let header = start; + + if (this.showAgenda) { + headers.push({ + time: header.format(DATETIME_FORMAT), + nonWorkingTime: false, + }); + } else if (this.cellUnit === CellUnit.Hour) { + if (start.hour() === 0) { + start = start.add(this.config.dayStartFrom, "hours"); + } + if (end.hour() === 0) { + end = end.add(this.config.dayStopTo, "hours"); + } + header = start; + + let prevHour = -1; + while (header >= start && header <= end) { + // prevent doubled hours on time change + if (header.hour() === prevHour) { + header = header.add(1, "hours"); + // eslint-disable-next-line no-continue + continue; + } + prevHour = header.hour(); + const minuteSteps = this.getMinuteStepsInHour(); + for (let i = 0; i < minuteSteps; i += 1) { + const hour = header.hour(); + if ( + hour >= this.config.dayStartFrom && + hour <= this.config.dayStopTo + ) { + const time = header.format(DATETIME_FORMAT); + const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc( + this, + time + ); + headers.push({ time, nonWorkingTime }); + } + + header = header.add(this.config.minuteStep, "minutes"); + } + } + } else if (this.cellUnit === CellUnit.Day) { + while (header >= start && header <= end) { + const time = header.format(DATETIME_FORMAT); + const dayOfWeek = header.weekday(); + if ( + this.config.displayWeekend || + (dayOfWeek !== 0 && dayOfWeek !== 6) + ) { + const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc( + this, + time + ); + headers.push({ time, nonWorkingTime }); + } + + header = header.add(1, "days"); + } + } else if (this.cellUnit === CellUnit.Week) { + while (header >= start && header <= end) { + const time = header.format(DATE_FORMAT); + headers.push({ time }); + header = header.add(1, "weeks").startOf("week"); + } + } else if (this.cellUnit === CellUnit.Month) { + while (header >= start && header <= end) { + const time = header.format(DATE_FORMAT); + headers.push({ time }); + header = header.add(1, "months").startOf("month"); + } + } else if (this.cellUnit === CellUnit.Year) { + while (header >= start && header <= end) { + const time = header.format(DATE_FORMAT); + headers.push({ time }); + header = header.add(1, "years").startOf("year"); + } + } + + this.headers = headers; + } + + // Fix Optimited code + // _createHeaders() { + // const headers = []; + // const start = this.localeDayjs(new Date(this.startDate)); + // const end = this.localeDayjs(new Date(this.endDate)); + + // const processHeader = (header, format, unit, incrementFn) => { + // let head = header; + // while (head >= start && head <= end) { + // const time = head.format(format); + // if (unit === CellUnit.Day) { + // const dayOfWeek = head.weekday(); + // if (this.config.displayWeekend || (dayOfWeek !== 0 && dayOfWeek !== 6)) { + // const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc(this, time); + // headers.push({ time, nonWorkingTime }); + // } + // } else { + // headers.push({ time }); + // } + // head = head.add(1, incrementFn); + // } + // }; + + // if (this.showAgenda) { + // headers.push({ time: start.format(DATETIME_FORMAT), nonWorkingTime: false }); + // } else if (this.cellUnit === CellUnit.Hour) { + // const hourIncrement = this.config.minuteStep < 60 ? 'minutes' : 'hours'; + // const minuteSteps = this.getMinuteStepsInHour(); + // let header = start.hour() === 0 ? start.add(this.config.dayStartFrom, 'hours') : start; + // while (header <= end) { + // const hour = header.hour(); + // if (hour >= this.config.dayStartFrom && hour <= this.config.dayStopTo) { + // const time = header.format(DATETIME_FORMAT); + // const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc(this, time); + // headers.push({ time, nonWorkingTime }); + // } + // header = header.add(minuteSteps, hourIncrement); + // } + // } else { + // const header = start; + // const format = this.cellUnit === CellUnit.Day ? DATETIME_FORMAT : DATE_FORMAT; + // const incrementFn = this.cellUnit === CellUnit.Day ? 'days' : `${this.cellUnit}s`; + // processHeader(header, format, this.cellUnit, incrementFn); + // } + + // this.headers = headers; + // } + + _createInitHeaderEvents(header: HeadersType): HeaderEventsType { + const start = this.localeDayjs(new Date(header.time)); + const startValue = start; + + let endValue; + if (this.showAgenda) { + const incrementUnit: ManipulateType = ( + { + [ViewType.Day]: "days", + [ViewType.Week]: "weeks", + [ViewType.Month]: "months", + [ViewType.Year]: "years", + [ViewType.Quarter]: "days", + [ViewType.Custom]: "days", + [ViewType.Custom1]: "days", + [ViewType.Custom2]: "days", + } as const + )[this.viewType]; + + if (incrementUnit === "days") { + endValue = this.endDate.clone().add(1, "days"); + } else { + endValue = start.add(1, incrementUnit); + } + } else { + const incrementUnit: ManipulateType = + ( + { + [CellUnit.Hour]: "minutes", + [CellUnit.Week]: "weeks", + [CellUnit.Month]: "months", + [CellUnit.Year]: "years", + [CellUnit.Day]: "days", + } as const + )[this.cellUnit] || "days"; + + endValue = start.add( + incrementUnit === "minutes" ? this.config.minuteStep : 1, + incrementUnit + ); + } + + return { + time: header.time, + nonWorkingTime: header.nonWorkingTime, + start: start, + end: endValue, + count: 0, + addMore: 0, + addMoreIndex: 0, + // TODO: This Specifies how many rows per Resource. Allow mofication + events: Array(3), + }; + } + + _createHeaderEvent(render: boolean, span: number, eventItem: EventItemType) { + return { render, span, eventItem }; + } + + _getEventSlotId(event: EventItemType) { + return this.isEventPerspective + ? this._getEventGroupId(event) + : event.resourceId; + } + + _getEventGroupId(event: EventItemType) { + return event.groupId ? event.groupId.toString() : event.id.toString(); + } + + _getEventGroupName(event: EventItemType) { + return event.groupName ? event.groupName : event.title; + } + + _generateEventGroups() { + const eventGroups: EventGroup[] = []; + const set = new Set(); + this.events.forEach((item) => { + const groupId = this._getEventGroupId(item); + const groupName = this._getEventGroupName(item); + + if (!set.has(groupId)) { + eventGroups.push({ + id: groupId, + name: groupName, + state: item, + }); + set.add(groupId); + } + }); + + this.eventGroups = eventGroups; + } + + _createInitRenderData( + isEventPerspective: boolean, + eventGroups: EventGroup[], + resources: Resource[], + headers: HeadersType[] + ) { + const slots = isEventPerspective ? eventGroups : resources; + const slotTree: SlotMapItem[] = []; + const slotMap = new Map(); + slots.forEach((slot) => { + const headerEvents = headers.map((header) => + this._createInitHeaderEvents(header) + ); + + const slotRenderData: RenderDataItem = { + slotId: slot.id, + slotName: slot.name, + slotTitle: "title" in slot ? slot.title : undefined, + parentId: "parentId" in slot ? slot.parentId : undefined, + groupOnly: "groupOnly" in slot ? slot.groupOnly : undefined, + hasSummary: false, + rowMaxCount: 0, + rowHeight: + this.config.nonAgendaSlotMinHeight !== 0 + ? this.config.nonAgendaSlotMinHeight + : this.config.eventItemLineHeight + 2, + headerItems: headerEvents, + indent: 0, + hasChildren: false, + expanded: true, + render: true, + }; + + const { id } = slot; + let value: SlotMapItem | undefined; + if (slotMap.has(id)) { + value = slotMap.get(id) as SlotMapItem; + value.data = slotRenderData; + } else { + value = { + data: slotRenderData, + children: [], + }; + slotMap.set(id, value); + } + + if ( + !("parentId" in slot) || + slot.parentId == undefined || + slot.parentId === id + ) { + slotTree.push(value); + } else { + let parentValue: SlotMapItem | undefined; + if (slotMap.has(slot.parentId)) { + parentValue = slotMap.get(slot.parentId) as SlotMapItem; + } else { + parentValue = { + data: undefined, + children: [], + }; + slotMap.set(slot.parentId, parentValue); + } + + parentValue.children.push(value); + } + }); + + const slotStack: SlotMapItem[] = []; + let i; + for (i = slotTree.length - 1; i >= 0; i -= 1) { + const item = slotTree[i]; + if (item) { + slotStack.push(item); + } + } + + const initRenderData = []; + let currentNode: SlotMapItem | undefined; + while (slotStack.length > 0) { + currentNode = slotStack.pop() as SlotMapItem; + console.log("currentNode", currentNode); + + if (currentNode.data) { + if (currentNode.data.indent > 0) { + currentNode.data.render = this.config.defaultExpanded; + } + if (currentNode.children.length > 0) { + currentNode.data.hasChildren = true; + currentNode.data.expanded = this.config.defaultExpanded; + } + initRenderData.push(currentNode.data); + + for (i = currentNode.children.length - 1; i >= 0; i -= 1) { + currentNode.children[i].data.indent = currentNode.data.indent + 1; + slotStack.push(currentNode.children[i]); + } + } + } + console.log("initRenderData", initRenderData); + return initRenderData; + } + + _getSpan(startTime: Dayjs, endTime: Dayjs, headers: HeadersType[]) { + if (this.showAgenda) return 1; + + // function startOfWeek(date) { + // const day = date.getDay(); + // const diff = date.getDate() - day; + // return new Date(date.getFullYear(), date.getMonth(), diff); + // } + + const timeBetween = ( + date1: Dayjs, + date2: Dayjs, + timeIn: "days" | "minute" | "minutes" | "day" + ) => { + if (timeIn === "days" || timeIn === "day") { + if (date1.date() === date2.date() && date1.month() === date2.month()) { + return 1; + } + } + + let one; + switch (timeIn) { + case "days": + case "day": + one = 1000 * 60 * 60 * 24; + break; + case "minutes": + case "minute": + one = 1000 * 60; + break; + default: + return 0; + } + + const date1Ms = date1.toDate().getTime(); + const date2Ms = date2.toDate().getTime(); + + const diff = (date2Ms - date1Ms) / one; + return diff < 0 ? 0 : diff; + }; + + let eventStart = startTime; + let eventEnd = endTime; + let span = 0; + + const windowStart = this.startDate.clone().startOf("day"); + const windowEnd = this.endDate.clone().endOf("day"); + // setHours(0, 0, 0, 0); + // windowEnd.setHours(23, 59, 59); + + if (this.viewType === ViewType.Day) { + if (headers.length > 0) { + let day = this.localeDayjs(new Date(headers[0]!.time)); + if (day.date() > eventStart.date() && day.date() < eventEnd.date()) { + span = 1440 / this.config.minuteStep; + } else if ( + day.date() > eventStart.date() && + day.date() === eventEnd.date() + ) { + span = Math.ceil( + timeBetween(day, eventEnd, "minutes") / this.config.minuteStep + ); + } else if ( + day.date() === eventStart.date() && + day.date() < eventEnd.date() + ) { + day = day.endOf("day"); + + span = Math.ceil( + timeBetween(eventStart, day, "minutes") / this.config.minuteStep + ); + } else if ( + (day.date() === eventStart.date() && + day.date() === eventEnd.date()) || + eventEnd.date() === eventStart.date() + ) { + span = Math.ceil( + timeBetween(eventStart, eventEnd, "minutes") / + this.config.minuteStep + ); + } + } + } else if ( + this.viewType === ViewType.Week || + this.viewType === ViewType.Month || + this.viewType === ViewType.Quarter || + this.viewType === ViewType.Year + ) { + const startDate = windowStart.isBefore(eventStart) + ? eventStart + : windowStart; + const endDate = windowEnd > eventEnd ? eventEnd : windowEnd; + span = Math.ceil(timeBetween(startDate, endDate, "days")); + } else { + if (this.cellUnit === CellUnit.Day) { + eventStart = eventStart.startOf("day"); + eventEnd = eventEnd.endOf("day"); + } + + const timeIn = this.cellUnit === CellUnit.Day ? "days" : "minutes"; + const dividedBy = + this.cellUnit === CellUnit.Day ? 1 : this.config.minuteStep; + + if (windowStart >= eventStart && eventEnd <= windowEnd) { + span = Math.ceil( + timeBetween(windowStart, eventEnd, timeIn) / dividedBy + ); + } else if (windowStart > eventStart && eventEnd > windowEnd) { + span = Math.ceil( + timeBetween(windowStart, windowEnd, timeIn) / dividedBy + ); + } else if (windowStart <= eventStart && eventEnd >= windowEnd) { + span = Math.ceil( + timeBetween(eventStart, windowEnd, timeIn) / dividedBy + ); + } else { + span = Math.ceil(timeBetween(eventStart, eventEnd, timeIn) / dividedBy); + } + } + + return span; + } + + _validateResource(resources: Resource[]) { + if (Object.prototype.toString.call(resources) !== "[object Array]") { + throw new Error("Resources should be Array object"); + } + + resources.forEach((item, index) => { + if (item === undefined) { + console.error(`Resource undefined: ${index}`); + throw new Error(`Resource undefined: ${index}`); + } + if (item.id === undefined || item.name === undefined) { + console.error("Resource property missed", index, item); + throw new Error(`Resource property undefined: ${index}`); + } + }); + } + + _validateEventGroups(eventGroups: EventGroup[]) { + if (Object.prototype.toString.call(eventGroups) !== "[object Array]") { + throw new Error("Event groups should be Array object"); + } + + eventGroups.forEach((item, index) => { + if (item === undefined) { + console.error(`Event group undefined: ${index}`); + throw new Error(`Event group undefined: ${index}`); + } + if (item.id === undefined || item.name === undefined) { + console.error("Event group property missed", index, item); + throw new Error(`Event group property undefined: ${index}`); + } + }); + } + + _validateEvents(events: EventItemType[]) { + if (Object.prototype.toString.call(events) !== "[object Array]") { + throw new Error("Events should be Array object"); + } + + events.forEach((e, index) => { + if (e === undefined) { + console.error(`Event undefined: ${index}`); + throw new Error(`Event undefined: ${index}`); + } + if ( + e.id === undefined || + e.resourceId === undefined || + e.title === undefined || + e.start === undefined || + e.end === undefined + ) { + console.error("Event property missed", index, e); + throw new Error(`Event property undefined: ${index}`); + } + }); + } + + _validateMinuteStep(minuteStep: number) { + if (60 % minuteStep !== 0) { + console.error( + "Minute step is not set properly - 60 minutes must be divisible without remainder by this number" + ); + throw new Error( + "Minute step is not set properly - 60 minutes must be divisible without remainder by this number" + ); + } + } + + _compare(event1: EventItemType, event2: EventItemType) { + const start1 = event1.start.clone(); + const start2 = event2.start.clone(); + if (start1 !== start2) return start1 < start2 ? -1 : 1; + + const end1 = event1.end.clone(); + const end2 = event2.end.clone(); + if (end1 !== end2) return end1.isBefore(end2) ? -1 : 1; + + return event1.id < event2.id ? -1 : 1; + } + + _createRenderData() { + // Initialize render data + const initRenderData = this._createInitRenderData( + this.isEventPerspective, + this.eventGroups, + this.resources, + this.headers + ); + console.log("initRenderData1", initRenderData); + + // Get the maximum number of events that can be displayed in a cell + const cellMaxEventsCount = this.getCellMaxEvents(); + console.log("cellMaxEventsCount", cellMaxEventsCount); + const cellMaxEventsCountValue = 30; + + // Iterate over each event + this.events.forEach((item) => { + // Filter render data to find the resource events list for the current event + const resourceEventsList = initRenderData.filter( + (x) => x.slotId === this._getEventSlotId(item) + ); + if (resourceEventsList.length > 0) { + const resourceEvents = resourceEventsList[0] as RenderDataItem; + const span = this._getSpan(item.start, item.end, this.headers); + const eventStart = item.start.clone(); + const eventEnd = item.end.clone(); + let pos = -1; + + // Iterate over each header item in the resource events + resourceEvents.headerItems.forEach((header, index) => { + const headerStart = header.start.clone(); + const headerEnd = header.end.clone(); + if (headerEnd.isAfter(eventStart) && headerStart.isBefore(eventEnd)) { + // Increment the count of events in the header + header.count += 1; + + if (header.count > resourceEvents.rowMaxCount) { + resourceEvents.rowMaxCount = header.count; + const rowsCount = + cellMaxEventsCount <= cellMaxEventsCountValue && + resourceEvents.rowMaxCount > cellMaxEventsCount + ? cellMaxEventsCount + : resourceEvents.rowMaxCount; + + console.log( + "rowsCount", + cellMaxEventsCount, + cellMaxEventsCountValue, + resourceEvents.rowMaxCount, + cellMaxEventsCount, + cellMaxEventsCount <= cellMaxEventsCountValue && + resourceEvents.rowMaxCount > cellMaxEventsCount, + rowsCount + ); + const newRowHeight = + rowsCount * this.config.eventItemLineHeight + + (this.config.creatable && this.config.checkConflict === false + ? 20 + : 2); + if (newRowHeight > resourceEvents.rowHeight) + resourceEvents.rowHeight = newRowHeight; + } + + // Find the position to insert the event in the header's events array + if (pos === -1) { + let tmp = 0; + while (header.events[tmp] !== undefined) tmp += 1; + pos = tmp; + } + + // Determine if the event should be rendered in the current header + let render = + headerStart.isBefore(eventStart) || + headerStart.isSame(eventStart) || + index === 0; + if (render === false) { + const previousHeader = resourceEvents.headerItems[index - 1]; + if (previousHeader != undefined) { + const previousHeaderStart = previousHeader.start.clone(); + const previousHeaderEnd = previousHeader.end.clone(); + if ( + previousHeaderEnd.isBefore(eventStart) || + previousHeaderStart.isAfter(eventEnd) + ) + render = true; + } + } + + // Create and assign the header event + header.events[pos] = this._createHeaderEvent(render, span, item); + } + }); + } + }); + console.log("initRenderData2", initRenderData); + + // Handle summary and additional event rendering logic + if ( + cellMaxEventsCount <= cellMaxEventsCountValue || + this.behaviors.getSummaryFunc !== undefined + ) { + initRenderData.forEach((resourceEvents) => { + let hasSummary = false; + + resourceEvents.headerItems.forEach((headerItem) => { + if (cellMaxEventsCount <= cellMaxEventsCountValue) { + let renderItemsCount = 0; + let addMoreIndex = 0; + let index = 0; + while (index < cellMaxEventsCount - 1) { + if (headerItem.events[index] !== undefined) { + renderItemsCount += 1; + addMoreIndex = index + 1; + } + index += 1; + } + + if (headerItem.events[index] !== undefined) { + if (renderItemsCount + 1 < headerItem.count) { + headerItem.addMore = headerItem.count - renderItemsCount; + headerItem.addMoreIndex = addMoreIndex; + } + } else if (renderItemsCount < headerItem.count) { + headerItem.addMore = headerItem.count - renderItemsCount; + headerItem.addMoreIndex = addMoreIndex; + } + } + + // Generate summary if the summary function is defined + if (this.behaviors.getSummaryFunc !== undefined) { + const events: HeaderEvent[] = []; + + headerItem.events.forEach((e) => { + if ("eventItem" in e) events.push(e); + }); + + const item = this.behaviors.getSummaryFunc( + this, + events, + resourceEvents.slotId, + resourceEvents.slotName, + headerItem.start, + headerItem.end + ); + headerItem.summary = item; + + if (!!headerItem.summary && headerItem.summary.text !== undefined) + hasSummary = true; + } + }); + + resourceEvents.hasSummary = hasSummary; + if (hasSummary) { + const rowsCount = + cellMaxEventsCount <= cellMaxEventsCountValue && + resourceEvents.rowMaxCount > cellMaxEventsCount + ? cellMaxEventsCount + : resourceEvents.rowMaxCount; + const newRowHeight = + (rowsCount + 1) * this.config.eventItemLineHeight + + (this.config.creatable && this.config.checkConflict === false + ? 20 + : 2); + if (newRowHeight > resourceEvents.rowHeight) + resourceEvents.rowHeight = newRowHeight; + } + }); + } + + console.log("this.renderData", this.renderData); + // Assign the processed render data to the class property + this.renderData = initRenderData; + } + + _startResizing() { + this.resizing = true; + } + + _stopResizing() { + this.resizing = false; + } + + _isResizing() { + return this.resizing; + } +} diff --git a/src/components/SchedulerHeader.jsx b/src/components/SchedulerHeader.jsx deleted file mode 100644 index 495d7fe..0000000 --- a/src/components/SchedulerHeader.jsx +++ /dev/null @@ -1,117 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import { Col, Row, Spin, Radio, Space, Popover, Calendar } from 'antd'; -import { RightOutlined, LeftOutlined } from '@ant-design/icons'; -import dayjs from 'dayjs'; -import { DATE_FORMAT } from '../config/default'; - -const RadioButton = Radio.Button; -const RadioGroup = Radio.Group; - -function SchedulerHeader({ onViewChange, goNext, goBack, onSelectDate, schedulerData, leftCustomHeader, rightCustomHeader }) { - const [viewSpinning, setViewSpinning] = useState(false); - const [dateSpinning, setDateSpinning] = useState(false); - const [visible, setVisible] = useState(false); - - const { viewType, showAgenda, isEventPerspective, config } = schedulerData; - const dateLabel = schedulerData.getDateLabel(); - const selectDate = schedulerData.getSelectedDate(); - const calendarLocale = schedulerData.getCalendarPopoverLocale()?.default?.Calendar; - const defaultValue = `${viewType}${showAgenda ? 1 : 0}${isEventPerspective ? 1 : 0}`; - - const handleEvents = (func, isViewSpinning, funcArg = undefined) => { - if (isViewSpinning) { - if (config.viewChangeSpinEnabled) setViewSpinning(true); - } else if (config.dateChangeSpinEnabled) setDateSpinning(true); - - const coreFunc = () => { - if (funcArg !== undefined) func(funcArg); - else func(); - - if (isViewSpinning) { - if (config.viewChangeSpinEnabled) setViewSpinning(false); - } else if (config.dateChangeSpinEnabled) setDateSpinning(false); - }; - - if (config.viewChangeSpinEnabled || config.dateChangeSpinEnabled) { - setTimeout(coreFunc, config.schedulerHeaderEventsFuncsTimeoutMs); // 100ms - } else { - coreFunc(); - } - }; - - const popover = ( -
- { - setVisible(false); - handleEvents(onSelectDate, false, date.format(DATE_FORMAT)); - }} - /> -
- ); - - const radioButtonList = config.views.map(item => ( - - {item.viewName} - - )); - - return ( - - {leftCustomHeader} - -
- -
- handleEvents(goBack, false)} /> - {config.calendarPopoverEnabled ? ( - - - {dateLabel} - - - ) : ( - {dateLabel} - )} - handleEvents(goNext, false)} /> -
- -
-
- - - - - handleEvents(onViewChange, true, event)}> - {radioButtonList} - - - - {rightCustomHeader} -
- ); -} - -SchedulerHeader.propTypes = { - onViewChange: PropTypes.func.isRequired, - goNext: PropTypes.func.isRequired, - goBack: PropTypes.func.isRequired, - onSelectDate: PropTypes.func.isRequired, - schedulerData: PropTypes.object.isRequired, - leftCustomHeader: PropTypes.object, - rightCustomHeader: PropTypes.object, -}; - -SchedulerHeader.defaultProps = { - leftCustomHeader: null, - rightCustomHeader: null, -}; - -export default SchedulerHeader; diff --git a/src/components/SchedulerHeader.tsx b/src/components/SchedulerHeader.tsx new file mode 100644 index 0000000..f53acd7 --- /dev/null +++ b/src/components/SchedulerHeader.tsx @@ -0,0 +1,161 @@ +import React, { useState } from "react"; +import { Col, Row, Spin, Radio, Space, Popover, Calendar } from "antd"; +import { RightOutlined, LeftOutlined } from "@ant-design/icons"; +import dayjs, { Dayjs } from "dayjs"; +import { SchedulerData } from "./SchedulerData"; + +const RadioButton = Radio.Button; +const RadioGroup = Radio.Group; + +interface SchedulerHeaderProps { + onViewChange: (event: any) => void; + goNext: () => void; + goBack: () => void; + onSelectDate: (date: Dayjs) => void; + schedulerData: SchedulerData; + leftCustomHeader?: React.ReactNode | null; + rightCustomHeader?: React.ReactNode | null; +} + +function SchedulerHeader({ + onViewChange, + goNext, + goBack, + onSelectDate, + schedulerData, + leftCustomHeader = null, + rightCustomHeader = null, +}: SchedulerHeaderProps) { + const [viewSpinning, setViewSpinning] = useState(false); + const [dateSpinning, setDateSpinning] = useState(false); + const [visible, setVisible] = useState(false); + + const { viewType, showAgenda, isEventPerspective, config } = schedulerData; + const dateLabel = schedulerData.getDateLabel(); + const selectDate = schedulerData.getSelectedDate(); + const calendarLocale = schedulerData.getCalendarPopoverLocale(); + console.log("calendarLocale", calendarLocale); + const defaultValue = `${viewType}${showAgenda ? 1 : 0}${isEventPerspective ? 1 : 0}`; + + const handleEvents = ( + func: Function, + isViewSpinning: boolean, + funcArg?: any + ) => { + if (isViewSpinning) { + if (config.viewChangeSpinEnabled) setViewSpinning(true); + } else if (config.dateChangeSpinEnabled) setDateSpinning(true); + + const coreFunc = () => { + if (funcArg !== undefined) func(funcArg); + else func(); + + if (isViewSpinning) { + if (config.viewChangeSpinEnabled) setViewSpinning(false); + } else if (config.dateChangeSpinEnabled) setDateSpinning(false); + }; + + if (config.viewChangeSpinEnabled || config.dateChangeSpinEnabled) { + setTimeout(coreFunc, config.schedulerHeaderEventsFuncsTimeoutMs); // 100ms + } else { + coreFunc(); + } + }; + + const popover = ( +
+ { + setVisible(false); + handleEvents(onSelectDate, false, date); + }} + /> +
+ ); + + const radioButtonList = config.views.map((item) => ( + + {item.viewName} + + )); + + return ( +
+
+ +
+ handleEvents(goBack, false)} + /> + {config.calendarPopoverEnabled ? ( + + + {dateLabel} + + + ) : ( + {dateLabel} + )} + handleEvents(goNext, false)} + /> +
+ +
+
+ + + + handleEvents(onViewChange, true, event)} + > + {radioButtonList} + + +
+ ); +} + +// SchedulerHeader.propTypes = { +// onViewChange: PropTypes.func.isRequired, +// goNext: PropTypes.func.isRequired, +// goBack: PropTypes.func.isRequired, +// onSelectDate: PropTypes.func.isRequired, +// schedulerData: PropTypes.object.isRequired, +// leftCustomHeader: PropTypes.object, +// rightCustomHeader: PropTypes.object, +// }; + +// SchedulerHeader.defaultProps = { +// leftCustomHeader: null, +// rightCustomHeader: null, +// }; + +export default SchedulerHeader; diff --git a/src/components/SelectedArea.jsx b/src/components/SelectedArea.jsx deleted file mode 100644 index 71a759c..0000000 --- a/src/components/SelectedArea.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -function SelectedArea({ left, width, schedulerData }) { - const { config } = schedulerData; - - const selectedAreaStyle = { - left, - width, - top: 0, - bottom: 0, - backgroundColor: config.selectedAreaColor, - }; - - return
; -} - -SelectedArea.propTypes = { - schedulerData: PropTypes.object.isRequired, - left: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, -}; - -export default SelectedArea; diff --git a/src/components/SelectedArea.tsx b/src/components/SelectedArea.tsx new file mode 100644 index 0000000..b8958a4 --- /dev/null +++ b/src/components/SelectedArea.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { SchedulerData } from "./SchedulerData"; + +interface SelectedAreaProps { + left: number; + width: number; + schedulerData: SchedulerData; +} + +function SelectedArea({ left, width, schedulerData }: SelectedAreaProps) { + const { config } = schedulerData; + + const selectedAreaStyle = { + left, + width, + top: 0, + bottom: 0, + backgroundColor: config.selectedAreaColor, + }; + + return
; +} + +// SelectedArea.propTypes = { +// schedulerData: PropTypes.object.isRequired, +// left: PropTypes.number.isRequired, +// width: PropTypes.number.isRequired, +// }; + +export default SelectedArea; diff --git a/src/components/Summary.jsx b/src/components/Summary.jsx deleted file mode 100644 index 84b9753..0000000 --- a/src/components/Summary.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { SummaryPos } from '../config/default'; - -function Summary({ schedulerData, summary, left, width, top }) { - const { config } = schedulerData; - const color = summary.color !== undefined ? summary.color : config.summaryColor; - let textAlign = 'center'; - - if (config.summaryPos === SummaryPos.TopRight || config.summaryPos === SummaryPos.BottomRight) { - textAlign = 'right'; - } else if (config.summaryPos === SummaryPos.TopLeft || config.summaryPos === SummaryPos.BottomLeft) { - textAlign = 'left'; - } - - const style = { - height: config.eventItemHeight, - color, - textAlign, - marginLeft: '6px', - marginRight: '6px', - ...(summary.fontSize !== undefined ? { fontSize: summary.fontSize } : {}), - }; - - return ( -
-
{summary.text}
-
- ); -} - -Summary.propTypes = { - schedulerData: PropTypes.object.isRequired, - summary: PropTypes.object.isRequired, - left: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - top: PropTypes.number.isRequired, -}; - -export default Summary; diff --git a/src/components/Summary.tsx b/src/components/Summary.tsx new file mode 100644 index 0000000..d602c21 --- /dev/null +++ b/src/components/Summary.tsx @@ -0,0 +1,52 @@ +import React, { CSSProperties } from "react"; +import PropTypes from "prop-types"; +import { SummaryPos } from "../config/default"; +import { SchedulerData } from "./SchedulerData"; +import { SummaryType } from "../types/baseType"; + +interface SummaryProps { + schedulerData: SchedulerData; + summary: SummaryType; + left: number; + width: number; + top: number; +} + +function Summary({ schedulerData, summary, left, width, top }: SummaryProps) { + const { config } = schedulerData; + const color = + summary.color !== undefined ? summary.color : config.summaryColor; + let textAlign: CSSProperties['textAlign'] = "center"; + + if ( + config.summaryPos === SummaryPos.TopRight || + config.summaryPos === SummaryPos.BottomRight + ) { + textAlign = "right"; + } else if ( + config.summaryPos === SummaryPos.TopLeft || + config.summaryPos === SummaryPos.BottomLeft + ) { + textAlign = "left"; + } + + const style: CSSProperties = { + height: config.eventItemHeight, + color, + ...(textAlign ? { textAlign } : {}), + marginLeft: "6px", + marginRight: "6px", + ...(summary.fontSize !== undefined ? { fontSize: summary.fontSize } : {}), + }; + + return ( +
+
{summary.text}
+
+ ); +} + +export default Summary; diff --git a/src/components/WrapperFun.jsx b/src/components/WrapperFun.tsx similarity index 65% rename from src/components/WrapperFun.jsx rename to src/components/WrapperFun.tsx index 2b71cbc..ad8f7e8 100644 --- a/src/components/WrapperFun.jsx +++ b/src/components/WrapperFun.tsx @@ -1,7 +1,8 @@ +//@ts-nocheck /* eslint-disable react/jsx-props-no-spreading */ -import React from 'react'; -import { DndProvider } from 'react-dnd'; -import { HTML5Backend } from 'react-dnd-html5-backend'; +import React from "react"; +import { DndProvider } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; export default function WrapperFun(Component) { return function WrappedComponent(props) { diff --git a/src/components/index.jsx b/src/components/index.jsx deleted file mode 100644 index 838adf3..0000000 --- a/src/components/index.jsx +++ /dev/null @@ -1,448 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -// Col, Row and Icon do not have their own less files for styling. They use -// rules declared in antd's global css. If these styles are imported directly -// from within antd, they'll include, for instance, reset rules. These will -// affect everything on the page and in essence would leak antd's global styles -// into all projects using this library. Instead of doing that, we are using -// a hack which allows us to wrap all antd styles to target specific root. In -// this case the root id will be "RBS-Scheduler-root". This way the reset styles -// won't be applied to elements declared outside of component. -// -// For development -// This fix is implemented with webpack's NormalModuleReplacementPlugin in -// webpack/webpack-dev.config.js. -// -// -// The next components have their own specific stylesheets which we import -// separately here to avoid importing from files which have required the global -// antd styles. - -import EventItem from './EventItem'; -import DnDSource from './DnDSource'; -import DnDContext from './DnDContext'; -import ResourceView from './ResourceView'; -import HeaderView from './HeaderView'; -import BodyView from './BodyView'; -import ResourceEvents from './ResourceEvents'; -import AgendaView from './AgendaView'; -import AddMorePopover from './AddMorePopover'; -import SchedulerData from './SchedulerData'; -import DemoData from '../sample-data/sample1'; -import SchedulerHeader from './SchedulerHeader'; -import { ViewType, CellUnit, DATETIME_FORMAT, DATE_FORMAT, SummaryPos } from '../config/default'; -import wrapperFun from './WrapperFun'; - -class Scheduler extends Component { - constructor(props) { - super(props); - - const { schedulerData, dndSources, parentRef } = props; - let sources = []; - sources.push(new DnDSource(dndProps => dndProps.eventItem, EventItem, schedulerData.config.dragAndDropEnabled)); - if (dndSources !== undefined && dndSources.length > 0) { - sources = [...sources, ...dndSources]; - } - const dndContext = new DnDContext(sources, ResourceEvents); - - this.currentArea = -1; - this.state = { - dndContext, - contentScrollbarHeight: 17, - contentScrollbarWidth: 17, - resourceScrollbarHeight: 17, - resourceScrollbarWidth: 17, - documentWidth: 0, - documentHeight: 0, - }; - this.scrollLeft = 0; - this.scrollTop = 0; - - if ((schedulerData.isSchedulerResponsive() && !schedulerData.config.responsiveByParent) || parentRef === undefined) { - schedulerData._setDocumentWidth(document.documentElement.clientWidth); - window.onresize = this.onWindowResize; - } - } - - onWindowResize = e => { - const { schedulerData } = this.props; - schedulerData._setDocumentWidth(document.documentElement.clientWidth); - this.setState({ documentWidth: document.documentElement.clientWidth, documentHeight: document.documentElement.clientHeight }); - }; - - static propTypes = { - parentRef: PropTypes.object, - schedulerData: PropTypes.object.isRequired, - prevClick: PropTypes.func.isRequired, - nextClick: PropTypes.func.isRequired, - onViewChange: PropTypes.func.isRequired, - onSelectDate: PropTypes.func.isRequired, - onSetAddMoreState: PropTypes.func, - updateEventStart: PropTypes.func, - updateEventEnd: PropTypes.func, - moveEvent: PropTypes.func, - movingEvent: PropTypes.func, - leftCustomHeader: PropTypes.object, - rightCustomHeader: PropTypes.object, - newEvent: PropTypes.func, - subtitleGetter: PropTypes.func, - eventItemClick: PropTypes.func, - viewEventClick: PropTypes.func, - viewEventText: PropTypes.string, - viewEvent2Click: PropTypes.func, - viewEvent2Text: PropTypes.string, - conflictOccurred: PropTypes.func, - eventItemTemplateResolver: PropTypes.func, - dndSources: PropTypes.array, - slotClickedFunc: PropTypes.func, - toggleExpandFunc: PropTypes.func, - slotItemTemplateResolver: PropTypes.func, - nonAgendaCellHeaderTemplateResolver: PropTypes.func, - onScrollLeft: PropTypes.func, - onScrollRight: PropTypes.func, - onScrollTop: PropTypes.func, - onScrollBottom: PropTypes.func, - }; - - componentDidMount(props, state) { - const { schedulerData, parentRef } = this.props; - - this.resolveScrollbarSize(); - - if (parentRef !== undefined) { - if (schedulerData.config.responsiveByParent && !!parentRef.current) { - schedulerData._setDocumentWidth(parentRef.current.offsetWidth); - this.ulObserver = new ResizeObserver((entries, observer) => { - if (parentRef.current) { - const width = parentRef.current.offsetWidth; - const height = parentRef.current.offsetHeight; - schedulerData._setDocumentWidth(width); - this.setState({ - documentWidth: width, - documentHeight: height, - }); - } - }); - - this.ulObserver.observe(parentRef.current); - } - } - } - - componentDidUpdate(props, state) { - this.resolveScrollbarSize(); - - const { schedulerData } = this.props; - const { localeDayjs, behaviors } = schedulerData; - if (schedulerData.getScrollToSpecialDayjs() && !!behaviors.getScrollSpecialDayjsFunc) { - if (!!this.schedulerContent && this.schedulerContent.scrollWidth > this.schedulerContent.clientWidth) { - const start = localeDayjs(new Date(schedulerData.startDate)).startOf('day'); - const end = localeDayjs(new Date(schedulerData.endDate)).endOf('day'); - const specialDayjs = behaviors.getScrollSpecialDayjsFunc(schedulerData, start, end); - if (specialDayjs >= start && specialDayjs <= end) { - let index = 0; - schedulerData.headers.forEach(item => { - const header = localeDayjs(new Date(item.time)); - if (specialDayjs >= header) index++; - }); - this.schedulerContent.scrollLeft = (index - 1) * schedulerData.getContentCellWidth(); - - schedulerData.setScrollToSpecialDayjs(false); - } - } - } - } - - render() { - const { schedulerData, leftCustomHeader, rightCustomHeader } = this.props; - const { viewType, renderData, showAgenda, config } = schedulerData; - const width = schedulerData.getSchedulerWidth(); - - let tbodyContent = ; - if (showAgenda) { - tbodyContent = ; - } else { - const resourceTableWidth = schedulerData.getResourceTableWidth(); - const schedulerContainerWidth = width - (config.resourceViewEnabled ? resourceTableWidth : 0); - const schedulerWidth = schedulerData.getContentTableWidth() - 1; - const DndResourceEvents = this.state.dndContext.getDropTarget(config.dragAndDropEnabled); - const eventDndSource = this.state.dndContext.getDndSource(); - - const displayRenderData = renderData.filter(o => o.render); - const resourceEventsList = displayRenderData.map(item => ); - - const { contentScrollbarHeight } = this.state; - const { contentScrollbarWidth } = this.state; - const { resourceScrollbarHeight } = this.state; - const { resourceScrollbarWidth } = this.state; - const contentHeight = config.schedulerContentHeight; - const resourcePaddingBottom = resourceScrollbarHeight === 0 ? contentScrollbarHeight : 0; - const contentPaddingBottom = contentScrollbarHeight === 0 ? resourceScrollbarHeight : 0; - let schedulerContentStyle = { - overflowX: viewType === ViewType.Week ? 'hidden' : 'auto', - overflowY: 'auto', - margin: '0px', - position: 'relative', - height: contentHeight, - paddingBottom: contentPaddingBottom, - }; - let resourceContentStyle = { - height: contentHeight, - overflowX: 'auto', - overflowY: 'auto', - width: resourceTableWidth + resourceScrollbarWidth - 2, - margin: `0px -${contentScrollbarWidth}px 0px 0px`, - }; - if (config.schedulerMaxHeight > 0) { - schedulerContentStyle = { - ...schedulerContentStyle, - maxHeight: config.schedulerMaxHeight - config.tableHeaderHeight, - }; - resourceContentStyle = { - ...resourceContentStyle, - maxHeight: config.schedulerMaxHeight - config.tableHeaderHeight, - }; - } - - const resourceName = schedulerData.isEventPerspective ? config.taskName : config.resourceName; - tbodyContent = ( - - -
-
-
- - - - - - -
{resourceName}
-
-
-
- -
-
- - -
-
-
-
- - -
-
-
-
-
-
-
- - {resourceEventsList} -
-
-
- - -
-
-
-
-
- - - ); - } - - let schedulerHeader =
; - if (config.headerEnabled) { - schedulerHeader = ( - - ); - } - - return ( - - - - - - - {tbodyContent} -
{schedulerHeader}
- ); - } - - resolveScrollbarSize = () => { - const { schedulerData } = this.props; - let contentScrollbarHeight = 17; - let contentScrollbarWidth = 17; - let resourceScrollbarHeight = 17; - let resourceScrollbarWidth = 17; - if (this.schedulerContent) { - contentScrollbarHeight = this.schedulerContent.offsetHeight - this.schedulerContent.clientHeight; - contentScrollbarWidth = this.schedulerContent.offsetWidth - this.schedulerContent.clientWidth; - } - if (this.schedulerResource) { - resourceScrollbarHeight = this.schedulerResource.offsetHeight - this.schedulerResource.clientHeight; - resourceScrollbarWidth = this.schedulerResource.offsetWidth - this.schedulerResource.clientWidth; - } - - let tmpState = {}; - let needSet = false; - if (contentScrollbarHeight !== this.state.contentScrollbarHeight) { - tmpState = { ...tmpState, contentScrollbarHeight }; - needSet = true; - } - if (contentScrollbarWidth !== this.state.contentScrollbarWidth) { - tmpState = { ...tmpState, contentScrollbarWidth }; - needSet = true; - } - if (resourceScrollbarHeight !== this.state.resourceScrollbarHeight) { - tmpState = { ...tmpState, resourceScrollbarHeight }; - needSet = true; - } - if (resourceScrollbarWidth !== this.state.resourceScrollbarWidth) { - tmpState = { ...tmpState, resourceScrollbarWidth }; - needSet = true; - } - if (needSet) this.setState(tmpState); - }; - - schedulerHeadRef = element => { - this.schedulerHead = element; - }; - - onSchedulerHeadMouseOver = () => { - this.currentArea = 2; - }; - - onSchedulerHeadMouseOut = () => { - this.currentArea = -1; - }; - - onSchedulerHeadScroll = (proxy, event) => { - if ((this.currentArea === 2 || this.currentArea === -1) && this.schedulerContent.scrollLeft !== this.schedulerHead.scrollLeft) { - this.schedulerContent.scrollLeft = this.schedulerHead.scrollLeft; - } - }; - - schedulerResourceRef = element => { - this.schedulerResource = element; - }; - - onSchedulerResourceMouseOver = () => { - this.currentArea = 1; - }; - - onSchedulerResourceMouseOut = () => { - this.currentArea = -1; - }; - - onSchedulerResourceScroll = (proxy, event) => { - if (this.schedulerResource) { - if ((this.currentArea === 1 || this.currentArea === -1) && this.schedulerContent.scrollTop !== this.schedulerResource.scrollTop) { - this.schedulerContent.scrollTop = this.schedulerResource.scrollTop; - } - } - }; - - schedulerContentRef = element => { - this.schedulerContent = element; - }; - - schedulerContentBgTableRef = element => { - this.schedulerContentBgTable = element; - }; - - onSchedulerContentMouseOver = () => { - this.currentArea = 0; - }; - - onSchedulerContentMouseOut = () => { - this.currentArea = -1; - }; - - onSchedulerContentScroll = (proxy, event) => { - if (this.schedulerResource) { - if (this.currentArea === 0 || this.currentArea === -1) { - if (this.schedulerHead.scrollLeft !== this.schedulerContent.scrollLeft) this.schedulerHead.scrollLeft = this.schedulerContent.scrollLeft; - if (this.schedulerResource.scrollTop !== this.schedulerContent.scrollTop) this.schedulerResource.scrollTop = this.schedulerContent.scrollTop; - } - } - - const { schedulerData, onScrollLeft, onScrollRight, onScrollTop, onScrollBottom } = this.props; - if (this.schedulerContent.scrollLeft !== this.scrollLeft) { - if (this.schedulerContent.scrollLeft === 0 && onScrollLeft !== undefined) { - onScrollLeft(schedulerData, this.schedulerContent, this.schedulerContent.scrollWidth - this.schedulerContent.clientWidth); - } - if (Math.round(this.schedulerContent.scrollLeft) === this.schedulerContent.scrollWidth - this.schedulerContent.clientWidth && onScrollRight !== undefined) { - onScrollRight(schedulerData, this.schedulerContent, this.schedulerContent.scrollWidth - this.schedulerContent.clientWidth); - } - } else if (this.schedulerContent.scrollTop !== this.scrollTop) { - if (this.schedulerContent.scrollTop === 0 && onScrollTop !== undefined) { - onScrollTop(schedulerData, this.schedulerContent, this.schedulerContent.scrollHeight - this.schedulerContent.clientHeight); - } - if (Math.round(this.schedulerContent.scrollTop) === this.schedulerContent.scrollHeight - this.schedulerContent.clientHeight && onScrollBottom !== undefined) { - onScrollBottom(schedulerData, this.schedulerContent, this.schedulerContent.scrollHeight - this.schedulerContent.clientHeight); - } - } - this.scrollLeft = this.schedulerContent.scrollLeft; - this.scrollTop = this.schedulerContent.scrollTop; - }; - - onViewChange = e => { - const { onViewChange, schedulerData } = this.props; - const viewType = parseInt(e.target.value.charAt(0)); - const showAgenda = e.target.value.charAt(1) === '1'; - const isEventPerspective = e.target.value.charAt(2) === '1'; - onViewChange(schedulerData, { viewType, showAgenda, isEventPerspective }); - this.setState({ ...this.state, spinning: false }); - }; - - goNext = () => { - const { nextClick, schedulerData } = this.props; - nextClick(schedulerData); - }; - - goBack = () => { - const { prevClick, schedulerData } = this.props; - prevClick(schedulerData); - }; - - onSelect = date => { - const { onSelectDate, schedulerData } = this.props; - onSelectDate(schedulerData, date); - }; -} - -export { DATE_FORMAT, DATETIME_FORMAT, Scheduler, SchedulerData, ViewType, CellUnit, SummaryPos, DnDSource, DnDContext, AddMorePopover, DemoData, wrapperFun }; diff --git a/src/config/default.js b/src/config/default.js deleted file mode 100644 index 4114a1f..0000000 --- a/src/config/default.js +++ /dev/null @@ -1,11 +0,0 @@ -export const DATE_FORMAT = 'YYYY-MM-DD'; - -export const DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; - -export const ViewType = { Day: 0, Week: 1, Month: 2, Quarter: 3, Year: 4, Custom: 5, Custom1: 6, Custom2: 7 }; - -export const CellUnit = { Day: 0, Hour: 1, Week: 2, Month: 3, Year: 4 }; - -export const SummaryPos = { Top: 0, TopRight: 1, TopLeft: 2, Bottom: 3, BottomRight: 4, BottomLeft: 5 }; - -export const DnDTypes = { EVENT: 'event' }; diff --git a/src/config/default.ts b/src/config/default.ts new file mode 100644 index 0000000..09f054c --- /dev/null +++ b/src/config/default.ts @@ -0,0 +1,35 @@ +export const DATE_FORMAT = "YYYY-MM-DD" as const; + +export const DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss" as const; + +export enum ViewType { + Day = "DAY", + Week = "WEEK", + Month = "MONTH", + Quarter = "QUARTER", + Year = "YEAR", + Custom = "CUSTOM", + Custom1 = "CUSTOM1", + Custom2 = "CUSTOM2", +} + +export enum CellUnit { + Day = "DAY", + Hour = "HOUR", + Week = "WEEK", + Month = "MONTH", + Year = "YEAR", +} + +export enum SummaryPos { + Top = "TOP", + TopRight = "TOP_RIGHT", + TopLeft = "TOP_LEFT", + Bottom = "BOTTOM", + BottomRight = "BOTTOM_RIGHT", + BottomLeft = "BOTTOM_LEFT", +} + +export enum DnDTypes { + EVENT = "event", +} diff --git a/src/config/scheduler.js b/src/config/scheduler.js deleted file mode 100644 index 29234cc..0000000 --- a/src/config/scheduler.js +++ /dev/null @@ -1,96 +0,0 @@ -import { ViewType, SummaryPos } from './default'; - -export default { - schedulerWidth: '100%', - besidesWidth: 20, - schedulerMaxHeight: 0, - tableHeaderHeight: 40, - schedulerContentHeight: '500px', - - responsiveByParent: false, - - agendaResourceTableWidth: 160, - agendaMaxEventWidth: 100, - - dayResourceTableWidth: 160, - weekResourceTableWidth: '16%', - monthResourceTableWidth: 160, - quarterResourceTableWidth: 160, - yearResourceTableWidth: 160, - customResourceTableWidth: 160, - - dayCellWidth: 30, - weekCellWidth: '12%', - monthCellWidth: 80, - quarterCellWidth: 80, - yearCellWidth: 80, - customCellWidth: 80, - - dayMaxEvents: 99, - weekMaxEvents: 99, - monthMaxEvents: 99, - quarterMaxEvents: 99, - yearMaxEvents: 99, - customMaxEvents: 99, - - eventItemPopoverTrigger: 'hover', - eventItemPopoverPlacement: 'bottomLeft', - eventItemPopoverWidth: 300, - - eventItemHeight: 22, - eventItemLineHeight: 24, - nonAgendaSlotMinHeight: 0, - dayStartFrom: 0, - dayStopTo: 23, - defaultEventBgColor: '#80C5F6', - selectedAreaColor: '#7EC2F3', - nonWorkingTimeHeadColor: '#999999', - nonWorkingTimeHeadBgColor: '#fff0f6', - nonWorkingTimeBodyBgColor: '#fff0f6', - summaryColor: '#666', - summaryPos: SummaryPos.TopRight, - groupOnlySlotColor: '#F8F8F8', - - startResizable: true, - endResizable: true, - movable: true, - creatable: true, - crossResourceMove: true, - checkConflict: false, - scrollToSpecialDayjsEnabled: true, - eventItemPopoverEnabled: true, - eventItemPopoverShowColor: true, - calendarPopoverEnabled: true, - recurringEventsEnabled: true, - viewChangeSpinEnabled: true, - dateChangeSpinEnabled: true, - headerEnabled: true, - resourceViewEnabled: true, - displayWeekend: true, - relativeMove: true, - defaultExpanded: true, - dragAndDropEnabled: true, - - schedulerHeaderEventsFuncsTimeoutMs: 100, - - resourceName: 'Resource Name', - taskName: 'Task Name', - agendaViewHeader: 'Agenda', - addMorePopoverHeaderFormat: 'MMM D, YYYY dddd', - eventItemPopoverDateFormat: 'MMM D', - nonAgendaDayCellHeaderFormat: 'ha', - nonAgendaWeekCellHeaderFormat: 'ww/YYYY', - nonAgendaMonthCellHeaderFormat: 'MMM YYYY', - nonAgendaYearCellHeaderFormat: 'YYYY', - nonAgendaOtherCellHeaderFormat: 'ddd M/D', - - minuteStep: 30, - - views: [ - { viewName: 'Day', viewType: ViewType.Day, showAgenda: false, isEventPerspective: false }, - { viewName: 'Week', viewType: ViewType.Week, showAgenda: false, isEventPerspective: false }, - { viewName: 'Month', viewType: ViewType.Month, showAgenda: false, isEventPerspective: false }, - { viewName: 'Quarter', viewType: ViewType.Quarter, showAgenda: false, isEventPerspective: false }, - { viewName: 'Year', viewType: ViewType.Year, showAgenda: false, isEventPerspective: false }, - ], -}; diff --git a/src/config/scheduler.ts b/src/config/scheduler.ts new file mode 100644 index 0000000..59e2251 --- /dev/null +++ b/src/config/scheduler.ts @@ -0,0 +1,131 @@ +import { SchedulerDataConfig } from "../types/baseType"; +import { ViewType, SummaryPos } from "./default"; + +export interface View { + viewName: string; + viewType: ViewType; + showAgenda: boolean; + isEventPerspective: boolean; +} + +export const config: SchedulerDataConfig = { + schedulerWidth: "100%", + besidesWidth: 20, + schedulerMaxHeight: 0, + tableHeaderHeight: 40, + schedulerContentHeight: "500px", + + responsiveByParent: false, + + agendaResourceTableWidth: 160, + agendaMaxEventWidth: 100, + + dayResourceTableWidth: 160, + weekResourceTableWidth: "16%", + monthResourceTableWidth: 160, + quarterResourceTableWidth: 160, + yearResourceTableWidth: 160, + customResourceTableWidth: 160, + + dayCellWidth: 30, + weekCellWidth: "12%", + monthCellWidth: 45, + quarterCellWidth: 80, + yearCellWidth: 80, + customCellWidth: 80, + + dayMaxEvents: 99, + weekMaxEvents: 999, + monthMaxEvents: 99, + quarterMaxEvents: 99, + yearMaxEvents: 99, + customMaxEvents: 99, + + eventItemPopoverTrigger: "hover", + eventItemPopoverPlacement: "bottomLeft", + eventItemPopoverWidth: 300, + + eventItemHeight: 60, + eventItemLineHeight: 80, + nonAgendaSlotMinHeight: 0, + dayStartFrom: 0, + dayStopTo: 23, + defaultEventBgColor: "#80C5F6", + selectedAreaColor: "#7EC2F3", + nonWorkingTimeHeadColor: "#999999", + nonWorkingTimeHeadBgColor: "#fff0f6", + nonWorkingTimeBodyBgColor: "#fff0f6", + summaryColor: "#666", + summaryPos: SummaryPos.TopRight, + groupOnlySlotColor: "#F8F8F8", + + startResizable: true, + endResizable: true, + movable: true, + creatable: true, + crossResourceMove: true, + checkConflict: false, + scrollToSpecialDayjsEnabled: true, + eventItemPopoverEnabled: true, + eventItemPopoverShowColor: true, + calendarPopoverEnabled: true, + recurringEventsEnabled: true, + viewChangeSpinEnabled: true, + dateChangeSpinEnabled: true, + headerEnabled: true, + resourceViewEnabled: true, + displayWeekend: true, + relativeMove: true, + defaultExpanded: true, + dragAndDropEnabled: true, + + schedulerHeaderEventsFuncsTimeoutMs: 100, + + resourceName: "Resource Name", + taskName: "Task Name", + agendaViewHeader: "Agenda", + addMorePopoverHeaderFormat: "MMM D, YYYY dddd", + eventItemPopoverDateFormat: "MMM D", + nonAgendaDayCellHeaderFormat: "ha", + nonAgendaWeekCellHeaderFormat: "ww/YYYY", + nonAgendaMonthCellHeaderFormat: "MMM YYYY", + nonAgendaYearCellHeaderFormat: "YYYY", + nonAgendaOtherCellHeaderFormat: "ddd M/D", + + minuteStep: 30, + + marginTop: 0, + fixedResourceTableWidth: false, + views: [ + { + viewName: "Day", + viewType: ViewType.Day, + showAgenda: false, + isEventPerspective: false, + }, + { + viewName: "Week", + viewType: ViewType.Week, + showAgenda: false, + isEventPerspective: false, + }, + { + viewName: "Month", + viewType: ViewType.Month, + showAgenda: false, + isEventPerspective: false, + }, + { + viewName: "Quarter", + viewType: ViewType.Quarter, + showAgenda: false, + isEventPerspective: false, + }, + { + viewName: "Year", + viewType: ViewType.Year, + showAgenda: false, + isEventPerspective: false, + }, + ], +}; diff --git a/src/css/style.css b/src/css/style.css index fa0e5b3..b759890 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -82,6 +82,7 @@ table.resource-table, table.scheduler-bg-table, table.scheduler-table { + /* scrollbar-width: none; */ width: 100%; margin: 0; padding: 0; diff --git a/src/helper/behaviors.js b/src/helper/behaviors.js deleted file mode 100644 index f1a56f8..0000000 --- a/src/helper/behaviors.js +++ /dev/null @@ -1,85 +0,0 @@ -import { ViewType, CellUnit } from '../config/default'; - -// getSummary func example -// export const getSummary = (schedulerData, headerEvents, slotId, slotName, headerStart, headerEnd) => ({ text: 'Summary', color: 'red', fontSize: '1.2rem' }); -export const getSummary = () => ({ text: 'Summary', color: 'red', fontSize: '1.2rem' }); - -// getCustomDate example -export const getCustomDate = (schedulerData, num, date = schedulerData.startDate) => { - const { viewType, localeDayjs } = schedulerData; - let startDate; - let endDate; - let cellUnit; - - if (viewType === ViewType.Custom1) { - const monday = localeDayjs(new Date(date)).startOf('week'); - startDate = num === 0 ? monday : localeDayjs(new Date(monday)).add(2 * num, 'weeks'); - endDate = localeDayjs(new Date(startDate)).add(1, 'weeks').endOf('week'); - cellUnit = CellUnit.Day; - } else if (viewType === ViewType.Custom2) { - const firstDayOfMonth = localeDayjs(new Date(date)).startOf('month'); - startDate = num === 0 ? firstDayOfMonth : localeDayjs(new Date(firstDayOfMonth)).add(2 * num, 'months'); - endDate = localeDayjs(new Date(startDate)).add(1, 'months').endOf('month'); - cellUnit = CellUnit.Day; - } else { - startDate = num === 0 ? date : localeDayjs(new Date(date)).add(2 * num, 'days'); - endDate = localeDayjs(new Date(startDate)).add(1, 'days'); - cellUnit = CellUnit.Hour; - } - - return { startDate, endDate, cellUnit }; -}; - -// getNonAgendaViewBodyCellBgColor example -export const getNonAgendaViewBodyCellBgColor = (schedulerData, slotId, header) => (header.nonWorkingTime ? undefined : '#87e8de'); - -// getDateLabel func example -export const getDateLabel = (schedulerData, viewType, startDate, endDate) => { - const { localeDayjs } = schedulerData; - const start = localeDayjs(new Date(startDate)); - const end = localeDayjs(endDate); - let dateLabel = ''; - - if (viewType === ViewType.Week || (start !== end && (viewType === ViewType.Custom || viewType === ViewType.Custom1 || viewType === ViewType.Custom2))) { - dateLabel = `${start.format('MMM D')}-${end.format('D, YYYY')}`; - if (start.month() !== end.month()) dateLabel = `${start.format('MMM D')}-${end.format('MMM D, YYYY')}`; - if (start.year() !== end.year()) dateLabel = `${start.format('MMM D, YYYY')}-${end.format('MMM D, YYYY')}`; - } else if (viewType === ViewType.Month) { - dateLabel = start.format('MMMM YYYY'); - } else if (viewType === ViewType.Quarter) { - dateLabel = `${start.format('MMM D')}-${end.format('MMM D, YYYY')}`; - } else if (viewType === ViewType.Year) { - dateLabel = start.format('YYYY'); - } else { - dateLabel = start.format('MMM D, YYYY'); - } - - return dateLabel; -}; - -export const getEventText = (schedulerData, event) => (schedulerData.isEventPerspective ? schedulerData.resources.find(item => item.id === event.resourceId)?.name || event.title : event.title); - -export const getScrollSpecialDayjs = schedulerData => { - const { localeDayjs } = schedulerData; - return localeDayjs(new Date()); -}; - -export const isNonWorkingTime = (schedulerData, time) => { - const { localeDayjs, cellUnit } = schedulerData; - if (cellUnit === CellUnit.Hour) { - const hour = localeDayjs(new Date(time)).hour(); - return hour < 9 || hour > 18; - } - const dayOfWeek = localeDayjs(new Date(time)).weekday(); - return dayOfWeek === 0 || dayOfWeek === 6; -}; - -export default { - getSummaryFunc: undefined, - getCustomDateFunc: undefined, - getNonAgendaViewBodyCellBgColorFunc: undefined, - getScrollSpecialDayjsFunc: getScrollSpecialDayjs, - getDateLabelFunc: getDateLabel, - getEventTextFunc: getEventText, - isNonWorkingTimeFunc: isNonWorkingTime, -}; diff --git a/src/helper/behaviors.ts b/src/helper/behaviors.ts new file mode 100644 index 0000000..fb0f317 --- /dev/null +++ b/src/helper/behaviors.ts @@ -0,0 +1,151 @@ +import { Dayjs } from "dayjs"; +import { ViewType, CellUnit } from "../config/default"; +import { SchedulerData } from "../components/Scheduler"; +import { EventItemType, HeaderEvent, HeadersType } from "../types/baseType"; + +// getSummary func example +// export const getSummary = (schedulerData, headerEvents, slotId, slotName, headerStart, headerEnd) => ({ text: 'Summary', color: 'red', fontSize: '1.2rem' }); +export const getSummary = () => ({ + text: "Summary", + color: "red", + fontSize: "1.2rem", +}); + +// getCustomDate example +export const getCustomDate = ( + schedulerData: SchedulerData, + num: number, + date = schedulerData.startDate +) => { + const { viewType, localeDayjs } = schedulerData; + let startDate; + let endDate; + let cellUnit; + + if (viewType === ViewType.Custom1) { + const monday = date.startOf("week"); + startDate = num === 0 ? monday : monday.add(2 * num, "weeks"); + endDate = startDate.add(1, "weeks").endOf("week"); + cellUnit = CellUnit.Day; + } else if (viewType === ViewType.Custom2) { + const firstDayOfMonth = date.startOf("month"); + startDate = + num === 0 ? firstDayOfMonth : firstDayOfMonth.add(2 * num, "months"); + endDate = startDate.add(1, "months").endOf("month"); + cellUnit = CellUnit.Day; + } else { + startDate = num === 0 ? date : date.add(2 * num, "days"); + endDate = startDate.add(1, "days"); + cellUnit = CellUnit.Hour; + } + + return { startDate, endDate, cellUnit }; +}; + +// getNonAgendaViewBodyCellBgColor example +export const getNonAgendaViewBodyCellBgColor = ( + schedulerData: SchedulerData, + slotId: string, + header: HeadersType +) => (header.nonWorkingTime ? undefined : "#87e8de"); + +// getDateLabel func example +export const getDateLabel = ( + schedulerData: SchedulerData, + viewType: ViewType, + startDate: Dayjs, + endDate: Dayjs +) => { + const start = startDate; + const end = endDate; + let dateLabel = ""; + + if ( + viewType === ViewType.Week || + (start !== end && + (viewType === ViewType.Custom || + viewType === ViewType.Custom1 || + viewType === ViewType.Custom2)) + ) { + dateLabel = `${start.format("MMM D")}-${end.format("D, YYYY")}`; + if (start.month() !== end.month()) + dateLabel = `${start.format("MMM D")}-${end.format("MMM D, YYYY")}`; + if (start.year() !== end.year()) + dateLabel = `${start.format("MMM D, YYYY")}-${end.format("MMM D, YYYY")}`; + } else if (viewType === ViewType.Month) { + dateLabel = start.format("MMMM YYYY"); + } else if (viewType === ViewType.Quarter) { + dateLabel = `${start.format("MMM D")}-${end.format("MMM D, YYYY")}`; + } else if (viewType === ViewType.Year) { + dateLabel = start.format("YYYY"); + } else { + dateLabel = start.format("MMM D, YYYY"); + } + + return dateLabel; +}; + +export const getEventText = (schedulerData: SchedulerData, event: EventItemType) => + schedulerData.isEventPerspective + ? schedulerData.resources.find((item) => item.id === event.resourceId) + ?.name || event.title + : event.title; + +export const getScrollSpecialDayjs = (schedulerData: SchedulerData) => { + const { localeDayjs } = schedulerData; + return localeDayjs(new Date()); +}; + +export const isNonWorkingTime = ( + schedulerData: SchedulerData, + time: string +) => { + const { localeDayjs, cellUnit } = schedulerData; + if (cellUnit === CellUnit.Hour) { + const hour = localeDayjs(new Date(time)).hour(); + return hour < 9 || hour > 18; + } + const dayOfWeek = localeDayjs(new Date(time)).weekday(); + return dayOfWeek === 0 || dayOfWeek === 6; +}; + +const defaultBehaviors: Behaviors = { + getSummaryFunc: undefined, + getCustomDateFunc: undefined, + getNonAgendaViewBodyCellBgColorFunc: undefined, + getScrollSpecialDayjsFunc: getScrollSpecialDayjs, + getDateLabelFunc: getDateLabel, + getEventTextFunc: getEventText, + isNonWorkingTimeFunc: isNonWorkingTime, +}; + +export type Behaviors = { + getSummaryFunc?: ( + schedulerData: SchedulerData, + headerEvents: HeaderEvent[], + slotId: string, + slotName: string, + headerStart: Dayjs, + headerEnd: Dayjs + ) => { text: string; color: string; fontSize: string }; + getCustomDateFunc?: ( + schedulerData: SchedulerData, + num: number, + date?: Dayjs + ) => { + startDate: Dayjs; + endDate: Dayjs; + cellUnit: CellUnit; + }; + getNonAgendaViewBodyCellBgColorFunc?: ( + schedulerData: SchedulerData, + slotId: string, + header: HeadersType + ) => string; + getScrollSpecialDayjsFunc: (schedulerData: SchedulerData) => Dayjs; + getDateLabelFunc: typeof getDateLabel; + getEventTextFunc: typeof getEventText; + isNonWorkingTimeFunc: typeof isNonWorkingTime; +}; + +export default defaultBehaviors; diff --git a/src/helper/utility.js b/src/helper/utility.ts similarity index 95% rename from src/helper/utility.js rename to src/helper/utility.ts index ee05b1f..b8d47dc 100644 --- a/src/helper/utility.js +++ b/src/helper/utility.ts @@ -1,3 +1,5 @@ +// @ts-nocheck + function getPos(element) { let x = 0; let y = 0; diff --git a/src/index.js b/src/index.ts similarity index 84% rename from src/index.js rename to src/index.ts index 5bd85d3..b600a58 100644 --- a/src/index.js +++ b/src/index.ts @@ -11,4 +11,4 @@ export { AddMorePopover, DemoData, wrapperFun, -} from './components/index'; +} from './components/Scheduler'; diff --git a/src/main.jsx b/src/main.tsx similarity index 100% rename from src/main.jsx rename to src/main.tsx diff --git a/src/types/baseType.ts b/src/types/baseType.ts new file mode 100644 index 0000000..37fb42d --- /dev/null +++ b/src/types/baseType.ts @@ -0,0 +1,314 @@ +import { Dayjs } from "dayjs"; +import { SummaryPos, ViewType } from "../config/default"; + +export interface View { + viewName?: string; + viewType: ViewType; + showAgenda: boolean; + isEventPerspective: boolean; +} + +export interface EventGroup { + id: string; + name: string; + state: EventType; +} + +export interface SummaryType { + text: string; + color: string; + fontSize: string; +} + +export interface SlotMapItem { + data?: RenderDataItem; + children: any[]; +} + +export interface RenderDataItem { + slotId: string; + slotName: string; + slotTitle: any; + parentId?: string; + groupOnly?: boolean; + hasSummary?: boolean; + rowMaxCount: number; + rowHeight: number; + headerItems: HeaderEventsType[]; + indent: number; + hasChildren: boolean; + expanded: boolean; + render: boolean; +} + +export interface ResourceEvent { + id: number; + name: string; + parentId?: string; + groupOnly?: boolean; + hasSummary?: boolean; + expanded?: boolean; + headerItems?: EventItemType[]; + render?: boolean; + rowHeight: number; + rowMaxCount: number; +} + +// Must keep +export interface HeaderEventsType { + time: string; + nonWorkingTime?: boolean; + start: Dayjs; + end: Dayjs; + count: number; + addMore: number; + addMoreIndex: number; + events: HeaderEvent[]; + summary?: SummaryType; +} + +export interface EventItemType { + id: number | string; + start: Dayjs; + end: Dayjs; + resourceId: string; + title: string; + bgColor?: string; + rrule?: string; + showPopover?: boolean; + resizable?: boolean; + movable?: boolean; + startResizable?: boolean; + endResizable?: boolean; + groupId?: string; + groupName?: string; + /** + * @deprecated This property should not be used as EXRULE has been [deprecated in RFC 5545](https://icalendar.org/iCalendar-RFC-5545/a-3-deprecated-features.html) and does not support a DTSTART property + */ + exrule?: string; + exdates?: string[]; + [x: string]: unknown; +} + +export interface Resource { + id: string; + name: string; + parentId?: string; + groupOnly?: boolean; +} + +export type HeadersType = { + time: string; + nonWorkingTime?: boolean; +}; + +export interface HeaderItem { + time: string; + start: string; + end: string; + addMore: number; + addMoreIndex: number; + count: number; + nonWorkingTime: boolean; + events: EventItemType[]; +} + +export interface HeaderEvent { + render: boolean; + span: number; + eventItem: EventItemType; +} + +export interface State { + headerItem: HeaderItem; + left: number; + top: number; + height: number; +} + +export interface SchedulerDataConfig { + schedulerWidth: string; + besidesWidth: number; + schedulerMaxHeight: number; + tableHeaderHeight: number; + schedulerContentHeight: string | number; + agendaResourceTableWidth: string | number; + agendaMaxEventWidth: number; + dayResourceTableWidth: string | number; + weekResourceTableWidth: string | number; + monthResourceTableWidth: string | number; + quarterResourceTableWidth: string | number; + yearResourceTableWidth: string | number; + customResourceTableWidth: string | number; + dayCellWidth: string | number; + weekCellWidth: string | number; + monthCellWidth: string | number; + quarterCellWidth: string | number; + yearCellWidth: string | number; + customCellWidth: string | number; + dayMaxEvents: number; + weekMaxEvents: number; + monthMaxEvents: number; + quarterMaxEvents: number; + yearMaxEvents: number; + customMaxEvents: number; + eventItemPopoverTrigger: "hover" | "click"; + eventItemPopoverPlacement: + | "topLeftMousePosition" + | "bottomLeftMousePosition" + | "topRightMousePosition" + | "bottomRightMousePosition" + | "top" + | "left" + | "right" + | "bottom" + | "topLeft" + | "topRight" + | "bottomLeft" + | "bottomRight" + | "leftTop" + | "leftBottom" + | "rightTop" + | "rightBottom"; + eventItemPopoverWidth: number; + eventItemHeight: number; + eventItemLineHeight: number; + nonAgendaSlotMinHeight: number; + dayStartFrom: number; + dayStopTo: number; + defaultEventBgColor: string; + selectedAreaColor: string; + nonWorkingTimeHeadColor: string; + nonWorkingTimeHeadBgColor: string; + nonWorkingTimeBodyBgColor: string; + summaryColor: string; + summaryPos: SummaryPos; + groupOnlySlotColor: string; + startResizable: boolean; + endResizable: boolean; + movable: boolean; + creatable: boolean; + crossResourceMove: boolean; + checkConflict: boolean; + scrollToSpecialDayjsEnabled: boolean; + eventItemPopoverEnabled: boolean; + eventItemPopoverShowColor: boolean; + calendarPopoverEnabled: boolean; + recurringEventsEnabled: boolean; + viewChangeSpinEnabled: boolean; + dateChangeSpinEnabled: boolean; + headerEnabled: boolean; + resourceViewEnabled: boolean; + displayWeekend: boolean; + relativeMove: boolean; + defaultExpanded: boolean; + dragAndDropEnabled: boolean; + schedulerHeaderEventsFuncsTimeoutMs: number; + resourceName: string; + taskName: string; + agendaViewHeader: string; + addMorePopoverHeaderFormat: string; + eventItemPopoverDateFormat: string; + nonAgendaDayCellHeaderFormat: string; + nonAgendaWeekCellHeaderFormat: string; + nonAgendaMonthCellHeaderFormat: string; + nonAgendaYearCellHeaderFormat: string; + nonAgendaOtherCellHeaderFormat: string; + minuteStep: number; + views: View[]; + responsiveByParent: boolean; + marginTop: number; + fixedResourceTableWidth: boolean; +} + +export interface SchedulerDataConfigOptional { + schedulerWidth?: string; + besidesWidth?: number; + schedulerMaxHeight?: number; + tableHeaderHeight?: number; + schedulerContentHeight?: string | number; + agendaResourceTableWidth?: string | number; + agendaMaxEventWidth?: number; + dayResourceTableWidth?: string | number; + weekResourceTableWidth?: string | number; + monthResourceTableWidth?: string | number; + quarterResourceTableWidth?: string | number; + yearResourceTableWidth?: string | number; + customResourceTableWidth?: string | number; + dayCellWidth?: string | number; + weekCellWidth?: string | number; + monthCellWidth?: string | number; + quarterCellWidth?: string | number; + yearCellWidth?: string | number; + customCellWidth?: string | number; + dayMaxEvents?: number; + weekMaxEvents?: number; + monthMaxEvents?: number; + quarterMaxEvents?: number; + yearMaxEvents?: number; + customMaxEvents?: number; + eventItemPopoverTrigger?: "hover" | "click"; + eventItemPopoverPlacement?: + | "topLeftMousePosition" + | "bottomLeftMousePosition" + | "topRightMousePosition" + | "bottomRightMousePosition" + | "top" + | "left" + | "right" + | "bottom" + | "topLeft" + | "topRight" + | "bottomLeft" + | "bottomRight" + | "leftTop" + | "leftBottom" + | "rightTop" + | "rightBottom"; + eventItemPopoverWidth?: number; + eventItemHeight?: number; + eventItemLineHeight?: number; + nonAgendaSlotMinHeight?: number; + dayStartFrom?: number; + dayStopTo?: number; + defaultEventBgColor?: string; + selectedAreaColor?: string; + nonWorkingTimeHeadColor?: string; + nonWorkingTimeHeadBgColor?: string; + nonWorkingTimeBodyBgColor?: string; + summaryColor?: string; + summaryPos?: SummaryPos; + groupOnlySlotColor?: string; + startResizable?: boolean; + endResizable?: boolean; + movable?: boolean; + creatable?: boolean; + crossResourceMove?: boolean; + checkConflict?: boolean; + scrollToSpecialDaysjsEnabled?: boolean; + eventItemPopoverEnabled?: boolean; + calendarPopoverEnabled?: boolean; + recurringEventsEnabled?: boolean; + viewChangeSpinEnabled?: boolean; + dateChangeSpinEnabled?: boolean; + headerEnabled?: boolean; + scrollToSpecialDayjsEnabled?: boolean; + resourceViewEnabled?: boolean; + displayWeekend?: boolean; + relativeMove?: boolean; + defaultExpanded?: boolean; + dragAndDropEnabled?: boolean; + schedulerHeaderEventsFuncsTimeoutMs?: number; + resourceName?: string; + taskName?: string; + agendaViewHeader?: string; + addMorePopoverHeaderFormat?: string; + eventItemPopoverDateFormat?: string; + nonAgendaDayCellHeaderFormat?: string; + nonAgendaOtherCellHeaderFormat?: string; + minuteStep?: number; + views?: View[]; + responsiveByParent?: boolean; + marginTop?: number; + fixedResourceTableWidth?: boolean; +} diff --git a/src/types/moreTypes.ts b/src/types/moreTypes.ts new file mode 100644 index 0000000..fec5d62 --- /dev/null +++ b/src/types/moreTypes.ts @@ -0,0 +1,301 @@ +import { ConfigType, Dayjs, OptionType } from "dayjs"; +import React, { CSSProperties } from "react"; +import { + EventItemType, + HeaderEvent, + HeaderEventsType, + HeaderItem, + HeadersType, + RenderDataItem, + ResourceEvent, + State, + View, +} from "./baseType"; +import { + CellUnit, + DnDSource, + SchedulerData, + ViewType, +} from "../components/Scheduler"; + +export type EventItemPopoverTemplateResolverFunc = ( + schedulerData: SchedulerData, + event: EventItemType, + title: string, + start: Dayjs, + end: Dayjs, + statusColor: string +) => React.ReactNode; + +export type SubtitleGetterFunc = ( + schedulerData: SchedulerData, + event: EventItemType +) => void; + +export type ViewEventClickFunc = ( + schedulerData: SchedulerData, + event: EventItemType +) => void; + +export type ViewEvent2ClickFunc = ( + schedulerData: SchedulerData, + event: EventItemType +) => void; + +export type UpdateEventStartFunc = ( + schedulerData: SchedulerData, + event: EventItemType, + newStart: Dayjs +) => void; + +export type UpdateEventEndFunc = ( + schedulerData: SchedulerData, + event: EventItemType, + newEnd: Dayjs +) => void; + +export type MoveEventFunc = ( + schedulerData: SchedulerData, + event: EventItemType, + slotId: string, + slotName: string, + start: Dayjs, + end: Dayjs +) => void; + +export type NewEventFunc = ( + schedulerData: SchedulerData, + slotId: string, + slotName: string, + start: Dayjs, + end: Dayjs, + type: string, + item: EventItemType +) => void; + +export type OnScrollLeftFunc = ( + schedulerData: SchedulerData, + // Questionable type was ReactNode + schedulerContent: HTMLDivElement, + maxScrollLeft: number +) => void; + +export type OnScrollRightFunc = ( + schedulerData: SchedulerData, + // Questionable type was ReactNode + schedulerContent: HTMLDivElement, + maxScrollLeft: number +) => void; + +export type OnScrollTopFunc = ( + schedulerData: SchedulerData, + // Questionable type was ReactNode + + schedulerContent: HTMLDivElement, + maxScrollTop: number +) => void; + +export type OnScrollBottomFunc = ( + schedulerData: SchedulerData, + // Questionable type was ReactNode + schedulerContent: HTMLDivElement, + maxScrollTop: number +) => void; + +export type OnSetAddMoreStateFunc = (newState: State) => void; + +export type ConflictOccurredFunc = ( + schedulerData: SchedulerData, + action: string, + item: EventItemType, + type: string, + slotId: string, + slotName: string, + newStart: Dayjs, + newEnd: Dayjs +) => void; + +export type NonAgendaCellHeaderTemplateResolverFunc = ( + schedulerData: SchedulerData, + item: HeadersType, + formattedDateItems: Dayjs, + style: CSSProperties +) => React.ReactNode; + +export type MovingEventFunc = ( + schedulerData: SchedulerData, + slotId: string, + slotName: string, + newStart: Dayjs, + newEnd: Dayjs, + action: string, + type: string, + item: EventItemType +) => void; + +export type SlotClickedFunc = ( + schedulerData: SchedulerData, + slot: RenderDataItem +) => void; + +export type SlotItemTemplateResolverFunc = ( + schedulerData: SchedulerData, + slot: RenderDataItem, + slotClickedFunc: SlotClickedFunc | undefined, + width: number, + clsName: string +) => React.ReactNode; + +export type EventItemClickFunc = ( + schedulerData: SchedulerData, + event: EventItemType +) => void; + +export type PrevClickFunc = (schedulerData: SchedulerData) => void; +export type NextClickFunc = (schedulerData: SchedulerData) => void; +export type OnSelectDateFunc = ( + schedulerData: SchedulerData, + date: Dayjs +) => void; +export type OnViewChangeFunc = ( + schedulerData: SchedulerData, + view: View +) => void; + +export type EventItemTemplateResolverFunc = ( + schedulerData: SchedulerData, + event: EventItemType, + bgColor: string, + isStart: boolean, + isEnd: boolean, + mustAddCssClass: string, + mustBeHeight: number, + agendaMaxEventWidth: number, + classNames: string +) => React.ReactNode; + +export type ToggleExpandFunc = ( + schedulerData: SchedulerData, + slotId: string +) => void; + +type SchedulerHeaderTemplateResolverFunc = ({ + onViewChange, + schedulerData, + onSelectDate, + goNext, + goBack, + rightCustomHeader, + leftCustomHeader, +}: { + onViewChange: (e: React.ChangeEvent) => void; + schedulerData: SchedulerData; + onSelectDate: (date: Dayjs) => void; + goNext: () => void; + goBack: () => void; + rightCustomHeader: React.ReactNode; + leftCustomHeader: React.ReactNode; +}) => React.ReactNode; + +export interface SchedulerProps { + parentRef?: React.RefObject; + schedulerData: SchedulerData; + prevClick: PrevClickFunc; + nextClick: NextClickFunc; + onViewChange: OnViewChangeFunc; + onSelectDate: OnSelectDateFunc; + onSetAddMoreState?: OnSetAddMoreStateFunc; + updateEventStart?: UpdateEventStartFunc; + updateEventEnd?: UpdateEventEndFunc; + moveEvent?: MoveEventFunc; + movingEvent?: MovingEventFunc; + leftCustomHeader?: React.ReactNode; + rightCustomHeader?: React.ReactNode; + newEvent?: NewEventFunc; + subtitleGetter?: SubtitleGetterFunc; + eventItemClick?: EventItemClickFunc; + viewEventClick?: ViewEventClickFunc; + viewEventText?: string; + viewEvent2Click?: ViewEvent2ClickFunc; + viewEvent2Text?: string; + conflictOccurred?: ConflictOccurredFunc; + eventItemTemplateResolver?: EventItemTemplateResolverFunc; + dndSources?: DnDSource[]; + slotClickedFunc?: SlotClickedFunc; + toggleExpandFunc?: ToggleExpandFunc; + slotItemTemplateResolver?: SlotItemTemplateResolverFunc; + nonAgendaCellHeaderTemplateResolver?: NonAgendaCellHeaderTemplateResolverFunc; + schedulerHeaderTemplateResolver?: SchedulerHeaderTemplateResolverFunc; + onScrollLeft?: OnScrollLeftFunc; + onScrollRight?: OnScrollRightFunc; + onScrollTop?: OnScrollTopFunc; + onScrollBottom?: OnScrollBottomFunc; +} + +export type CloseActionFunc = (newState?: State) => void; + +export interface AddMorePopoverProps< + EventType extends EventItemType = EventItemType, +> { + schedulerData: SchedulerData; + headerItem: HeaderItem; + left: number; + top: number; + height: number; + closeAction: CloseActionFunc; + subtitleGetter?: SchedulerProps["subtitleGetter"]; + moveEvent?: SchedulerProps["moveEvent"]; + eventItemClick?: SchedulerProps["eventItemClick"]; + viewEventClick?: SchedulerProps["viewEventClick"]; + viewEventText?: string; + viewEvent2Text?: string; + viewEvent2Click?: SchedulerProps["viewEvent2Click"]; + eventItemTemplateResolver?: SchedulerProps["eventItemTemplateResolver"]; +} + +export interface SchedulerDataBehaviors< + EventType extends EventItemType = EventItemType, +> { + isNonWorkingTimeFunc?: ( + schedulerData: SchedulerData, + time: string + ) => boolean; + getCustomDateFunc?: ( + schedulerData: SchedulerData, + num: number, + date?: string | Dayjs + ) => { + startDate: Dayjs; + endDate: Dayjs; + cellUnit: CellUnit; + }; + getEventTextFunc?: (schedulerData: SchedulerData, event: EventType) => string; + getDateLabel?: ( + schedulerData: SchedulerData, + viewType: ViewType, + startDate: Date, + endDate: Date + ) => string; + getScrollSpecialDayjs?: ( + schedulerData: SchedulerData, + startDayjs: Dayjs, + endDays: Dayjs + ) => Dayjs; + getSummaryFunc?: ( + schedulerData: SchedulerData, + headerEvents: HeaderEvent[], + slotId: string, + slotName: string, + headerStart: string, + headerEnd: string + ) => { text: string; color: string; fontSize: string }; + getNonAgendaViewBodyCellBgColorFunc?: ( + schedulerData: SchedulerData, + slotId: string, + header: { nonWorkingTime: boolean; time: string } + ) => string; +} + +export const DATE_FORMAT = "YYYY-MM-DD"; + +export const DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss"; diff --git a/typing/index.d.ts b/typing/index.d.ts deleted file mode 100644 index 35d55c6..0000000 --- a/typing/index.d.ts +++ /dev/null @@ -1,378 +0,0 @@ -import { ConfigType, Dayjs, OptionType } from 'dayjs'; -import React, { CSSProperties } from 'react'; - -export default class Scheduler extends React.Component, any> {} - -export const AddMorePopover: (props: AddMorePopoverProps) => React.ReactElement; - -export interface SchedulerProps { - schedulerData: SchedulerData; - prevClick(schedulerData: SchedulerData): void; - nextClick(schedulerData: SchedulerData): void; - onSelectDate(schedulerData: SchedulerData, date: string): void; - onViewChange(schedulerData: SchedulerData, view: View): void; - eventItemClick?: (schedulerData: SchedulerData, event: EventType) => void; - eventItemTemplateResolver?: ( - schedulerData: SchedulerData, - event: EventType, - bgColor: string, - isStart: boolean, - isEnd: boolean, - mustAddCssClass: string, - mustBeHeight: number, - agendaMaxEventWidth: number - ) => void; - eventItemPopoverTemplateResolver?: (schedulerData: SchedulerData, event: EventType, title: string, start: Dayjs, end: Dayjs, statusColor: string) => void; - toggleExpandFunc?: (schedulerData: SchedulerData, slotId: string) => void; - viewEventClick?: (schedulerData: SchedulerData, event: EventType) => void; - viewEventText?: string; - viewEvent2Text?: string; - viewEvent2Click?: (schedulerData: SchedulerData, event: EventType) => void; - updateEventStart?: (schedulerData: SchedulerData, event: EventType, newStart: string) => void; - updateEventEnd?: (schedulerData: SchedulerData, event: EventType, newEnd: string) => void; - moveEvent?: (schedulerData: SchedulerData, event: EventType, slotId: string, slotName: string, start: string, end: string) => void; - newEvent?: (schedulerData: SchedulerData, slotId: string, slotName: string, start: string, end: string, type: string, item: EventType) => void; - onScrollLeft?: (schedulerData: SchedulerData, schedulerContent: React.ReactNode, maxScrollLeft: number) => void; - onScrollRight?: (schedulerData: SchedulerData, schedulerContent: React.ReactNode, maxScrollLeft: number) => void; - onScrollTop?: (schedulerData: SchedulerData, schedulerContent: React.ReactNode, maxScrollTop: number) => void; - onScrollBottom?: (schedulerData: SchedulerData, schedulerContent: React.ReactNode, maxScrollTop: number) => void; - onSetAddMoreState?: (newState: State) => void; - conflictOccurred?: ( - schedulerData: SchedulerData, - action: string, - item: EventType, - type: string, - slotId: string, - slotName: string, - newStart: string, - newEnd: string - ) => void; - nonAgendaCellHeaderTemplateResolver?: ( - schedulerData: SchedulerData, - item: { time: string; nonWorkingTime: boolean }, - formattedDateItems: string[], - style: CSSProperties - ) => void; - subtitleGetter?: (schedulerData: SchedulerData, event: EventType) => void; - movingEvent?: ( - schedulerData: SchedulerData, - slotId: string, - slotName: string, - newStart: string, - newEnd: string, - action: string, - type: string, - item: EventType - ) => void; - slotClickedFunc?: (schedulerData: SchedulerData, slot: ResourceEvent) => void; - slotItemTemplateResolver?: ( - schedulerData: SchedulerData, - slot: ResourceEvent, - slotClickedFunc: (schedulerData: SchedulerData, slot: ResourceEvent) => void, - width: number, - clsName: string - ) => void; - leftCustomHeader?: React.ReactNode; - rightCustomHeader?: React.ReactNode; - dndSources?: DnDSource[]; - parentRef?: React.RefObject; -} - -export interface AddMorePopoverProps { - schedulerData: SchedulerData; - headerItem: HeaderItem; - left: number; - top: number; - height: number; - closeAction: (newState: State) => void; - subtitleGetter?: SchedulerProps['subtitleGetter']; - moveEvent?: SchedulerProps['moveEvent']; - eventItemClick?: SchedulerProps['eventItemClick']; - viewEventClick?: SchedulerProps['viewEventClick']; - viewEventText?: string; - viewEvent2Text?: string; - viewEvent2Click?: SchedulerProps['viewEvent2Click']; - eventItemTemplateResolver?: SchedulerProps['eventItemTemplateResolver']; -} - -export class SchedulerData { - localeDayjs(date?: ConfigType): Dayjs; - localeDayjs(date?: ConfigType, format?: OptionType, strict?: boolean): Dayjs; - localeDayjs(date?: ConfigType, format?: OptionType, locale?: string, strict?: boolean): Dayjs; - - cellUnit: CellUnit; - viewType: ViewType; - startDate: string; - config: SchedulerDataConfig; - resources: Resource[]; - events: EventType[]; - eventGroups: EventGroup[]; - eventGroupsAutoGenerated: boolean; - showAgenda: boolean; - isEventPerspective: boolean; - resizing: boolean; - scrollToSpecialDayjs: boolean; - documentWidth: number; - behaviors: SchedulerDataBehaviors; - - constructor( - date?: string | Dayjs, - viewType?: ViewType, - showAgenda?: boolean, - isEventPerspective?: boolean, - newConfig?: SchedulerDataConfig, - newBehaviours?: SchedulerDataBehaviors - ); - - setSchedulerLocale(preset: string | ILocale, object?: Partial): void; - setCalendarPopoverLocale(lang: string): void; - setResources(resources: Resource[]): void; - setEvents(events: EventType[]): void; - prev(): void; - next(): void; - setViewType(viewType?: ViewType, showAgenda?: boolean, isEventPerspective?: boolean): void; - setDate(date?: string): void; - setEventGroups(eventGroups: EventGroup[]): void; - setEventGroupsAutoGenerated(autoGenerated: boolean): void; - setMinuteStep(minuteStep: number): void; - getMinuteStepsInHour(): number; - addEventGroup(eventGroup: EventGroup): void; - updateEventStart(event: EventType, newStart: string): void; - updateEventEnd(event: EventType, newEnd: string): void; - moveEvent(event: EventType, newSlotId: string, newSlotName: string, newStart: string, newEnd: string): void; - getSlots(): EventGroup[] | Resource[]; - addResource(resource: Resource): void; - getSlotById(slotId: string): EventType; - toggleExpandStatus(slotId: string): void; - removeEventById(eventId: string): void; - removeEvent(event: EventType): void; - isEventInTimeWindow(eventStart: Date | Dayjs, eventEnd: Date | Dayjs, windowStart: Date | Dayjs, windowEnd: Date | Dayjs): boolean; - addEvent(newEvent: EventType): void; - getResourceById(resourceId: string): ResourceEvent; - getViewStartDate(): Dayjs; - getViewEndDate(): Dayjs; - getViewDates(): { startDate: Dayjs; endDate: Dayjs }; -} - -export class DnDContext { - constructor(sources: DnDSource[], DecoratedComponent: React.ReactNode); -} - -export class DnDSource { - constructor(resolveDragObjFunc: (props: {}) => any, DecoratedComponent: React.ReactNode, dragAnDropEnabled: boolean, dndType: string); -} - -export enum CellUnit { - Day, - Hour, -} - -export enum ViewType { - Day, - Week, - Month, - Quarter, - Year, - Custom, - Custom1, - Custom2, -} - -export interface View { - viewName?: string; - viewType: ViewType; - showAgenda: boolean; - isEventPerspective: boolean; -} - -export interface EventGroup { - id: string; - name: string; - state: EventType; -} - -export interface EventItem { - id: number | string; - start: string; - end: string; - resourceId: string; - title: string; - bgColor?: string; - rrule?: string; - showPopover?: boolean; - resizable?: boolean; - movable?: boolean; - startResizable?: boolean; - endResizable?: boolean; - groupId?: string; - groupName?: string; - /** - * @deprecated This property should not be used as EXRULE has been [deprecated in RFC 5545](https://icalendar.org/iCalendar-RFC-5545/a-3-deprecated-features.html) and does not support a DTSTART property - */ - exrule?: string; - exdates?: string[]; - [x: string]: unknown; -} - -export interface ResourceEvent { - id: number; - name: string; - parentId?: string; - groupOnly?: boolean; - hasSummary?: boolean; - expanded?: boolean; - headerItems?: EventType[]; - render?: boolean; - rowHeight: number; - rowMaxCount: number; -} - -export interface Resource { - id: string; - name: string; - parentId?: string; - groupOnly?: boolean; -} - -export interface HeaderItem { - time: string; - start: string; - end: string; - addMore: number; - addMoreIndex: number; - count: number; - nonWorkingTime: boolean; - events: EventType[]; -} - -export interface HeaderEvent { - render: boolean; - span: number; - eventItem: EventType; -} - -export interface State { - headerItem: HeaderItem; - left: number; - top: number; - height: number; -} - -export interface SchedulerDataConfig { - schedulerWidth?: `${number}%`; - besidesWidth?: number; - schedulerMaxHeight?: number; - tableHeaderHeight?: number; - schedulerContentHeight?: string | number; - agendaResourceTableWidth?: string | number; - agendaMaxEventWidth?: string | number; - dayResourceTableWidth?: string | number; - weekResourceTableWidth?: string | number; - monthResourceTableWidth?: string | number; - quarterResourceTableWidth?: string | number; - yearResourceTableWidth?: string | number; - customResourceTableWidth?: string | number; - dayCellWidth?: string | number; - weekCellWidth?: string | number; - monthCellWidth?: string | number; - quarterCellWidth?: string | number; - yearCellWidth?: string | number; - customCellWidth?: string | number; - dayMaxEvents?: number; - weekMaxEvents?: number; - monthMaxEvents?: number; - quarterMaxEvents?: number; - yearMaxEvents?: number; - customMaxEvents?: number; - eventItemPopoverTrigger?: 'hover' | 'click'; - eventItemPopoverPlacement?: - | 'topLeftMousePosition' - | 'bottomLeftMousePosition' - | 'topRightMousePosition' - | 'bottomRightMousePosition' - | 'top' - | 'left' - | 'right' - | 'bottom' - | 'topLeft' - | 'topRight' - | 'bottomLeft' - | 'bottomRight' - | 'leftTop' - | 'leftBottom' - | 'rightTop' - | 'rightBottom'; - eventItemPopoverWidth?: number; - eventItemHeight?: number; - eventItemLineHeight?: number; - nonAgendaSlotMinHeight?: number; - dayStartFrom?: number; - dayStopTo?: number; - defaultEventBgColor?: string; - selectedAreaColor?: string; - nonWorkingTimeHeadColor?: string; - nonWorkingTimeHeadBgColor?: string; - nonWorkingTimeBodyBgColor?: string; - summaryColor?: string; - summaryPos?: SummaryPos; - groupOnlySlotColor?: string; - startResizable?: boolean; - endResizable?: boolean; - movable?: boolean; - creatable?: boolean; - crossResourceMove?: boolean; - checkConflict?: boolean; - scrollToSpecialDaysjsEnabled?: boolean; - eventItemPopoverEnabled?: boolean; - calendarPopoverEnabled?: boolean; - recurringEventsEnabled?: boolean; - viewChangeSpinEnabled?: boolean; - dateChangeSpinEnabled?: boolean; - headerEnabled?: boolean; - resourceViewEnabled?: boolean; - displayWeekend?: boolean; - relativeMove?: boolean; - defaultExpanded?: boolean; - dragAndDropEnabled?: boolean; - schedulerHeaderEventsFuncsTimeoutMs?: number; - resourceName?: string; - taskName?: string; - agendaViewHeader?: string; - addMorePopoverHeaderFormat?: string; - eventItemPopoverDateFormat?: string; - nonAgendaDayCellHeaderFormat?: string; - nonAgendaOtherCellHeaderFormat?: string; - minuteStep?: number; - views?: View[]; - responsiveByParent?: boolean; -} - -export enum SummaryPos { - Top, - TopRight, - TopLeft, - Bottom, - BottomRight, - BottomLeft, -} - -export interface SchedulerDataBehaviors { - isNonWorkingTimeFunc?: (schedulerData: SchedulerData, time: string) => boolean; - getCustomDateFunc?: (schedulerData: SchedulerData, num: number, date?: string | Dayjs) => { startDate: string | Dayjs; endDate: string | Dayjs; cellUnit: CellUnit }; - getEventTextFunc?: (schedulerData: SchedulerData, event: EventType) => string; - getDateLabel?: (schedulerData: SchedulerData, viewType: ViewType, startDate: string | Date, endDate: string | Date) => string; - getScrollSpecialDayjs?: (schedulerData: SchedulerData, startDayjs: Dayjs, endDays: Dayjs) => Dayjs; - getSummaryFunc?: ( - schedulerData: SchedulerData, - headerEvents: HeaderEvent[], - slotId: string, - slotName: string, - headerStart: string, - headerEnd: string - ) => { text: string; color: string; fontSize: string }; - getNonAgendaViewBodyCellBgColorFunc?: (schedulerData: SchedulerData, slotId: string, header: { nonWorkingTime: boolean; time: string }) => string; -} - -export const DATE_FORMAT = 'YYYY-MM-DD'; - -export const DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; From ca83f8ca446a67a84d8cb42efb405e86c1ed2df2 Mon Sep 17 00:00:00 2001 From: Pedro Roque Date: Thu, 12 Sep 2024 16:11:07 +0100 Subject: [PATCH 2/9] Added typescript package and supporting typescript configuration. --- .babelrc | 6 +++++- package.json | 7 ++++++- src/main.tsx | 7 ++++++- tsconfig.json | 24 ++++++++++++++++++++++++ webpack/webpack.dev.config.js | 15 ++++++++++----- 5 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 tsconfig.json diff --git a/.babelrc b/.babelrc index 2b7bafa..202d425 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,7 @@ { - "presets": ["@babel/preset-env", "@babel/preset-react"] + "presets": [ + "@babel/preset-env", + "@babel/preset-react", + "@babel/preset-typescript" + ] } diff --git a/package.json b/package.json index 6864dd7..352498d 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,9 @@ }, "dependencies": { "@ant-design/icons": "^5.3.0", + "@babel/preset-typescript": "^7.24.7", + "@types/react": "18.2.74", + "@types/react-dom": "18.2.23", "antd": "^5.14.1", "dayjs": "^1.11.10", "prop-types": "^15.8.1", @@ -67,7 +70,9 @@ "react-dnd": "^14.0.5", "react-dnd-html5-backend": "^14.1.0", "react-dom": "^18.2.0", - "rrule": "^2.8.1" + "rrule": "^2.8.1", + "ts-loader": "^9.5.1", + "typescript": "^5.6.2" }, "devDependencies": { "@babel/cli": "^7.23.9", diff --git a/src/main.tsx b/src/main.tsx index f996a7e..792198b 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,6 +1,11 @@ +// @ts-nocheck + import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './examples/AddMore'; import './css/style.css'; -ReactDOM.createRoot(document.getElementById('root')).render(); +const rootElement = document.getElementById('root'); +if (rootElement) { + ReactDOM.createRoot(rootElement).render(); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..249010a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Default", + "compilerOptions": { + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es2022", + "allowJs": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "checkJs": true, + + "lib": ["dom", "dom.iterable", "ES2022"], + "noEmit": true, + "module": "NodeNext", + "moduleResolution": "NodeNext", + "incremental": false, + "jsx": "react-jsx" + + } +} diff --git a/webpack/webpack.dev.config.js b/webpack/webpack.dev.config.js index f590e1e..109848b 100644 --- a/webpack/webpack.dev.config.js +++ b/webpack/webpack.dev.config.js @@ -4,7 +4,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const ESLintWebpackPlugin = require('eslint-webpack-plugin'); module.exports = { - entry: './src/examples/index.jsx', + entry: './src/examples/index.tsx', output: { path: path.resolve(__dirname, '..', 'dist'), filename: 'bundle.js', @@ -12,7 +12,7 @@ module.exports = { module: { rules: [ { - test: /\.(js|jsx)$/, + test: /\.(js|jsx|ts|tsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader' }, }, @@ -20,18 +20,23 @@ module.exports = { test: /\.css$/, use: ['style-loader', 'css-loader'], }, + { + test: /\.tsx?$/, + exclude: /node_modules/, + use: 'ts-loader', + }, ], }, resolve: { - extensions: ['.js', '.jsx'], + extensions: ['.js', '.jsx', '.ts', '.tsx'], }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', title: 'React Big Schedule' }), - new ESLintWebpackPlugin({ emitError: true, emitWarning: false, failOnError: true, extensions: ['js', 'jsx'] }), + new ESLintWebpackPlugin({ emitError: true, emitWarning: false, failOnError: true, extensions: ['js', 'jsx', 'ts', 'tsx'] }), ], devServer: { static: { directory: path.join(__dirname, '..', 'dist') }, compress: true, port: 8080, }, -}; +}; \ No newline at end of file From 89c421c96214925cf7b1c925af16b81e8cf211e0 Mon Sep 17 00:00:00 2001 From: Pedro Roque Date: Thu, 12 Sep 2024 16:20:07 +0100 Subject: [PATCH 3/9] Disabled checking JS files. --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 249010a..e139ffc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ "isolatedModules": true, "strict": true, "noUncheckedIndexedAccess": true, - "checkJs": true, + "checkJs": false, "lib": ["dom", "dom.iterable", "ES2022"], "noEmit": true, From 56d62e728f2119c290bd32fb60655505af4226f3 Mon Sep 17 00:00:00 2001 From: Pedro Roque Date: Thu, 12 Sep 2024 16:25:22 +0100 Subject: [PATCH 4/9] Added warning. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index e051986..eb38393 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ + +# Fork Warning + +This is a fork used internally by Muzz and is currently only used to visualize data. We can assure that the scheduler visualization is working. However, editing and modification of events are not used by us and have not been tested. Use at your own risk. + # React Big Schedule (react-big-schedule) [![NPM version][npm-image]][npm-url] [![MIT License][mit-image]][mit-url] [![CodeQL][codeql-image]][codeql-url] [![CodeFactor][codeFactor-badge]][codeFactor-link] From 6bbfe52b11c4c969850b1140522dc2dec654e0a0 Mon Sep 17 00:00:00 2001 From: Pedro Roque Date: Fri, 13 Sep 2024 16:54:35 +0100 Subject: [PATCH 5/9] Fix typing build issue --- scripts/build.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/build.js b/scripts/build.js index caa2ad1..3865448 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -19,7 +19,6 @@ async function build() { const root = path.resolve(__dirname, '..'); const sourceDir = path.resolve(root, 'src'); const targetDir = path.resolve(root, 'dist'); - const typingDir = path.resolve(root, 'typing'); const jsTarget = targetDir; const cssTarget = path.resolve(targetDir, 'css'); const excludedFolders = ['examples', 'main.jsx']; @@ -36,9 +35,6 @@ async function build() { process.stdout.write('Copying CSS Files... \n'); await fs.copy(`${sourceDir}/css/`, cssTarget); - process.stdout.write('Copying Typescript Files... \n'); - await fs.copy(`${typingDir}/`, targetDir); - process.stdout.write('Success! \n'); } catch (e) { console.log(e); From 197b4b30481e0f5f284b88f62b44e68d11a146fb Mon Sep 17 00:00:00 2001 From: Pedro Roque Date: Thu, 24 Oct 2024 17:26:33 +0100 Subject: [PATCH 6/9] Sutff --- .eslintignore | 1 - .eslintrc.json | 24 ------------- eslint.config.mjs | 9 +++++ package.json | 48 +++++++++++--------------- src/classComponents/ResourceEvents.tsx | 19 ++++------ src/types/baseType.ts | 8 ++--- src/types/moreTypes.ts | 11 +++--- 7 files changed, 46 insertions(+), 74 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.json create mode 100644 eslint.config.mjs diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 0915f64..0000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -src/examples/ diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index b6a8173..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true, - "node": true - }, - "plugins": ["react"], - "parser": "@babel/eslint-parser", - "extends": ["airbnb"], - "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" }, - "rules": { - "max-len": ["error", { "code": 200 }], - "arrow-parens": ["error", "as-needed"], - "linebreak-style": "off", - "object-curly-newline": "off", - "react/forbid-prop-types": "off", - "react/require-default-props": "off", - "react/jsx-props-no-spreading": "off", - "no-underscore-dangle": "off", - "no-param-reassign": "off", - "no-console": "off", - "class-methods-use-this": "off" - } -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..aacf15a --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,9 @@ +import baseConfig from "@repo/eslint-config/base"; + +/** @type {import('typescript-eslint').Config} */ +export default [ + { + ignores: [".nitro/**", ".output/**"], + }, + ...baseConfig, +]; diff --git a/package.json b/package.json index 352498d..7f921bb 100644 --- a/package.json +++ b/package.json @@ -54,47 +54,41 @@ "scripts": { "build": "node scripts/build.js", "start": "webpack serve --mode development --config ./webpack/webpack.dev.config.js", - "clean": "rimraf ./dist && mkdir dist", - "lint": "eslint ./src", - "fix": "eslint ./" + "clean": "rimraf ./dist && mkdir dist" }, "dependencies": { - "@ant-design/icons": "^5.3.0", - "@babel/preset-typescript": "^7.24.7", + "@ant-design/icons": "^5.5.1", + "@babel/preset-typescript": "^7.25.7", "@types/react": "18.2.74", "@types/react-dom": "18.2.23", - "antd": "^5.14.1", - "dayjs": "^1.11.10", + "antd": "^5.21.5", + "dayjs": "^1.11.13", "prop-types": "^15.8.1", - "react": "^18.2.0", "react-dnd": "^14.0.5", "react-dnd-html5-backend": "^14.1.0", - "react-dom": "^18.2.0", + "react": "catalog:react19", + "react-dom": "catalog:react19", + "@repo/eslint-config": "workspace:*", "rrule": "^2.8.1", "ts-loader": "^9.5.1", - "typescript": "^5.6.2" + "typescript": "catalog:" }, "devDependencies": { - "@babel/cli": "^7.23.9", - "@babel/core": "^7.23.9", - "@babel/eslint-parser": "^7.23.10", - "@babel/preset-env": "^7.23.9", - "@babel/preset-react": "^7.23.3", - "babel-loader": "^9.1.3", + "@babel/cli": "^7.25.7", + "@babel/core": "^7.25.8", + "@babel/eslint-parser": "^7.25.8", + "@babel/preset-env": "^7.25.8", + "@babel/preset-react": "^7.25.7", + "babel-loader": "^9.2.1", "copy-webpack-plugin": "^12.0.2", - "css-loader": "^7.1.0", - "eslint": "^9.0.0", - "eslint-config-airbnb": "^19.0.4", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsx-a11y": "^6.8.0", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-webpack-plugin": "^4.0.1", + "css-loader": "^7.1.2", + "eslint": "catalog:", + "@types/eslint": "catalog:", "fs-extra": "^11.2.0", - "html-webpack-plugin": "^5.6.0", + "html-webpack-plugin": "^5.6.2", "style-loader": "^4.0.0", - "webpack": "^5.90.3", + "webpack": "^5.95.0", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^5.0.2" + "webpack-dev-server": "^5.1.0" } } diff --git a/src/classComponents/ResourceEvents.tsx b/src/classComponents/ResourceEvents.tsx index 7010137..d2c3bd0 100644 --- a/src/classComponents/ResourceEvents.tsx +++ b/src/classComponents/ResourceEvents.tsx @@ -1,19 +1,14 @@ -import React, { Component, ReactNode } from "react"; -import { DragSourceMonitor, DropTargetMonitor } from "react-dnd"; +import type { ReactNode } from "react"; +import { Component } from "react"; import AddMore from "../components/AddMore"; import Summary from "../components/Summary"; import SelectedArea from "../components/SelectedArea"; -import { - CellUnit, - DATETIME_FORMAT, - SummaryPos, - DnDTypes, -} from "../config/default"; +import { CellUnit, SummaryPos, DnDTypes } from "../config/default"; import { getPos } from "../helper/utility"; -import { SchedulerData } from "../components/SchedulerData"; -import { RenderDataItem } from "../types/baseType"; -import { Dayjs } from "dayjs"; -import { DnDSource } from "./DnDSource"; +import type { SchedulerData } from "../components/SchedulerData"; +import type { RenderDataItem } from "../types/baseType"; +import type { Dayjs } from "dayjs"; +import type { DnDSource } from "./DnDSource"; interface ResourceEventsProps { resourceEvents: RenderDataItem; diff --git a/src/types/baseType.ts b/src/types/baseType.ts index 37fb42d..ecee986 100644 --- a/src/types/baseType.ts +++ b/src/types/baseType.ts @@ -1,5 +1,5 @@ -import { Dayjs } from "dayjs"; -import { SummaryPos, ViewType } from "../config/default"; +import type { Dayjs } from "dayjs"; +import type { SummaryPos, ViewType } from "../config/default"; export interface View { viewName?: string; @@ -97,10 +97,10 @@ export interface Resource { groupOnly?: boolean; } -export type HeadersType = { +export interface HeadersType { time: string; nonWorkingTime?: boolean; -}; +} export interface HeaderItem { time: string; diff --git a/src/types/moreTypes.ts b/src/types/moreTypes.ts index fec5d62..a1b5205 100644 --- a/src/types/moreTypes.ts +++ b/src/types/moreTypes.ts @@ -1,17 +1,16 @@ -import { ConfigType, Dayjs, OptionType } from "dayjs"; -import React, { CSSProperties } from "react"; -import { +import type { Dayjs } from "dayjs"; +import type { CSSProperties } from "react"; +import type React from "react"; +import type { EventItemType, HeaderEvent, - HeaderEventsType, HeaderItem, HeadersType, RenderDataItem, - ResourceEvent, State, View, } from "./baseType"; -import { +import type { CellUnit, DnDSource, SchedulerData, From 95dc83dc177a3d2febd12ac9c1a5f7ab633ec37e Mon Sep 17 00:00:00 2001 From: kyesildagli Date: Mon, 28 Oct 2024 15:01:57 +0000 Subject: [PATCH 7/9] ts ignore --- src/classComponents/DnDContext.ts | 9 +++++++-- src/classComponents/DnDSource.ts | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/classComponents/DnDContext.ts b/src/classComponents/DnDContext.ts index 343bc2f..0df21bb 100644 --- a/src/classComponents/DnDContext.ts +++ b/src/classComponents/DnDContext.ts @@ -1,3 +1,4 @@ +// @ts-ignore import { DropTarget, DropTargetMonitor, DropTargetConnector } from "react-dnd"; import { DnDTypes, @@ -8,7 +9,11 @@ import { import { getPos } from "../helper/utility"; import dayjs, { Dayjs } from "dayjs"; import { SchedulerData } from "../components/Scheduler"; -import { EventItemType, RenderDataItem, ResourceEvent } from "../types/baseType"; +import { + EventItemType, + RenderDataItem, + ResourceEvent, +} from "../types/baseType"; import { DnDSource } from "./DnDSource"; // Types in this in this file have been generated by AI and are not accurate. Please replace them with the correct types. @@ -202,7 +207,7 @@ export class DnDContext { canDrop: (props: DnDContextProps, monitor: DropTargetMonitor) => { const { schedulerData, resourceEvents } = props; - const item: EventItemType = monitor.getItem(); + const item: EventItemType = monitor.getItem(); if (schedulerData._isResizing()) return false; const { config } = schedulerData; return ( diff --git a/src/classComponents/DnDSource.ts b/src/classComponents/DnDSource.ts index bc5e3e7..bf8b1a4 100644 --- a/src/classComponents/DnDSource.ts +++ b/src/classComponents/DnDSource.ts @@ -1,3 +1,5 @@ +// @ts-ignore + import { DragSource, DragSourceConnector, DragSourceMonitor } from "react-dnd"; import { DnDTypes, ViewType, DATETIME_FORMAT } from "../config/default"; import dayjs from "dayjs"; From b2ad54c86283fc0d999ef568b74c4e8f68e4d8bd Mon Sep 17 00:00:00 2001 From: kyesildagli Date: Tue, 29 Oct 2024 15:49:11 +0000 Subject: [PATCH 8/9] fix build --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 352498d..9cf23d3 100644 --- a/package.json +++ b/package.json @@ -61,15 +61,15 @@ "dependencies": { "@ant-design/icons": "^5.3.0", "@babel/preset-typescript": "^7.24.7", - "@types/react": "18.2.74", - "@types/react-dom": "18.2.23", + "@types/react": "catalog:react19", + "@types/react-dom": "catalog:react19", "antd": "^5.14.1", "dayjs": "^1.11.10", "prop-types": "^15.8.1", - "react": "^18.2.0", + "react": "catalog:react19", "react-dnd": "^14.0.5", "react-dnd-html5-backend": "^14.1.0", - "react-dom": "^18.2.0", + "react-dom": "catalog:react19", "rrule": "^2.8.1", "ts-loader": "^9.5.1", "typescript": "^5.6.2" From d312e9645c1dc0cd355f0ca72f1924ad03f2ce5e Mon Sep 17 00:00:00 2001 From: Pedro Roque Date: Thu, 5 Dec 2024 12:04:16 +0000 Subject: [PATCH 9/9] Fix --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 9cf23d3..7ef1782 100644 --- a/package.json +++ b/package.json @@ -61,15 +61,13 @@ "dependencies": { "@ant-design/icons": "^5.3.0", "@babel/preset-typescript": "^7.24.7", - "@types/react": "catalog:react19", - "@types/react-dom": "catalog:react19", "antd": "^5.14.1", "dayjs": "^1.11.10", "prop-types": "^15.8.1", "react": "catalog:react19", + "react-dom": "catalog:react19", "react-dnd": "^14.0.5", "react-dnd-html5-backend": "^14.1.0", - "react-dom": "catalog:react19", "rrule": "^2.8.1", "ts-loader": "^9.5.1", "typescript": "^5.6.2" @@ -90,6 +88,9 @@ "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-webpack-plugin": "^4.0.1", + "@types/node": "catalog:", + "@types/react": "catalog:react19", + "@types/react-dom": "catalog:react19", "fs-extra": "^11.2.0", "html-webpack-plugin": "^5.6.0", "style-loader": "^4.0.0",