Skip to content

Grouping

Use resourceGrouping for team, department, location, room-building, machine-line, or sprint-team grouping. Parent-child resources can render as a tree.

grid.eventSchedulerResources = [
{ id: 'north', name: 'North Campus', role: 'Location' },
{ id: 'room-a', parentId: 'north', name: 'Room A', role: 'Room', metadata: { floor: '1' } },
{ id: 'room-b', parentId: 'north', name: 'Room B', role: 'Room', metadata: { floor: '2' } },
];
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
resourceGrouping: {
enabled: true,
tree: true,
groupBy: (resource) => String(resource.metadata?.floor ?? resource.group ?? 'Other'),
groupSummaryFormatter: ({ summary }) =>
`${summary.eventCount} events - ${summary.totalHours}h`,
},
};

Group rows aggregate workload, coverage, and utilization summaries when those features are enabled.

Use resourceTimeline when resources are the main rows and time moves horizontally.

grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
dateRange: { start: '2026-06-08', end: '2026-06-14' },
resourceColumnSize: 220,
timelineColumnSize: 72,
resourceGrouping: {
enabled: true,
groupBy: (resource) => resource.metadata?.location as string,
tree: true,
collapsed: false,
groupSummaryFormatter: ({ summary }) =>
`${summary.eventCount} events - ${summary.totalHours}h`,
},
resourceMetaFormatter: (resource) =>
[resource.role, resource.metadata?.location].filter(Boolean).join(' - '),
};

Events without resourceId, resourceIds, or side-channel assignments render in the generated unassigned row when unassigned.enabled is not false.

grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
unassigned: {
enabled: true,
showCount: true,
requiredRoleField: 'metadata.requiredRole',
filter: { status: ['draft', 'planned'], role: 'Technician' },
counterFormatter: (summary) => `${summary.visibleCount} open requests`,
requiredRoleFormatter: ({ role }) => `Need ${role}`,
assignmentValidator: ({ event, resource }) => {
const required = event.metadata?.requiredRole;
const skills = Array.isArray(resource.metadata?.skills)
? resource.metadata.skills
: [];
return !required || skills.includes(required) || `${resource.name} cannot fill ${required}.`;
},
},
};

Coverage compares required capacity with assigned event capacity in the visible range. Utilization calculates scheduled workload by resource.

grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
coverage: {
enabled: true,
capacityField: 'capacity',
countEvent: ({ event, resource }) =>
event.status === 'cancelled' ? false : resource.capacity ?? 1,
},
utilization: {
enabled: true,
minHoursField: 'metadata.minHoursPerWeek',
targetHoursField: 'metadata.targetHoursPerWeek',
maxHoursField: 'metadata.maxHoursPerWeek',
excludeStatuses: ['cancelled', 'blocked'],
},
};

Use filters and savedViews to reduce projected data without mutating source arrays:

grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
filters: {
search: 'security',
resourceRoles: 'Security',
eventStatuses: ['planned', 'confirmed'],
conflictsOnly: false,
unassignedOnly: false,
},
activeSavedViewId: 'security-week',
savedViews: {
views: [
{ id: 'conflicts', label: 'Conflicts only', filters: { conflictsOnly: true } },
{ id: 'unassigned', label: 'Unassigned', filters: { unassignedOnly: true } },
{
id: 'security-week',
label: 'Security team',
dateRange: { start: '2026-06-08', end: '2026-06-12' },
filters: { resourceRoles: 'Security' },
},
],
},
};

The projection exposes filterSummary with visible and hidden resource/event counts.