# CRM Pipeline Page

> A React CRM pipeline page: pipeline switcher, search and filters, a deals board or list with totals, a deal sheet and a new deal dialog.

- Type: React block (Blocks)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.1
- Install: `npx wingo-ui@latest add crm-pipeline-page`
- Docs: https://wingo-ui.com/components/crm-pipeline-page

## Interaction

Switch the pipeline from the title (B2B sales or Partnerships; a bottom sheet on phones): the filters reset, the view stays. Pick a close date range in the header (This month, Next month, This quarter, Next 90 days, or two months of days; clear it with its ×). Type in the search to find a deal by title, company or contact, press Mine, No activity 14+ days or Over $10,000, and Clear filters to reset; the new count is announced. The board's own toolbar switches Board and List, filters by owner chips and hides or shows the won and lost columns; its summary follows the filters. Drag a card to another stage (long press on touch, or focus it and use Space and the arrows), or use its ⋯ menu (Move to, Mark as won, Mark as lost, Open, Delete). A drop on Lost asks for the reason first. Moves save optimistically; a failed save springs the card back with a toast. + Add deal under a column adds one by title. Click, tap or press Enter on a card to open its sheet (from the right, a tall bottom sheet on phones): rename it inline, change the value, stage, close date and owner, read the contact and the details, write a note (⌘ Enter adds it) and read the activity; Save appears once something changed, Mark as won and Lost close it (the reason dialog requires a reason), Reopen brings a closed deal back, ⋯ deletes it with an undo toast. Closing the sheet with unsaved changes asks Discard your changes? first; Escape closes it and focus returns to the card. New deal (or + on phones) opens a validated form: title, a company combobox that can add a new company, its contacts, value, stage, close date (30 days out by default) and owner; the new deal lands in its column. Sections rise in once on mount; reduced motion keeps everything still.

## Installation

