@progress/kendo-angular-scheduler
Version:
Kendo UI Scheduler Angular - Outlook or Google-style angular scheduler calendar. Full-featured and customizable embedded scheduling from the creator developers trust for professional UI components.
177 lines (176 loc) • 7.25 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { TaskCollection, compose } from './tasks.collection';
import { MS_PER_DAY, toLocalDate } from '@progress/kendo-date-math';
import { intersects, eventResources, toUTCDate, addUTCDays, getUTCDate } from '../utils';
import { getField } from '../../common/util';
/**
* @hidden
*/
export function createResourceGroups(groupedResources) {
let result = [];
const firstResource = groupedResources[0];
const firstResourceData = firstResource.data;
for (let dataIdx = 0; dataIdx < firstResourceData.length; dataIdx++) {
const item = firstResourceData[dataIdx];
result.push({ resources: [getField(item, firstResource.textField)] });
}
for (let idx = 1; idx < groupedResources.length; idx++) {
const resource = groupedResources[idx];
const data = resource.data;
const current = [];
for (let resourceIdx = 0; resourceIdx < result.length; resourceIdx++) {
const resourceItem = result[resourceIdx];
for (let dataIdx = 0; dataIdx < data.length; dataIdx++) {
const item = data[dataIdx];
current.push({ resources: resourceItem.resources.concat(getField(item, resource.textField)) });
}
}
result = current;
}
return result;
}
function createTask(item, resourceIdx, resources, color) {
const event = item.event;
return {
event,
start: item.start.toUTCDate(),
end: item.end.toUTCDate(),
title: event.title,
isAllDay: event.isAllDay,
color,
resourceIdx,
resources
};
}
const durationInDays = ({ start, end, isAllDay = false }) => {
const eventEnd = isAllDay ? getUTCDate(end) : end;
const duration = Math.ceil((eventEnd - +getUTCDate(start)) / MS_PER_DAY);
if (isAllDay && duration > 0 && !endsAtMidnight(end)) {
return duration + 1;
}
return duration;
};
const endsAtMidnight = (end) => {
end = toLocalDate(end);
return (end.getHours() === 0 && end.getMinutes() === 0 && end.getSeconds() === 0 && end.getMilliseconds() === 0);
};
const curry = fn => {
const len = fn.length;
return (...args) => len === args.length
? fn(...args)
: curry(fn.bind(null, ...args));
};
const range = num => Array.from(new Array(num).keys());
const cloneTask = (eventStart, eventEnd) => task => ({
...task,
start: getUTCDate(eventStart),
end: task.tail && !task.isAllDay ? eventEnd : addUTCDays(eventStart, 1),
startDate: getUTCDate(eventStart)
});
const previousEventEnd = (start, events) => events.length
? events[events.length - 1].end
: start;
const markAsTail = isLast => task => {
if (isLast) {
task.tail = true;
}
return task;
};
const markAsMid = task => {
if (!task.tail) {
task.mid = true;
}
return task;
};
const addTaskPart = (task, start) => (tasks, _, day, days) => tasks.concat(compose(markAsMid, cloneTask(previousEventEnd(start, tasks), task.end), markAsTail(day === days.length - 1))(task));
const splitMultiDayTask = (task, start) => range(durationInDays(task) - 1)
.reduce(addTaskPart(task, start), []);
/**
* @hidden
*/
export const splitTasks = curry((periodStart, periodEnd, tasks) => {
const result = [];
for (let index = 0; index < tasks.length; index++) {
const task = { ...tasks[index] };
task.startDate = getUTCDate(task.start);
if (task.start >= periodStart && task.start <= periodEnd) {
result.push(task);
}
if (durationInDays(task) > 1) {
task.end = addUTCDays(task.startDate, 1);
task.head = true;
result.push(...splitMultiDayTask({ ...tasks[index] }, task.end)
.filter(tsk => getUTCDate(tsk.end) <= periodEnd && tsk.start >= periodStart));
}
}
return result;
});
function groupByResource(groupedResources, resourceGroups, dateRange) {
const groups = resourceGroups.filter(group => group.tasks && group.tasks.length);
if (!groups.length) {
return [];
}
const values = groups[0].resources.map(resource => ({ value: resource, span: 0, groupIdx: 0 }));
const periodStart = toUTCDate(dateRange.start);
const periodEnd = toUTCDate(dateRange.end);
for (let groupIdx = 0; groupIdx < groups.length; groupIdx++) {
const group = groups[groupIdx];
group.tasks = splitTasks(periodStart, periodEnd, group.tasks);
const count = group.tasks.length;
group.tasks = new TaskCollection(periodStart, periodEnd, group.tasks);
let invalidate = false;
for (let resourceIdx = 0; resourceIdx < groupedResources.length; resourceIdx++) {
const resourceValue = values[resourceIdx];
if (resourceValue.value !== group.resources[resourceIdx] || invalidate) {
resourceValue.value = group.resources[resourceIdx];
const spanGroup = groups[resourceValue.groupIdx];
spanGroup.spans = spanGroup.spans || [];
spanGroup.spans[resourceIdx] = resourceValue.span;
resourceValue.span = count;
resourceValue.groupIdx = groupIdx;
invalidate = true;
}
else {
resourceValue.span += count;
}
}
}
values.forEach((value, index) => {
const group = groups[value.groupIdx];
group.spans = group.spans || [];
group.spans[index] = value.span;
});
return groups;
}
/**
* @hidden
*/
export function groupEvents(items, { taskResources, resourceGroups, spans, allResources, dateRange }) {
const groups = resourceGroups || [{}];
const periodStart = toUTCDate(dateRange.start);
const periodEnd = toUTCDate(dateRange.end);
for (let idx = 0; idx < items.length; idx++) {
const item = items[idx];
const event = item.event;
if (!intersects(item.start.toUTCDate(), item.end.toUTCDate(), periodStart, periodEnd)) {
continue;
}
const resources = eventResources(event, { taskResources, hasGroups: resourceGroups && resourceGroups.length > 0, spans, allResources });
if (resources.length && resources[0].leafIdx >= 0) {
for (let resourceIdx = 0; resourceIdx < resources.length; resourceIdx++) {
const current = resources[resourceIdx];
const task = createTask(item, current.leafIdx, current.resources, current.color);
const tasks = groups[current.leafIdx].tasks = groups[current.leafIdx].tasks || [];
tasks.push(task);
}
}
}
if (resourceGroups) {
return groupByResource(taskResources, groups, dateRange);
}
groups[0].tasks = new TaskCollection(periodStart, periodEnd, splitTasks(periodStart, periodEnd, groups[0].tasks || []));
return groups;
}