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:
-
KanbanBoardwraps the entire board in aDragDropContextand manages theonDragEndcallback. When a card is dropped, it reads thesourceanddestinationfrom the result, removes the deal from the source column, inserts it into the destination column at the correct index, and updates the deal'sstagefield. -
Each
KanbanColumnis aDroppablecontainer identified by itsstageId. 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. -
Each
KanbanCardis aDraggableidentified by thedealId. It renders a card with deal summary information. Theprovided.draggablePropsandprovided.dragHandlePropsare spread onto the card element, andprovided.innerRefis 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
- Tasks & Email -- task management, calendar, and email
- Reports & Analytics -- sales dashboards and pipeline analytics
- Contacts & Companies -- contact and company management