Hi,
Right now if we try to drop the task in non-working time of resource then it will automatically find the next available time of resource and it will assign the task at that available time.
But our use case is that for some resources we have to assign the task at there non-working time and that will be overtime for them.
How can we allow to drop the task and assign the task at the time where the task was dropped ?
Is there any config available to support this which will over-ride the non-working time of resource?
Is it possible to disable it to automatically assigning the task to working time if we drag to non-working time?
@arcady Where should I call skipNonWorkingTime method defined above? Do I need to call this on drag and drop event?
My drag and drop file has code:-
import { DragHelper, DomHelper, Rectangle, WidgetHelper, StringHelper, DateHelper } from '@bryntum/schedulerpro/schedulerpro.umd';
import { moment } from '../../../utilities/moment';
import {DRAG_PAST_DATE_ERROR_MSG, WORKFLOW_UPDATE_ERROR, TECHNICIAN_OVERLAP_TASK_ERROR, CUSTOM_ERROR_KEY} from "../utills";
import { getLodash } from '../../../utilities/lodash';
import { ActionDispatcher } from '../../../uibuilder/Actions/ActionDispatcher';
import { SetPageContext }
from '../../../uibuilder/ActionType/pageConfig/SetPageContext';
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
});
}
/**
*
* @param {object} schedulerObj : contains the scheduler object
* @param {string} key : contains the key
* @returns {string}: based on the key
*/
getErrorMsg = (schedulerObj, key)=> {
const customError= getLodash(schedulerObj.jsonDef, 'customErrorMessage');
let errorMsg='';
if(key){
switch (key) {
case CUSTOM_ERROR_KEY.TASK_OVERLAPPING_ERROR:
errorMsg= customError?.taskOverlappingErrorMsg || TECHNICIAN_OVERLAP_TASK_ERROR;
break;
case CUSTOM_ERROR_KEY.WORKFLOW_UPDATE_ERROR:
errorMsg= customError?.workflowUpdateErrorMsg || WORKFLOW_UPDATE_ERROR;
break;
case CUSTOM_ERROR_KEY.PAST_DATE_ERROR:
errorMsg= customError?.pastDateErrorMsg || DRAG_PAST_DATE_ERROR_MSG;
break;
default:
break;
}
}
return errorMsg;
}
/**
*
* @param {object} schedulerObj : contains scheduler object
* @param {string} errorMsgKey : contains key based on that error message will come
*/
showErrorMsg = (schedulerObj, errorMsgKey)=> {
const errorMsg= this.getErrorMsg(schedulerObj,errorMsgKey);
schedulerObj.setShowAlert(true);
schedulerObj.setErrorMessage(errorMsg);
}
/**
*
* @param {object} obj : contains the object value use to set the invalid of scheduler
* @returns {boolean} : if valid true else false
*/
isContextValid = (obj)=> {
let isValidContext = false;
if(obj && obj.valid && Boolean(obj.date && obj.resource)){
isValidContext= true;
//Now check for overlap task if allowOverlap false defined in the config
if(obj && obj.schedule && !obj.schedule.allowOverlap && !obj.allowTechnicianOverlap && !obj.allowEventOverlap){
isValidContext= obj.schedule.isDateRangeAvailable(obj.startDate, obj.endDate, null, obj.resource);
!isValidContext && this.showErrorMsg(obj.me, CUSTOM_ERROR_KEY.TASK_OVERLAPPING_ERROR);
}
}
return isValidContext;
}
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,
{ schedule } = me,
{ task } = context,
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 && 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
paramObj= { valid: context.valid, date,resource},
isValid= this.isContextValid(paramObj);
context.valid = isValid;
// 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, resource } = context,
listCount = me.grid.data.length,
{ schedule } = me,
coordinate = DomHelper[`getTranslate${schedule.isHorizontal ? 'X' : 'Y'}`](context.element),
date = me.schedule.getDateFromCoordinate(coordinate, 'round', false),
startDate = schedule.getDateFromCoordinate(coordinate, 'round', false),
endDate = startDate && DateHelper.add(startDate, task.duration, task.durationUnit),
paramObj= { valid: context.valid,
date,resource,schedule,
startDate, endDate ,
me,
allowTechnicianOverlap: resource?.originalData?.allowTechnicianOverlap,
alloweEventOverlap: task?.originalData?.alloweEventOverlap
}
schedule.disableScrollingCloseToEdges(me.schedule.timeAxisSubGrid);
const isValid = this.isContextValid(paramObj);
// If drop was done in a valid location, set the startDate and transfer the task to the Scheduler event store
if (isValid && context.valid && target) {
const
// 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')) {
this.showErrorMsg(me,CUSTOM_ERROR_KEY.PAST_DATE_ERROR)
return false;
}
if (date) {
me.setShowAlert(false);
me.setErrorMessage('');
me.showLoader();
const pageContext = new SetPageContext({taskDetail: task.data, resourceId: context.resource.data.id });
debugger;
this.dispatch(pageContext.plainAction());
const ah = new ActionDispatcher(this);
ah.doAction(this.jsonDef.dropGridTaskAction);
//const response = await me.submitTaskDetails({ ...task.data, resourceId: context.resource.data.id });
me.hideLoader();
// if(response === null || (response && response.errorCode)) {
// this.showErrorMsg(me,CUSTOM_ERROR_KEY.WORKFLOW_UPDATE_ERROR)
// 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');
}
}
You don't need to call it. It's already called each time you drag a task so the task shifts to the nearest working period of time.
As far as I understood you wanted to customize that logic so I pointed you to the method that should be overridden.
@arcady can I override this method from my code ? if yes then where , which file should I write this method to override above logic? Please provide some work around to override this method.
Why not have two calendars, one for standard time and then another for overtime? And then depending on which time it's in assign the event to the appropriate calendar?