react-scheduler/react-big-schedule

View on GitHub
src/components/ResourceEvents.jsx

Summary

Maintainability
D
2 days
Test Coverage
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 ? <SelectedArea {...this.props} left={left} width={width} /> : <div />;

    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 = (
              <DnDEventItem
                {...this.props}
                key={evt.eventItem.id}
                eventItem={evt.eventItem}
                isStart={isStart}
                isEnd={isEnd}
                isInPopover={false}
                left={left}
                width={width}
                top={top}
                leftIndex={index}
                rightIndex={index + evt.span}
              />
            );
            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 = (
            <AddMore
              {...this.props}
              key={headerItem.time}
              headerItem={headerItem}
              number={headerItem.addMore}
              left={left}
              width={width}
              top={top}
              clickAction={this.onAddMoreClick}
            />
          );
          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 = <Summary key={key} schedulerData={schedulerData} summary={headerItem.summary} left={left} width={width} top={top} />;
          eventList.push(summary);
        }
      }
    });

    const eventContainer = (
      <div ref={this.eventContainerRef} className="event-container" style={{ height: resourceEvents.rowHeight }}>
        {selectedArea}
        {eventList}
      </div>
    );
    return (
      <tr>
        <td style={{ width: rowWidth }}>{config.dragAndDropEnabled ? connectDropTarget(eventContainer) : eventContainer}</td>
      </tr>
    );
  }
}

export default ResourceEvents;