```bash
npx wingo-ui@latest add crm-pipeline-page
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/crm-pipeline-page` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/blocks/crm-pipeline-page.tsx`
- `components/blocks/crm-pipeline-page-data.ts`

npm dependencies: `npm i motion lucide-react react-hook-form zod`

Wingo UI dependencies (installed automatically):

- [Alert](https://wingo-ui.com/components/alert) ([Markdown](https://wingo-ui.com/components/alert.md))
- [Alert Dialog](https://wingo-ui.com/components/alert-dialog) ([Markdown](https://wingo-ui.com/components/alert-dialog.md))
- [Avatar](https://wingo-ui.com/components/avatar) ([Markdown](https://wingo-ui.com/components/avatar.md))
- [Badge](https://wingo-ui.com/components/badge) ([Markdown](https://wingo-ui.com/components/badge.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Combobox](https://wingo-ui.com/components/combobox) ([Markdown](https://wingo-ui.com/components/combobox.md))
- [Contact card](https://wingo-ui.com/components/contact-card) ([Markdown](https://wingo-ui.com/components/contact-card.md))
- [Currency Input](https://wingo-ui.com/components/currency-input) ([Markdown](https://wingo-ui.com/components/currency-input.md))
- [Date Picker](https://wingo-ui.com/components/date-picker) ([Markdown](https://wingo-ui.com/components/date-picker.md))
- [Date Range Picker](https://wingo-ui.com/components/date-range-picker) ([Markdown](https://wingo-ui.com/components/date-range-picker.md))
- [Deal Pipeline](https://wingo-ui.com/components/deal-pipeline) ([Markdown](https://wingo-ui.com/components/deal-pipeline.md))
- [Description List](https://wingo-ui.com/components/description-list) ([Markdown](https://wingo-ui.com/components/description-list.md))
- [Dialog](https://wingo-ui.com/components/dialog) ([Markdown](https://wingo-ui.com/components/dialog.md))
- [Dropdown Menu](https://wingo-ui.com/components/dropdown-menu) ([Markdown](https://wingo-ui.com/components/dropdown-menu.md))
- [Empty State](https://wingo-ui.com/components/empty-state) ([Markdown](https://wingo-ui.com/components/empty-state.md))
- [Inline Edit](https://wingo-ui.com/components/inline-edit) ([Markdown](https://wingo-ui.com/components/inline-edit.md))
- [Input](https://wingo-ui.com/components/input) ([Markdown](https://wingo-ui.com/components/input.md))
- [Kbd](https://wingo-ui.com/components/kbd) ([Markdown](https://wingo-ui.com/components/kbd.md))
- [Page Header](https://wingo-ui.com/components/page-header) ([Markdown](https://wingo-ui.com/components/page-header.md))
- [Search Input](https://wingo-ui.com/components/search-input) ([Markdown](https://wingo-ui.com/components/search-input.md))
- [Select](https://wingo-ui.com/components/select) ([Markdown](https://wingo-ui.com/components/select.md))
- [Sheet](https://wingo-ui.com/components/sheet) ([Markdown](https://wingo-ui.com/components/sheet.md))
- [Tag](https://wingo-ui.com/components/tag) ([Markdown](https://wingo-ui.com/components/tag.md))
- [Textarea](https://wingo-ui.com/components/textarea) ([Markdown](https://wingo-ui.com/components/textarea.md))
- [Timeline](https://wingo-ui.com/components/timeline) ([Markdown](https://wingo-ui.com/components/timeline.md))
- [Toast](https://wingo-ui.com/components/toast) ([Markdown](https://wingo-ui.com/components/toast.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [Commerce](https://wingo-ui.com/components/commerce) ([Markdown](https://wingo-ui.com/components/commerce.md))
- [Date utils](https://wingo-ui.com/components/date-utils) ([Markdown](https://wingo-ui.com/components/date-utils.md))
- [Form validation](https://wingo-ui.com/components/form-validation) ([Markdown](https://wingo-ui.com/components/form-validation.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [Number format](https://wingo-ui.com/components/number-format) ([Markdown](https://wingo-ui.com/components/number-format.md))
- [Relative time](https://wingo-ui.com/components/relative-time) ([Markdown](https://wingo-ui.com/components/relative-time.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
// app/(admin)/deals/page.tsx (the shell lives in app/(admin)/layout.tsx: <AdminShell layout="fill">{children}</AdminShell>)
"use client"

import { CrmPipelinePage, type CrmDeal } from "@/components/blocks/crm-pipeline-page"

export default function OportunitatiPage() {
  return (
    <CrmPipelinePage
      height="fill"
      currentUserId="ana-pop"
      onDealMove={async (move) => {
        await fetch(`/api/deals/${move.deal.id}`, {
          method: "PATCH",
          body: JSON.stringify({ stageId: move.to.id, lostReason: move.lostReason }),
        })
      }}
      onDealCreate={async (deal: CrmDeal) => {
        const response = await fetch("/api/deals", { method: "POST", body: JSON.stringify(deal) })
        return (await response.json()) as CrmDeal
      }}
    />
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `pipelines?` | `CrmPipeline[]` |  | The pipelines ({ id, name, stages: DealStage[] }) of the title switcher; defaults to CRM_PIPELINE_SAMPLE.pipelines (B2B sales and Partnerships). |
| `pipeline?` | `string` |  | The shown pipeline's id, controlled; pair it with onPipelineChange. |
| `defaultPipeline?` | `string` | `"b2b-sales"` | The pipeline shown first while uncontrolled. |
| `onPipelineChange?` | `(pipeline: string) => void` |  | Called when the title switcher picks another pipeline. |
| `deals?` | `CrmDeal[]` |  | Every pipeline's deals, controlled (DealData plus companyId, contactId, createdAt, source, products and activity); a deal belongs to the pipeline that has its stage. |
| `defaultDeals?` | `CrmDeal[]` |  | The deals while uncontrolled; defaults to CRM_PIPELINE_SAMPLE.deals (the 12 W8 deals and 4 partnerships). |
| `onDealsChange?` | `(deals: CrmDeal[]) => void` |  | Called with the whole new list after every move, edit, create and delete. |
| `view?` | `"board" \| "list"` |  | The board or the grouped list, controlled. |
| `defaultView?` | `"board" \| "list"` | `"board"` | The view while uncontrolled; switching pipelines keeps it. |
| `onViewChange?` | `(view: DealPipelineView) => void` |  | Called when the board's Board / List switch changes. |
| `owners?` | `{ id: string; name: string; avatar?: string; role?: string }[]` |  | The sales team for the owner pickers; defaults to CRM_PIPELINE_SAMPLE.owners. |
| `companies?` | `CrmPipelineCompany[]` |  | Clients and their contacts for the new deal form and the sheet's contact card; defaults to CRM_PIPELINE_SAMPLE.companies. |
| `lostReasons?` | `string[]` |  | The reasons offered when a deal is lost; defaults to Price, Chose a competitor, Not enough budget, No decision, Other reason. |
| `currentUserId?` | `string` | `"ana-pop"` | Who you are: the Mine chip and the default owner of new deals. |
| `ownerFilter?` | `string[]` |  | The owner ids pressed in the board's owner chips (Mine presses yours), controlled. |
| `defaultOwnerFilter?` | `string[]` |  | The owner filter while uncontrolled; defaults to [] (everyone). |
| `onOwnerFilterChange?` | `(owners: string[]) => void` |  | Called when an owner chip or Mine changes the filter. |
| `closePeriod?` | `DateRange \| null` |  | The expected close date filter, controlled; null shows every date. |
| `defaultClosePeriod?` | `DateRange \| null` |  | The close date filter while uncontrolled; defaults to null. |
| `onClosePeriodChange?` | `(period: DateRange \| null) => void` |  | Called when the header's date range is picked or cleared. |
| `largeDealAmount?` | `number` | `10000` | The threshold of the Over $10,000 chip, in the page currency. |
| `height?` | `"auto" \| "fill" \| "screen"` | `"auto"` | fill takes what is left of a shell with layout="fill" and scrolls the board inside; screen is a standalone h-dvh page; auto grows with the longest column. Phones scroll the page around a tall board. |
| `showSummary?` | `boolean` | `true` | The board's summary tiles (open pipeline, weighted forecast, won this month, win rate, average deal). |
| `defaultShowClosed?` | `boolean` | `true` | Whether the Won and Lost columns start visible (the board's Show closed switch). |
| `staleAfterDays?` | `number` | `14` | Days in a stage before a card shows the stale hint, and the No activity chip's threshold; 0 turns both off. |
| `requireLostReason?` | `boolean` | `true` | A move to Pierdut (drag, menu, sheet) asks for a reason and blocks until one is chosen. |
| `defaultOpenDeal?` | `string` | `""` | A deal id whose sheet opens on mount. |
| `onDealMove?` | `(move: DealMove) => (boolean \| void \| Promise<unknown>)` |  | A drop, a menu move or a sheet move. false refuses it, a promise keeps the card saving and a rejection springs it back with a toast. Without it a 600 ms request is simulated. |
| `onDealSave?` | `(deal: CrmDeal) => (void \| Promise<unknown>)` |  | The sheet's saves (title, value, close date, owner, a note), optimistic; a rejection rolls back with a toast. Simulated by default. |
| `onDealCreate?` | `(deal: CrmDeal) => (void \| CrmDeal \| Promise<void \| CrmDeal>)` |  | A deal from the dialog or a column's quick add; resolve with the stored deal to replace the draft. A rejection shows the error in the dialog. Simulated by default. |
| `onDealDelete?` | `(deal: CrmDeal) => (void \| Promise<unknown>)` |  | Deleting from a card or the sheet, after a confirm; an undo toast follows, a rejection restores it. Simulated by default. |
| `labels?` | `Partial<CrmPipelinePageLabels>` |  | Every visible string (English by default, DEFAULT_CRM_PIPELINE_PAGE_LABELS); CRM_PIPELINE_PAGE_LABELS_RO is the ready Romanian set. Nested keys (pipeline, dateRange, datePicker, datePresets, currencyInput, inlineEdit, select, contactCard, timeline, validation) translate the composed items. |
| `locale?` | `string` | `"en-US"` | Intl locale for money, numbers and dates. |
| `currency?` | `string` | `"USD"` | The pipeline currency; deals in another currency are counted apart, never converted. |
| `now?` | `Date \| number` |  | The reference time for stale hints, overdue dates and new stamps; defaults to the live clock. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | The accent of the primary actions, the pressed chips, the probability bars and the win rate ring. |
| `radius?` | `"sm" \| "md" \| "lg"` | `"md"` | The summary tiles, the empty state, the sheet and the dialogs (12px, 16px or 24px tiles). |
| `loading?` | `boolean` | `false` | The header, the summary and the columns as skeletons. |
| `animateIn?` | `boolean` | `true` | The header and the board rise in once on mount; off in thumbnails and under reduced motion. |
| `linkComponent?` | `(props: LinkComponentProps) => ReactNode` | `"<a>"` | The router link for the company and deal links; defaults to the LinkProvider's (a plain anchor). |
| `className?` | `string` |  | Merged onto the root. |
| `classNames?` | `Partial<Record<CrmPipelinePageSlot, string>>` |  | Classes for the parts: header, filters, board, sheet, newDialog, empty. |

## Parts

### DEFAULT_CRM_PIPELINE_PAGE_LABELS (constant)

Every English string of the pipeline page: toolbar, board, list, deal sheet, new deal dialog and announcements. Spread it and override what you translate.

### CRM_PIPELINE_PAGE_LABELS_EN (constant)

An alias of DEFAULT_CRM_PIPELINE_PAGE_LABELS, kept so older imports keep working.

### CRM_PIPELINE_PAGE_LABELS_RO (constant)

A ready Romanian set of every string. Pass it as labels for a Romanian CRM.

### CRM_PIPELINE_SAMPLE (constant)

The demo content in one object: pipelines, deals, owners, companies, lost reasons, sources, now and the current user. Replace it with data from your API.

### CRM_PIPELINES (constant)

The sample pipelines with their stages and win probabilities, for the pipeline switcher.

### CRM_PIPELINE_OWNERS (constant)

The sample sales team that owns the deals, with names and avatars.

### CRM_PIPELINE_COMPANIES (constant)

The sample client companies the deals belong to, offered in the new deal dialog.

### CRM_LOST_REASONS (constant)

The reasons offered when a deal is marked lost: price, a competitor, budget, no decision and other.

### CRM_DEAL_SOURCES (constant)

The lead sources offered for a deal: referral, website, event, cold call, existing client and partner.

### CRM_PIPELINE_NOW (constant)

The fixed "now" of the sample (September 24, 2026, 4:40 PM) that close dates are measured from.

### CRM_PIPELINE_DEAL_IDS (constant)

The four sample deal ids the demo offers for defaultOpenDeal.

## Examples

- **Deal sheet open on ERP integration**: defaultOpenDeal="d02": Construct Group, $103,400 in Negotiation, with its contact and activity.
- **List view**: defaultView="list": a table grouped by stage with totals; cards on phones.
- **Only my deals**: The Mine chip pressed, closed columns hidden: Needs analysis and Negotiation are empty.
- **The Partnerships pipeline**: defaultPipeline="partnerships": three stages, four deals, no lost column.
- **Brand tone, large radius**: tone="brand" on the primary actions, the chips and the probability bars; radius="lg".
- **Loading**: loading: the header, the summary and the columns as skeletons.
- **Romanian copy, RON**: CRM_PIPELINE_PAGE_LABELS_RO, locale="ro-RO", currency="RON": the ready Romanian set for the page and its parts.

## See also

- [Deal Pipeline](https://wingo-ui.com/components/deal-pipeline) ([Markdown](https://wingo-ui.com/components/deal-pipeline.md))
- [Kanban](https://wingo-ui.com/components/kanban) ([Markdown](https://wingo-ui.com/components/kanban.md))
- [Sheet](https://wingo-ui.com/components/sheet) ([Markdown](https://wingo-ui.com/components/sheet.md))
- [Admin Shell](https://wingo-ui.com/components/admin-shell) ([Markdown](https://wingo-ui.com/components/admin-shell.md))
- [Admin Dashboard Page](https://wingo-ui.com/components/admin-dashboard-page) ([Markdown](https://wingo-ui.com/components/admin-dashboard-page.md))

---

Canonical: https://wingo-ui.com/components/crm-pipeline-page
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
