Deals & Pipeline

Last updated on 2026-03-22

The deals module is the centerpiece of the CRM. It features a drag-and-drop Kanban pipeline board, detailed deal views, pipeline configuration, and revenue forecasting. All screens are in the (dashboard) route group.

Pipeline Kanban Board

Route: /deals

The Kanban board is the hero feature of the CRM Dashboard Kit. It provides a visual, drag-and-drop interface for managing deals across pipeline stages.

  • Drag-and-drop -- powered by @hello-pangea/dnd, deal cards can be dragged between columns to update their stage
  • Pipeline columns -- Lead, Qualified, Proposal, Negotiation, Closed Won, and Closed Lost, each with a distinct color indicator
  • Deal cards -- each card shows company name (with logo), deal value, owner avatar, close date, and priority badge (high/medium/low)
  • Column totals -- each column header displays the deal count and total value, updating live as cards are moved
  • View toggle -- switch between compact view (minimal card info) and comfortable view (full card details) using ToggleGroup
  • Add deal -- "+" button on each column header to create a new deal pre-set to that stage
  • Card actions -- click a card to navigate to the deal detail page; hover to reveal a quick actions menu (Edit, Delete)
import { KanbanBoard } from "@/components/deals/kanban-board"
import { KanbanColumn } from "@/components/deals/kanban-column"
import { KanbanCard } from "@/components/deals/kanban-card"

Kanban Board Layout

┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
│ Lead     │ Qualified│ Proposal │ Negotiate│ Won      │ Lost     │
│ 5 $120K  │ 3 $85K   │ 4 $210K  │ 2 $95K   │ 6 $340K  │ 2 $45K   │
├──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤
│ ┌──────┐ │ ┌──────┐ │ ┌──────┐ │ ┌──────┐ │ ┌──────┐ │ ┌──────┐ │
│ │ Card │ │ │ Card │ │ │ Card │ │ │ Card │ │ │ Card │ │ │ Card │ │
│ └──────┘ │ └──────┘ │ └──────┘ │ └──────┘ │ └──────┘ │ └──────┘ │
│ ┌──────┐ │ ┌──────┐ │ ┌──────┐ │ ┌──────┐ │ ┌──────┐ │ ┌──────┐ │
│ │ Card │ │ │ Card │ │ │ Card │ │ │ Card │ │ │ Card │ │ │ Card │ │
│ └──────┘ │ └──────┘ │ └──────┘ │ └──────┘ │ └──────┘ │ └──────┘ │
└──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘

Kanban Board Architecture

The Kanban board uses a three-tier component hierarchy built on @hello-pangea/dnd:

KanbanBoard (DragDropContext)
├── KanbanColumn (Droppable) × 6 stages
│   ├── Column header (title, count, total value, add button)
│   ├── KanbanCard (Draggable) × N deals
│   │   ├── Company name + logo
│   │   ├── Deal value
│   │   ├── Owner avatar
│   │   ├── Close date
│   │   └── Priority badge
│   └── Column footer
└── onDragEnd handler (updates deal stage in state)

How it works:

  1. KanbanBoard wraps the entire board in a DragDropContext and manages the onDragEnd callback. When a card is dropped, it reads the source and destination from the result, removes the deal from the source column, inserts it into the destination column at the correct index, and updates the deal's stage field.

  2. Each KanbanColumn is a Droppable container identified by its stageId. It receives the filtered list of deals for that stage and renders them as an ordered list. The column header displays the stage name, deal count, and summed deal value.

  3. Each KanbanCard is a Draggable identified by the dealId. It renders a card with deal summary information. The provided.draggableProps and provided.dragHandleProps are spread onto the card element, and provided.innerRef is used as the ref callback.

// kanban-board.tsx (simplified)
import { DragDropContext, type DropResult } from "@hello-pangea/dnd"

function KanbanBoard({ deals, stages }: KanbanBoardProps) {
  const [columns, setColumns] = useState(groupDealsByStage(deals, stages))

  function onDragEnd(result: DropResult) {
    const { source, destination } = result
    if (!destination) return
    if (source.droppableId === destination.droppableId && source.index === destination.index) return

    // Move deal between columns
    const sourceColumn = [...columns[source.droppableId]]
    const [movedDeal] = sourceColumn.splice(source.index, 1)
    movedDeal.stage = destination.droppableId

    const destColumn = source.droppableId === destination.droppableId
      ? sourceColumn
      : [...columns[destination.droppableId]]
    destColumn.splice(destination.index, 0, movedDeal)

    setColumns({
      ...columns,
      [source.droppableId]: sourceColumn,
      [destination.droppableId]: destColumn,
    })
  }

  return (
    <DragDropContext onDragEnd={onDragEnd}>
      {stages.map((stage) => (
        <KanbanColumn key={stage.id} stage={stage} deals={columns[stage.id]} />
      ))}
    </DragDropContext>
  )
}

