# Grouping
URL: https://pro.rv-grid.com/guides/event-scheduler/grouping/
Source: src/content/docs/guides/event-scheduler/grouping.mdx
Description: Group scheduler resources by team, department, location, parent tree, or custom business rules.
Use `resourceGrouping` for team, department, location, room-building, machine-line, or sprint-team grouping. Parent-child resources can render as a tree.

```ts
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.

## Resource Planner Patterns

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

```ts
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.

```ts
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.

```ts
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:

```ts
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.
