Premium support for our pure JavaScript UI components


Post by gorakh.nath »

Hi,
I have enabled resourceNonWorkingTimeFeature = {true} and nonWorkingTimeFeature={ true }
It is working as expected.Now if I drag and drop from the grid to the non-workingTime then it will find the next available working time and setting that automatically.
My use case is different:-
The task could be scheduled at a time when the technician is not available
We should enable solutions to configure this such that a popup could appear if needed (this could be turned off if not needed)
Eg if the task is dragged onto the unavailable time slot, show a popup saying “Are you sure you want to schedule this Task to Technician A outside their shift?”
Please suggest me how to achieve this requirement?
My drag.js file code is:-

import { DragHelper, DomHelper, Rectangle, WidgetHelper, StringHelper } from '@bryntum/schedulerpro/schedulerpro.umd';
import { moment } from '../../../utilities/moment';
import {DRAG_PAST_DATE_ERROR_MSG, WORKFLOW_UPDATE_ERROR} from "../utills";
import { getLodash } from '../../../utilities/lodash';

export default class Drag extends DragHelper {
  static get defaultConfig () {
    return {
      // Don't drag the actual row element, clone it
      cloneTarget: true,
      mode: 'translateXY',
      // Only allow drops on the schedule area
      dropTargetSelector: '.b-timeline-subgrid',
      // Only allow drag of row elements inside on the unplanned grid
      targetSelector: '.b-grid-row:not(.b-group-row)'
    };
  }

  construct (config) {
    const me = this;

super.construct(config);

me.on({
  dragstart: me.onTaskDragStart,
  drag: me.onTaskDrag,
  drop: me.onTaskDrop,
  thisObj: me
});
  }

  onTaskDragStart ({ context }) {
    const
      me = this,
      { schedule } = me,
      mouseX = context.clientX,
      proxy = context.element,
      task = me.grid.getRecordFromElement(context.grabbed),
      newSize = me.schedule.timeAxisViewModel.getDistanceForDuration(task.durationMS);

// save a reference to the task so we can access it later
context.task = task;

// Mutate dragged element (grid row) into an event bar
proxy.classList.remove('b-grid-row');
proxy.classList.add('b-sch-event-wrap');
proxy.classList.add('b-unassigned-class');
proxy.classList.add(`b-${schedule.mode}`);
// proxy.innerHTML = `<i class="${task.iconCls}"></i> ${task.name}`;

proxy.innerHTML = StringHelper.xss`
        <div class="b-sch-event b-has-content">
            <div class="b-sch-event-content">
                <div>${task.name}</div>
                <span>Duration: ${task.duration} ${task.durationUnit}</span>
             </div>
        </div>
    `;

me.schedule.enableScrollingCloseToEdges(me.schedule.timeAxisSubGrid);

if (schedule.isHorizontal) {
  // If the new width is narrower than the grabbed element...
  if (context.grabbed.offsetWidth > newSize) {
    const proxyRect = Rectangle.from(context.grabbed);

    // If the mouse is off (nearly or) the end, centre the element on the mouse
    if (mouseX > proxyRect.x + newSize - 20) {
      context.newX = context.elementStartX = context.elementX = mouseX - newSize / 2;
      DomHelper.setTranslateX(proxy, context.newX);
    }
  }

  proxy.style.width = `${newSize}px`;
} else {
  const width = schedule.resourceColumns.columnWidth;

  // Always center horizontal under mouse for vertical mode
  context.newX = context.elementStartX = context.elementX = mouseX - width / 2;
  DomHelper.setTranslateX(proxy, context.newX);

  proxy.style.width = `${width}px`;
  proxy.style.height = `${newSize}px`;
}

// Prevent tooltips from showing while dragging
schedule.element.classList.add('b-dragging-event');
  }

