Hi,
I am using dayresource view to display planned events per resource. I am trying to set my coreHours and visibleStartTime like this:
coreHours: {
start: 8,
end: 18
},
visibleStartTime: 7,
but my visibleStartTime setting does not work, the calendar does not scroll to 7 am. What is the problem here?
When I am using normal day view it does work, but with dayresource doesn't :/
My code snippet:
const calendar = new bryntum.calendar.Calendar({
date: new Date(),
cls: "executors-planned-tasks-calendar",
height: 600,
width: 1360,
viewPreset: "hourAndDay",
allowOverlap: false,
shortEventDuration: "1 hour",
mode: "dayresourceview",
sidebar: {
hidden: true,
items: {
resourceFilter: null
}
},
project: {
events,
resources,
listeners: {
refresh({ source }) {
const rf = calendar.widgetMap.resourceFilter;
calendar.eventStore.addFilter({
id: "custom-filter",
filterBy: (event) => rf.valueCollection.includes(event.resourceId)
});
rf.store = source.resourceStore;
rf.value = rf.store.records[0];
window.rf = rf;
},
once: true
}
},
tbar: {
items: {
resourceFilter: {
type: "combo",
width: "17rem",
weight: 100,
multiSelect: true,
placeholder: "executors",
listCls: "custom-resource-filter",
// The value is the records selected
valueField: null,
displayField: "name",
listItemTpl: (resource) => `
<div class="resource-list-text">
<div class="resource-name">${resource.name}</div>
</div>
`,
listeners: {
// "up." means resolve in ownership chain. Will call on the Calendar
change: "up.onFilterCriteriaChange",
// We have to filter after the Calendar has processed the change
prio: -1000
},
// We want the ChipView to scroll horizontally with no wrapping.
chipView: {
itemTpl: (resource) => {
if (resource.name.length > 15) {
return `${resource.name.substring(0, 12) + "..."}`;
} else {
return `${resource.name}`;
}
},
scrollable: {
overflowX: "hidden-scroll",
overflowY: false
}
}
},
todayButton: false,
prevButton: {
weight: 200
},
viewDescription: {
weight: 300
},
nextButton: {
weight: 400
},
button: {
weight: 700,
text: "week",
cls: "week-mode-button",
disabled: true,
tooltip: "Week view is for now disabled",
onClick: (event) => {}
}
}
},
// The subviews have a close tool which filters them out
modeDefaults: {
timeFormat: "HH:mm",
coreHours: {
start: 8,
end: 18
},
view: {
visibleStartTime: 7,
// Show a close icon to filter out the resource
tools: {
close: {
cls: "b-fa b-fa-times",
tooltip: "Filter out this resource",
// Will find the handler on the Calendar
handler: "up.onSubviewCloseClick"
}
},
strips: {
// A simple widget showing total planned calendar events' hours for each resource
resourceInfo: {
type: "widget",
dock: "header",
cls: "b-total-events-duration-header",
// This method gets called when the panel is created and we return some meta data about the
// resource, like "8u". Will be found on the Calendar
html: "up.getSubViewTotalHoursCountHeader"
}
}
}
},
modes: {
day: null,
month: null,
year: null,
week: null,
agenda: null,
dayresource: {
descriptionRenderer() {
return `${Ext.Date.format(calendar.date, "j F Y")}`;
},
resourceWidth: "18em",
weight: 500,
view: {
type: "dayview",
showHeaderAvatars: false,
allDayEvents: {
fullWeek: false
},
eventRenderer: (event) => {
const eventRecord = event.eventRecord.data,
renderData = event.renderData;
let eventCls = "";
switch (eventRecord.type) {
case "TASK":
eventCls = "task-event";
renderData.eventColor = me.taskEventColor;
renderData.bodyColor = me.taskEventColor;
break;
case "TRAVEL":
eventCls = "travel-event";
renderData.eventColor = me.travelEventColor;
renderData.bodyColor = me.travelEventColor;
break;
case "LEAVE":
eventCls = "leave-event";
renderData.eventColor = me.taskEventColor;
renderData.bodyColor = me.taskEventColor;
break;
case "BREAK":
eventCls = "break-event";
renderData.eventColor = me.breakEventColor;
renderData.bodyColor = me.breakEventColor;
break;
default:
break;
}
let amountOfAssets = eventRecord.planningAssetAmount ? eventRecord.planningAssetAmount : "",
productCode = eventRecord.productCode ? eventRecord.productCode : "",
eventName = eventRecord.name ? eventRecord.name : "";
if (eventRecord.type === "TASK") {
amountOfAssets = `${amountOfAssets}x`;
//show "..." when text is too long
switch (eventRecord.duration) {
case 0.5:
case 0.75:
case 1:
if (eventName.length >= 14 && eventName.length < 55) {
eventName = eventName.substring(0, 14) + "...";
}
break;
case 1.25:
if (eventName.length >= 55 && eventName.length < 60) {
eventName = eventName.substring(0, 55) + "...";
}
break;
case 1.5:
if (eventName.length >= 60 && eventName.length < 75) {
eventName = eventName.substring(0, 60) + "...";
}
eventName = eventName.substring(0, 60) + "...";
break;
case 1.75:
if (eventName.length >= 75 && eventName.length < 85) {
eventName = eventName.substring(0, 75) + "...";
}
break;
case 2:
if (eventName.length >= 85 && eventName.length < 115) {
eventName = eventName.substring(0, 85) + "...";
}
break;
case 2.25:
if (eventName.length >= 115 && eventName.length < 135) {
eventName = eventName.substring(0, 115) + "...";
}
break;
case 2.5:
if (eventName.length >= 135 && eventName.length < 155) {
eventName = eventName.substring(0, 135) + "...";
}
break;
case 2.75:
if (eventName.length >= 155) {
eventName = eventName.substring(0, 155) + "...";
}
break;
default:
break;
}
} else if (eventRecord.type === "TRAVEL") {
eventName = UCare4.util.Translator.translate("travel_time");
}
const html = `<div id="custom-${eventCls}">
<div class="custom-event-name-container">
<span class="custom-event-name-text">${amountOfAssets}<span class="product-code-text">${productCode}</span> ${eventName}</span>
</div>
</div>`;
return html;
}
},
hideNonWorkingDays: false,
range: "day",
type: "resource",
title: UCare4.util.Translator.translate("day"),
// Demo uses more padding than default, switch to the short event duration "earlier" to fit contents
shortEventDuration: "1 hour"
},
// Mode name can be anything if it contains a "type" property.
weekResources: {
weight: 600,
disabled: true,
// Type has the final say over which view type is created
type: "resource",
title: UCare4.util.Translator.translate("workweek"),
// Specify how wide each resource panel should be
resourceWidth: "4em",
hideNonWorkingDays: true,
fitHours: true,
// Info to display below a resource name
meta: (resource) => resource.name
}
},
// Features named by the properties are included.
// An object is used to configure the feature.
features: {
scheduleContextMenu: false,
eventMenu: {
items: {
editEvent: false,
duplicate: false,
deleteEvent: {
weight: 300,
icon: "b-fa b-fa-fw b-fa-calendar-times",
async onItem(event) {
if (event && event.eventRecord && event.eventRecord.data) {
const eventRecord = event.eventRecord;
if (eventRecord.data.travelEventId) {
const travelEventResponse = await UCare4.util.Client.delete(
`${Ext.manifest.calendar_service}/api/rest/v1/event/${eventRecord.data.travelEventId}`
);
if (!me.isSuccess(travelEventResponse.status)) {
me.showEventCouldNotBeDeletedError();
return;
}
}
const taskEventResponse = await UCare4.util.Client.delete(
`${Ext.manifest.calendar_service}/api/rest/v1/event/${eventRecord.data.realEventId}`
);
if (!me.isSuccess(taskEventResponse.status)) {
me.showEventCouldNotBeDeletedError();
return;
}
await me.refreshData(calendar, true);
me.showEventIsSuccessfullyUnscheduled();
if (eventRecord && eventRecord.data) {
calendar.recalculateEventDurationWidgetForAllResources(eventRecord.data.resourceId);
}
}
}
}
}
},
scheduleMenu: {
items: {
// Knocks out the predefined addEvent item
addEvent: null
}
},
externalEventSource: {
dragRootElement: "tasks-to-plan-list",
dragItemSelector: ".contract-planning-management-listitem",
droppable: true,
draggable: true,
getRecordFromElement(element) {
// Return an object from which an EventModel can be created.
// Same format as loading an EventStore. { name : 'name', startDate: ''} etc
return me.createRecordFromElement(element);
}
},
drag: {
// Each drag mode has a separate validation callback.
// We route them all to one on the calendar instance
validateCreateFn() {
return calendar.validateCreate(...arguments);
//do something
}
}
},
listeners: {
async dropExternal(event) {
let result = false;
const dragTargetResource = event.event.target &&
calendar.resolveResourceRecord(event.event.target);
let startDate = calendar.getDateFromDomEvent(event.domEvent),
endDate = me.calculateEndDate(startDate, event.eventRecord.data.duration);
event.eventRecord.data["startDate"] = startDate;
event.eventRecord.data["endDate"] = endDate;
const dateIsAvailable = calendar.eventStore.isDateRangeAvailable(
startDate,
endDate,
event.eventRecord,
dragTargetResource
);
if (!dateIsAvailable) {
return false;
}
event.eventRecord.data["resourceId"] = dragTargetResource.data.id;
// Show MessageBox and wait for user response
const userResponse = await new Promise((resolve) => {
UCare4.ux.MessageBox.showConfirm(
UCare4.util.Translator.translate("calculate_travel_time"),
UCare4.util.Translator.translate("calculate_travel_time_message"),
(choice) => {
resolve(choice);
}
);
});
// Validate based on user response
if (userResponse === "yes") {
result = await me.createEventWithTravelTime(event, calendar);
} else {
result = await me.createEvent(event, calendar);
}
return result;
},
dragMoveEnd: async (event) => {
if (event.drag && event.drag.source && event.drag.source.isExternalZone) {
calendar.recalculateEventDurationWidget(event);
}
await me.refreshData(calendar, true);
},
beforeDragMoveEnd: async (event) => {
if (event.drag && event.drag.source && event.drag.source.isExternalZone) return true;
return await calendar.executeDragEnd(event, false);
},
beforeDragResizeEnd: async (event) => {
if (event.drag && event.drag.source && event.drag.source.isExternalZone) return true;
return await calendar.executeDragEnd(event, true);
},
paint: () => {
calendar.addHoverTooltipToWeekButton();
},
eventClick: (event) => {
if (event && event.eventRecord && event.eventRecord.data) {
//set executor where event is planned - just for now
const resourceRecord = event.resourceRecord.data;
event.eventRecord.data["executorName"] = resourceRecord.name;
event.eventRecord.data["executorId"] = resourceRecord.id;
event.eventRecord.data["executorComplianceUserId"] = resourceRecord.compliance_user_id;
event.eventRecord.data["executorAvatar"] = resourceRecord.avatar;
if (event.eventRecord.type === "TASK") {
//create calendar event edit/read detail popup
Ext.create("UCare4.ux.popup.calendarevent.CalendarEventPopup", {
eventRecord: event.eventRecord.data
}).show();
} else if (event.eventRecord.type === "LEAVE") {
Ext.create("UCare4.ux.popup.customcalendarevent.CustomCalendarEventPopup", {
eventRecord: event.eventRecord.data,
calendar: calendar
}).show();
}
}
},
eventMouseOver: (event) => {
const eventRecord = event.eventRecord.data,
dialog = Ext.ComponentQuery.query("ucare4-event-tip");
if (dialog) {
Ext.each(dialog, (tip) => {
tip.destroy();
});
}
if (eventRecord.type === "TASK") {
Ext.create("UCare4.ux.tip.calendarevent.EventTip", {
source: event,
eventRecord: eventRecord
}).show();
}
},
dateChange: async (event) => {
await me.refreshData(calendar, true);
calendar.recalculateEventDurationWidgetForAllResources();
}
},
handleYesResponse: async (event) => {
const response = await me.deleteEventTravelTime(event);
if ((response && Ext.isObject(response) && me.isSuccess(response.status)) || response === true) {
return await me.updateEventWithTravelTime(event, calendar);
} else {
return false;
}
},
handleNoResponse: async (event) => {
const response = await me.deleteEventTravelTime(event);
if ((response && Ext.isObject(response) && me.isSuccess(response.status)) || response === true) {
return await me.updateEvent(event);
} else {
return false;
}
},
addHoverTooltipToWeekButton: () => {
const weekResourcesShowButton = document.querySelector('[data-ref="weekResourcesShowButton"]'),
tooltip = document.createElement("div");
tooltip.className = "week-resources-show-button-tooltip";
tooltip.innerHTML = "Week view is for now disabled"; //TODO: translate later
document.body.appendChild(tooltip);
weekResourcesShowButton.addEventListener("mouseover", () => {
// Change the button's background color
const rect = weekResourcesShowButton.getBoundingClientRect();
const top = rect.top - tooltip.offsetHeight - 5;
const left = rect.left + (weekResourcesShowButton.offsetWidth - tooltip.offsetWidth) / 2;
// Set the position and display the tooltip
tooltip.style.top = top + "px";
tooltip.style.left = left + "px";
tooltip.style.display = "block";
});
weekResourcesShowButton.addEventListener("mouseout", function () {
// Hide the tooltip on mouseout
tooltip.style.display = "none";
});
},
validateCreate(event) {
const dragTargetResource = event.event.target && calendar.resolveResourceRecord(event.event.target);
const dateIsAvailable = calendar.eventStore.isDateRangeAvailable(
event.eventRecord.data.startDate,
event.eventRecord.data.endDate,
event.eventRecord,
dragTargetResource
);
if (!dateIsAvailable) {
return false;
}
if (dragTargetResource) event.eventRecord.data["resourceId"] = dragTargetResource.data.id;
Ext.create("UCare4.ux.popup.customcalendarevent.CustomCalendarEventPopup", {
eventRecord: event.eventRecord.data,
calendar: calendar
}).show();
if (event.eventRecord && event.eventRecord.data) {
calendar.recalculateEventDurationWidget(event, event.eventRecord.data.duration);
}
return true;
},
onSubviewCloseClick(domEvent, view) {
const values = calendar.widgetMap.resourceFilter.value;
let newValues = [];
for (const value of values) {
if (value.data.id === view.resourceId) {
continue;
}
newValues.push(value);
}
this.widgetMap.resourceFilter.value = newValues;
},
getSubViewTotalHoursCountHeader(widget, resourceId, newEventRecordDuration) {
let id = null;
if (widget) {
id = widget.owner.resourceId;
}
const events = calendar.events;
if (!resourceId) {
resourceId = id;
}
let totalEventsDuration = 0;
for (const event of events) {
if (event && event.resourceId === resourceId && calendar.isSameDay(event.startDate, calendar.date)) {
totalEventsDuration = totalEventsDuration + event.data.duration;
}
}
if (!totalEventsDuration) {
totalEventsDuration = "--";
}
if (newEventRecordDuration) {
totalEventsDuration = totalEventsDuration + newEventRecordDuration;
}
if (Number.isInteger(totalEventsDuration)) {
// If there are no decimal places, return the number as is
totalEventsDuration = totalEventsDuration;
} else if (Ext.isNumeric(totalEventsDuration)) {
// If there are decimal places, round to 2 decimal places
totalEventsDuration = Ext.Number.toFixed(totalEventsDuration, 2);
}
return `<span class="total-event-duration blue fat">${totalEventsDuration} ${UCare4.util.Translator.translate(
"hours_short"
)}</span>`;
},
isSameDay: (eventDate, calendarDate) => {
// Create Date objects for the given date and today's date
const inputDate = new Date(eventDate);
// Set hours, minutes, seconds, and milliseconds to 0 for accurate comparison
inputDate.setHours(0, 0, 0, 0);
calendarDate.setHours(0, 0, 0, 0);
// Compare the two Date objects
return inputDate.getTime() === calendarDate.getTime();
},
executeDragEnd: async (event, addNewDuration) => {
let result = false,
dragTargetResource = event.event.target && calendar.resolveResourceRecord(event.event.target);
if (event.eventRecord.data.type === "TRAVEL") {
UCare4.util.Notificator.show(
"{event_travel_time_may_not_be_changed}",
UCare4.util.Notificator.WARNING,
2000,
true,
true
);
return false;
}
const dateIsAvailable = calendar.eventStore.isDateRangeAvailable(
event.newStartDate,
event.newEndDate,
event.eventRecord,
dragTargetResource
);
if (!dateIsAvailable) {
return false;
}
if (
event.eventRecord.originalData.resourceId !== dragTargetResource.data.id ||
event.eventRecord.data.resourceId !== dragTargetResource.data.id
) {
return false;
}
// Show MessageBox and wait for user response
const userResponse = await new Promise((resolve) => {
UCare4.ux.MessageBox.showConfirm(
UCare4.util.Translator.translate("calculate_travel_time"),
UCare4.util.Translator.translate("calculate_travel_time_message"),
(choice) => {
resolve(choice);
}
);
});
// Validate based on user response
if (userResponse === "yes") {
result = await calendar.handleYesResponse(event);
} else {
result = await calendar.handleNoResponse(event);
}
if (!result) return false;
if (event.eventRecord && event.eventRecord.data) {
if (addNewDuration) {
const startDateUtc = me.getUtcDate(event.newStartDate),
endDateUtc = me.getUtcDate(event.newEndDate),
newDuration = me.getDurationInHoursFromUtc(startDateUtc, endDateUtc);
event.eventRecord.data["duration"] = newDuration;
calendar.recalculateEventDurationWidget(event);
} else {
calendar.recalculateEventDurationWidget(event);
}
}
return true;
},
recalculateEventDurationWidget: (event, duration = null) => {
if (event.view) {
let resourceView = event.view.element,
el = resourceView.querySelector(".total-event-duration"),
newHtml = calendar.getSubViewTotalHoursCountHeader(null, event.eventRecord.data.resourceId, duration);
el.outerHTML = newHtml;
} else {
calendar.recalculateEventDurationWidgetForAllResources(event.eventRecord.data.resourceId, duration);
}
},
recalculateEventDurationWidgetForAllResources: function (resourceIdFromEvent = null, duration) {
//refresh resourceInfo widget
let resourceViewContent = document.querySelector(".b-resourceview-content").children;
for (let resource of resourceViewContent) {
const firstObjectIndex = 0; // Arrays are zero-indexed, so the second object is at index 1
const lastObjectIndex = resourceViewContent.length - 1; //
if (
resourceViewContent[firstObjectIndex].$refOwnerId !== resource.$refOwnerId &&
resourceViewContent[lastObjectIndex].$refOwnerId !== resource.$refOwnerId
) {
const el = resource.querySelector(".total-event-duration"),
resourceRecordFromEl = calendar.resolveResourceRecord(resource);
if (resourceRecordFromEl && resourceIdFromEvent === resourceRecordFromEl.data.id) {
newHtml = calendar.getSubViewTotalHoursCountHeader(null, resourceIdFromEvent, duration);
el.outerHTML = newHtml;
} else if (resourceIdFromEvent === null && resourceRecordFromEl && resourceRecordFromEl.data.id) {
newHtml = calendar.getSubViewTotalHoursCountHeader(null, resourceRecordFromEl.data.id);
el.outerHTML = newHtml;
}
}
}
},
onFilterCriteriaChange() {
this.eventStore.filter();
this.modes.dayresource.onResourceFilterSelectionChange();
}
});