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_id in 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 from activities
    • Activities -- filtered activity feed from activities table for this deal
    • Notes -- chronological notes from activities where type = 'note'; add note form calls the createActivity Server Action
  • Sidebar -- stage select (calls updateDealStage Server 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_products table
  • Tags -- multi-select tag picker; writes to deal_tags junction 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 updatePipelineStage Server Action
  • Add stage -- appends a new stage via createPipelineStage Server 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