Lattice Grid Buy a licence

demo D276

A project plan, balanced across people

Put people on tasks and see who is booked past what they can do: the clashing bars are outlined and the avatar ringed, then one press shifts the lower-priority work later until the plan is clear

gantt.overAllocations · gantt.level()

Building…
Loading a live grid…

The configuration

'gantt-resource-management': () => ({
  rows: [],
  config: {},
  foot: [
    'each bar carries the person on the task, drawn as an avatar beside it',
    'one person owns two tasks that run at the same time, so they are booked past what they can do: the clashing bars are outlined and the avatar is ringed',
    'press Level to shift the lower-priority task later until no one is over-booked, keeping the dependency order and stepping over weekends',
    'the summary bars roll up from their children and the dependency links are drawn as arrows',
  ],
  mount: (el: HTMLElement, LG: any) => {
    void LG;
    const bar = document.createElement('div');
    bar.style.cssText = 'display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:12px';
    const levelBtn = toolButton('Level the plan');
    const resetBtn = toolButton('Reset');
    const status = document.createElement('span');
    status.style.cssText = 'font-size:.8125rem;color:var(--ink-2)';
    bar.append(levelBtn, resetBtn, status);
    const wrap = document.createElement('div');
    wrap.style.cssText = 'border:1px solid var(--rule);border-radius:9px;background:var(--paper);min-width:0;overflow:auto';
    el.append(bar, wrap);
    let gantt: any;
    let view: any;
    let disposed = false;
    const resources = [
      { id: 'Ana Ruiz', capacity: 1 },
      { id: 'Bo Ito', capacity: 1 },
      { id: 'Cy Okafor', capacity: 1 },
    ];
    loadGantt()
      .then((G: any) => {
        if (disposed) return;
        const { tasks, dependencies } = resourcePlan();
        const build = () => {
          gantt = G.createGantt({
            tasks,
            dependencies,
            resources,
            projectStart: '2027-01-04',
            calendar: 'weekends',
            autoSchedule: true,
          });
        };
        const render = () => {
          view?.destroy?.();
          view = gantt.mountSplit(wrap, {
            gridWidth: 300,
            rowHeight: 34,
            height: 440,
            zoom: 'day',
            nonWorking: 'weekends',
            showArrows: true,
            showProgress: true,
          });
          const over = gantt.overAllocations ?? [];
          if (over.length) {
            const names = Array.from(new Set(over.map((o: any) => o.resource))).join(', ');
            status.textContent = `Over-booked: ${names}. Press Level to clear it.`;
            status.style.color = '#c0392b';
            levelBtn.disabled = false;
          } else {
            status.textContent = 'Everyone is within capacity.';
            status.style.color = 'var(--ink-2)';
            levelBtn.disabled = true;
          }
        };
        build();
        render();
        levelBtn.addEventListener('click', () => {
          gantt.level();
          render();
        });
        resetBtn.addEventListener('click', () => {
          gantt.destroy?.();
          build();
          render();
        });
      })
      .catch((err) => console.error('[gantt-resource]', err));
    return () => {
      disposed = true;
      view?.destroy?.();
      gantt?.destroy?.();
    };
  },
})