Deals & Pipeline
Last updated on 2026-09-02
The deals module is the centerpiece of the CRM. It features a drag-and-drop Kanban pipeline board with real database writes, detailed deal views, pipeline configuration, and revenue forecasting. All data comes from Supabase and all mutations use Server Actions.
Pipeline Kanban Board
Route: /deals
The Kanban board provides a visual, drag-and-drop interface for managing deals across pipeline stages -- with every drag-and-drop persisted to the database.
- Drag-and-drop -- powered by
@hello-pangea/dnd, deal cards can be dragged between columns to update their stage - Database persistence -- when a card is dropped, a Server Action updates the deal's
stage_idin Supabase and the activity trigger auto-logs the stage change - 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
- Column totals -- each column header displays the deal count and total value, updating live as cards are moved
- View toggle -- switch between compact and comfortable card views 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 quick actions
How Data Flows
1. Server Component fetches deals and stages from Supabase
2. Passes data to KanbanBoard client component
3. User drags a card to a new column
4. onDragEnd fires → optimistic UI update
5. Server Action updateDealStage() writes to Supabase
6. Database trigger logs activity with old/new stage
7. revalidatePath() refreshes server data
Kanban Board Architecture
KanbanBoard (DragDropContext)
├── KanbanColumn (Droppable) x 6 stages
│ ├── Column header (title, count, total value, add button)
│ ├── KanbanCard (Draggable) x N deals
│ │ ├── Company name + logo
│ │ ├── Deal value
│ │ ├── Owner avatar
│ │ ├── Close date
│ │ └── Priority badge
│ └── Column footer
└── onDragEnd handler
├── Optimistic state update
└── Server Action → Supabase write
// kanban-board.tsx (simplified)
import { DragDropContext, type DropResult } from "@hello-pangea/dnd"
import { updateDealStage } from "@/lib/actions/deals"
function KanbanBoard({ deals, stages }: KanbanBoardProps) {
const [columns, setColumns] = useState(groupDealsByStage(deals, stages))
async function onDragEnd(result: DropResult) {
const { source, destination, draggableId } = result
if (!destination) return
if (source.droppableId === destination.droppableId
&& source.index === destination.index) return
// Optimistic UI update
const sourceColumn = [...columns[source.droppableId]]
const [movedDeal] = sourceColumn.splice(source.index, 1)
movedDeal.stage_id = 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,
})
// Persist to database
await updateDealStage(draggableId, destination.droppableId)
}
return (
<DragDropContext onDragEnd={onDragEnd}>
{stages.map((stage) => (
<KanbanColumn key={stage.id} stage={stage} deals={columns[stage.id]} />
))}
</DragDropContext>
)
}
Data Sources
| Data | Source | How |
|---|---|---|
| Deals | deals table |
Server Component query with joins |
| Pipeline stages | pipeline_stages table |
Ordered by position |
| Companies | companies table |
Joined on deals.company_id |
| Team members | profiles table |
Joined on deals.owner_id |
Deal Detail
Route: /deals/[id]
A comprehensive view of an individual deal with a hero section, tabbed content, and a summary sidebar. All data is fetched server-side from Supabase.
- 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 from
deal_products, key dates, and deal history timeline fromactivities - Activities -- filtered activity feed from
activitiestable for this deal - Notes -- chronological notes from
activitieswheretype = 'note'; add note form calls thecreateActivityServer Action
- Overview -- deal description, products/line items table from
- Sidebar -- stage select (calls
updateDealStageServer Action), close date picker, assigned owner select, associated contact and company links, tags, and deal source
Deal Create / Edit
Route: /deals/new and /deals/[id]/edit
A Zod-validated form that calls the createDeal or updateDeal Server Action.
- Deal Info -- deal name, description textarea
- Company & Contact -- company select (searchable) and contact select (filtered by selected company); data from Supabase
- Pipeline & Stage -- pipeline select and stage select (dynamically filtered by pipeline)
- Financials -- deal value (currency input), probability (percentage slider), and expected close date
- Priority -- radio group for High, Medium, Low
- Products / Line Items -- repeatable row group written to
deal_productstable - Tags -- multi-select tag picker; writes to
deal_tagsjunction table
Pipeline Settings
Route: /deals/settings
Configuration screen for managing pipelines and their stages. Admin-only -- requires is_crm_admin().
- Pipeline list -- shows all pipelines with stage count and active deal count from Supabase
- Stage management -- ordered list of stages with drag-to-reorder; position changes saved via Server Action
- Stage editor -- name, color picker, default probability, and description; saved via
updatePipelineStageServer Action - Add stage -- appends a new stage via
createPipelineStageServer Action - Delete stage -- removes with confirmation; reassigns deals first
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. All data comes from aggregate Supabase queries via the reports.ts Server Actions.
- Forecast categories -- three tiers:
- Committed -- deals in Negotiation or later with 75%+ probability
- Best Case -- Committed plus Proposal stage deals (50%+)
- Pipeline -- all open deals including early-stage leads
- Monthly forecast bar chart -- grouped bars via Recharts showing Committed, Best Case, and Pipeline per month
- Forecast table -- tabular breakdown with monthly columns
- Team breakdown -- each sales rep with quota (from
profiles.quota), committed revenue, attainment percentage with progress bar, and gap to quota
Next Steps
- Contacts & Companies -- contact and company management with Supabase
- Tasks & Email -- task management, calendar, and email
- Reports & Analytics -- sales dashboards and pipeline analytics