Data Sources

Data Source Location
Deals deals data/seed.ts
Pipeline stages pipelineStages data/seed.ts
Companies companies data/seed.ts
Team members teamMembers data/seed.ts

Deal Detail

Route: /deals/[id]

A comprehensive view of an individual deal with a hero section, tabbed content, and a summary sidebar.

  • Deal hero -- deal name, company link, value (formatted as currency), stage badge with color, probability percentage, and priority indicator
  • Tabbed content -- three tabs:
    • Overview -- deal description, products/line items table with quantities and amounts, key dates (created, last updated, expected close), and deal history timeline
    • Activities -- filtered activity feed for this deal showing calls, emails, meetings, and notes
    • Notes -- chronological list of notes with author avatar, timestamp, and content; add note form at the top
  • Sidebar -- stage select (change stage inline), close date picker, assigned owner select, associated contact and company links, tags, and deal source
import { DealHero } from "@/components/deals/deal-hero"
import { DealSidebar } from "@/components/deals/deal-sidebar"
import { DealTabs } from "@/components/deals/deal-tabs"
import { LineItemsTable } from "@/components/deals/line-items-table"

Deal Create / Edit

Route: /deals/new and /deals/[id]/edit

A Zod-validated form for creating or editing deals, organized into logical sections.

  • Deal Info -- deal name, description textarea
  • Company & Contact -- company select (searchable) and contact select (filtered by selected company)
  • Pipeline & Stage -- pipeline select and stage select (dynamically filtered by pipeline)
  • Financials -- deal value (currency input), probability (percentage slider), and expected close date (date picker)
  • Priority -- radio group for High, Medium, Low with color indicators
  • Products / Line Items -- repeatable row group with product name, quantity, unit price, and calculated line total; add/remove row buttons
  • Tags -- multi-select tag picker
import { DealForm } from "@/components/deals/deal-form"
import { dealSchema } from "@/lib/schemas"

Validation Schema

const dealSchema = z.object({
  name: z.string().min(1, "Deal name is required"),
  description: z.string().optional(),
  companyId: z.string().min(1, "Company is required"),
  contactId: z.string().min(1, "Contact is required"),
  pipelineId: z.string().min(1, "Pipeline is required"),
  stageId: z.string().min(1, "Stage is required"),
  value: z.number().min(0, "Value must be positive"),
  probability: z.number().min(0).max(100),
  closeDate: z.date(),
  priority: z.enum(["high", "medium", "low"]),
  lineItems: z.array(lineItemSchema).optional(),
  tags: z.array(z.string()),
})

Pipeline Settings

Route: /deals/settings

Configuration screen for managing pipelines and their stages.

  • Pipeline list -- shows all pipelines with name, description, stage count, and active deal count
  • Stage management -- ordered list of stages within the selected pipeline, with drag-to-reorder support
  • Stage editor -- each stage has a name, color picker (using oklch tokens), default probability percentage, and description
  • Add stage -- button to append a new stage to the pipeline
  • Delete stage -- removes a stage with a confirmation dialog warning about deals that will need reassignment
  • Win/loss reasons -- configurable lists of reasons for Closed Won and Closed Lost outcomes, used in deal close workflows
import { PipelineSettings } from "@/components/deals/pipeline-settings"
import { StageEditor } from "@/components/deals/stage-editor"

Default Pipeline Stages

Stage Color Default Probability
Lead gray 10%
Qualified blue 25%
Proposal indigo 50%
Negotiation violet 75%
Closed Won green 100%
Closed Lost red 0%

Forecasting

Route: /deals/forecast

Revenue forecasting dashboard showing projected revenue by month and team performance against quotas.

  • Date range selector -- month picker for the forecast period (current quarter by default)
  • Forecast categories -- three tiers displayed as stacked bar chart:
    • Committed -- deals in Negotiation stage or later with high probability (75%+)
    • Best Case -- Committed plus deals in Proposal stage (50%+ probability)
    • Pipeline -- all open deals including early-stage leads
  • Monthly forecast bar chart -- grouped bars showing Committed, Best Case, and Pipeline values per month via Recharts BarChart
  • Forecast table -- tabular breakdown with monthly columns and category rows, including totals
  • Team breakdown -- table of each sales rep with their quota, committed revenue, attainment percentage (with progress bar), and gap to quota
  • Quota attainment -- visual progress bars with color coding: green (100%+), amber (75-99%), red (below 75%)
import { ForecastChart } from "@/components/charts/forecast-chart"
import { ForecastTable } from "@/components/deals/forecast-table"
import { TeamQuotaTable } from "@/components/deals/team-quota-table"

Data Sources

Data Source Location
Deals (for forecast) deals data/seed.ts
Team quotas teamMembers (quota field) data/seed.ts
Pipeline stages pipelineStages data/seed.ts

Next Steps