Back to examples

See who is booked, per person and project

Five people, three projects, five weeks. Every cell in the grid is how much of a person’s time is booked on that day, measured against how much they actually have. There is no Gantt chart here, and the page does no scheduling or counting of its own: the grid is one engine call.

The usual way to draw a load view is to walk the tasks and add one per working day. That breaks as soon as the team stops being uniform: someone works three mornings a week, someone is on leave, two testers share one queue, a designer is half on one project and half on another. The engine already knows all of that, because it scheduled the plan with it.

Capacity comes from calendars

Each person has their own calendar and capacity, and each assignment carries units. Ana works Monday to Wednesday, 9 to 13. Ben is on leave in the third week, so his Frontend build stops and resumes after it. The QA pair has a capacity of two. Eli is full time on Page designs and half time on App screens, which overlap for three days.

Calendars, capacity and units travel with the plan
// People have their own calendars and capacity; assignments carry units.
project.solve({
  config: { apply_constraints: true },
  calendars: [
    { id: "global",    worktime: { hours: ["8:00-12:00", "13:00-17:00"], days: [0, 1, 1, 1, 1, 1, 0] } },
    { id: "part-time", worktime: { hours: ["9:00-13:00"], days: [0, 1, 1, 1, 0, 0, 0] } },
    { id: "ben",       worktime: { /* weekdays */ dates: { "2026-08-24": false, /* … */ "2026-08-28": false } } },
  ],
  resources: [
    { id: "ana", calendar_id: "part-time" },        // Mon–Wed mornings
    { id: "ben", calendar_id: "ben" },              // on leave 24–28 Aug
    { id: "qa",  calendar_id: "global", capacity: 2 },  // two testers
    // …
  ],
  assignments: [
    { task: 2, resource: "eli", value: 1 },         // Page designs, full time
    { task: 7, resource: "eli", value: 0.5 },       // App screens, half time
    // …
  ],
  tasks: [ /* 15 tasks in 3 projects */ ],
  links: [ /* … */ ],
});

One call, the whole grid

loadProfile reads the solved plan and returns, for every person, one bucket per day or hour with its load and capacity. It does not change the plan. With tasks: "all"each bucket also lists the tasks booked in it, which is what the panel under the grid shows when you click a cell.

Ask for the load of a date range
const profile = project.loadProfile({
  from: "2026-08-10 00:00", to: "2026-09-14 00:00",
  basis: "day",                 // or "hour"
  tasks: "all",                 // list what is booked in each bucket
  resources: ["ana", "eli"],    // optional: only these people
});

Capacity is per bucket, not a constant. A person with a calendar gets capacity only in their working time, so Ana has none on Thursdays and Fridays and Ben has none during his leave. The hatched cells are those buckets. Load is in units of a person’s time: Eli’s 1.5 is a full-time task and a half-time task on the same day.

What the engine returns
// The demo creates its project with new Project({ dates: "date" }),
// so bucket starts arrive as Date values.
{ basis: "day", resources: [
  { resource: "eli", capacityMode: "calendar", buckets: [
      // …
      { start: new Date(2026, 7, 17), load: 1.5, capacity: 1, tasks: [2, 7] },
      // …
  ] },
  { resource: "ben", capacityMode: "calendar", buckets: [
      // …
      { start: new Date(2026, 7, 24), load: 0, capacity: 0, tasks: [] },  // on leave
      // …
  ] },
] }

Per person, per project

The team buttons pass resources, so the engine computes the load only for those people. The project buttons work on the same answer: each bucket lists its tasks, and the page adds up the units of the tasks in that project. Pick Mobile app and you see how much of the team it takes, against everyone’s full capacity.

A project's share of a bucket
// The engine reports load per person. A project's share is the units
// of its tasks among the ones booked in the bucket.
const share = bucket.tasks
  .map(id => tasks.get(id))
  .filter(task => task.project === "app")
  .reduce((sum, task) => sum + task.units, 0);

Days or hours

Switch to Hours and the same call runs with basis: "hour" for one week. Now the calendars show in full: Ana’s four morning hours, which run through the lunch hour everyone else has off, and the exact hours where Dee carries the API and a hotfix at the same time.

What is next

This view tells you who is overbooked. The next demo asks the engine to fix it: it delays or splits tasks until nobody is overbooked, and reports what it changed.

The engine and its benchmarks are in the plea repository ↗