  onTaskDrag ({ event, context }) {
    const
      me = this,
      coordinate = DomHelper[`getTranslate${me.schedule.isHorizontal ? 'X' : 'Y'}`](context.element),
      date = me.schedule.getDateFromCoordinate(coordinate, 'round', false),
      // Coordinates required when used in vertical mode, since it does not use actual columns
      resource = context.target && me.schedule.resolveResourceRecord(context.target, [event.offsetX, event.offsetY]);

// Don't allow drops anywhere, only allow drops if the drop is on the timeaxis and on top of a Resource
context.valid = context.valid && Boolean(date && resource);

// Save reference to resource so we can use it in onTaskDrop
context.resource = resource;
  }

  // Drop callback after a mouse up, take action and transfer the unplanned task to the real SchedulerEventStore (if it's valid)
  async onTaskDrop ({ context, event }) {

const
  me = this,
  { task, target } = context;
const listCount = me.grid.data.length;

me.schedule.disableScrollingCloseToEdges(me.schedule.timeAxisSubGrid);
const customErrorMessage= getLodash(me.jsonDef, 'customErrorMessage');
// If drop was done in a valid location, set the startDate and transfer the task to the Scheduler event store
if (context.valid && target) {
  const
    coordinate = DomHelper[`getTranslate${me.schedule.isHorizontal ? 'X' : 'Y'}`](context.element),
    date = me.schedule.getDateFromCoordinate(coordinate, 'round', false),
    // Try resolving event record from target element, to determine if drop was on another event
    targetEventRecord = me.schedule.resolveEventRecord(context.target);
  const finalize = me.context.finalize;

  if(moment().isAfter(date, 'second')) {
    me.setShowAlert(true);
    const errorMsg= (customErrorMessage && customErrorMessage.pastDateErrorMsg) || DRAG_PAST_DATE_ERROR_MSG;
    me.setErrorMessage(errorMsg);
    return false;
  }

  if (date) {
    me.setShowAlert(false);
    me.setErrorMessage('');
    me.showLoader();
    const response = await me.submitTaskDetails({ ...task.data, resourceId: context.resource.data.id });
    me.hideLoader();
    //  if(response === null || (response && response.errorCode)) {
    //   const errorMsg= (customErrorMessage && customErrorMessage.workflowUpdateErrorMsg) || WORKFLOW_UPDATE_ERROR;
    //    me.setShowAlert(true);
    //    me.setErrorMessage(errorMsg);
    //    return false;
    // }
    // Remove from grid first so that the data change
    // below does not fire events into the grid.
    me.setTaskListCount(listCount-1);
    me.grid.store.remove(task);

    // task.setStartDate(date, true);
    task.startDate = date;
    task.resource = context.resource;
    me.schedule.eventStore.add(task);
  }

  // Dropped on a scheduled event, display toast
  if (targetEventRecord) {
    WidgetHelper.toast(`Dropped on ${targetEventRecord.name}`);
  }
  finalize();
  // me.context.finalize();
} else {
  me.abort();
}

me.schedule.element.classList.remove('b-dragging-event');
  }
}

Post by saki »

First of all, resourcNonWorkingTime is a visualization tool only and by itself it does not affect scheduling. From the docs:

Feature that highlights non-working time periods for resources according to their calendar. If no calenar for a resource defined, an active project's calendar will be used.

It is the https://bryntum.com/docs/scheduler-pro/#SchedulerPro/model/ResourceModel#field-calendar that influences the scheduling. However, if a time range is defined as non-working the engine won't schedule the event at that time. See https://bryntum.com/docs/scheduler-pro/#SchedulerPro/guides/basics/calendars.md#scheduling-logic-when-using-project%2C-event-and-resource-calenders please.

Now you have 2 options:

  1. change https://bryntum.com/docs/scheduler-pro/#SchedulerPro/model/CalendarModel#field-intervals interval that initially defined a range as non-working and then the engine would allow to schedule the event because it would not be non-working time anymore

  2. use https://bryntum.com/docs/scheduler-pro/#Scheduler/feature/ResourceTimeRanges that do not influence scheduling but can be visualized – this is probably easier to implement but your mileage may vary

In any case, read the guides please, especially the above and also https://bryntum.com/docs/scheduler-pro/#../engine/schedulerpro_events_scheduling.md to get all details on event scheduling.


Post Reply