All questions

Gantt Chart

Premium

Gantt Chart

Build a Gantt chart as a single React component: five project tasks drawn as horizontal bars across a shared 14-day timeline. It's a CSS-grid layout problem wearing a scheduling costume — each bar is positioned by grid-column, not by pixels — plus a small slice of state: clicking a bar selects it, and two buttons nudge the selected task earlier or later in time.

Task

The starter App.tsx renders the chart statically: a day-number header (1–14), one row per task, and each bar placed with grid-column: start + 2 / span duration. Make it interactive:

  1. Hold the tasks in state. Lift the TASKS array into useState, and track a selectedId (string | null, starting null). Map over the stateful tasks so edits re-render.
  2. Select on click. Clicking a bar sets selectedId to that task's id and adds the selected class (a white outline ring) to that one bar.
  3. Show the detail line. When a task is selected, show Name: day X → Y (Nd) — its 1-based start day, end day, and duration. Otherwise show Click a task bar.
  4. Shift with the buttons. The − and + buttons move the selected task's start by ∓1 day, clamped to [0, 14 − duration] so the bar never runs off either end of the track. Disable both while nothing is selected.

Examples

  • The page loads with five bars laid out across the 14 days and Click a task bar below. The − / + buttons are greyed out.
  • Clicking the blue Design bar (start day 3, 4 days long) rings it white and shows Design: day 3 → 7 (4d). The buttons enable.
  • Pressing + slides Design one day later (day 4 → 8). Holding + eventually clamps: Design stops at day 11 (11 + 4 = 15, the far edge), never spilling past day 14.

Notes

  • Placement is grid-column, not left/width. Each bar lives on the same 14-column track; start + 2 / span duration is the whole layout. Column 1 is the name column, so day d sits at grid column d + 2.
  • One selection, derived everywhere. The ring, the detail line, and the buttons' disabled state all read from the single selectedId — don't track them separately.
  • Clamp on write, not on render. Fold the Math.max(0, Math.min(14 − duration, …)) into the state update so start is always valid; nothing downstream has to defend against an off-track value.
  • Styling (dark theme, the grid, bar colors, the selected ring) is already in styles.css; focus on the state and the shift math.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium