Business scenario
General business workflow showing B2B sales pipeline — stage filters, deal actions, notes, and win-probability formatting
Your Role
You are an account executive (or sales manager) running the weekly pipeline review.
import { ActionColumnContext, AdaptableButton, AdaptableOptions, CustomToolbarFormContext, } from '@adaptabletools/adaptable'; import {DealStage, PipelineDeal} from './rowData'; const FORWARD_STAGE: Record<DealStage, DealStage | null> = { Lead: 'Qualified', Qualified: 'Proposal', Proposal: 'Negotiation', Negotiation: 'Won', Won: null, Lost: null, }; const BACK_STAGE: Record<DealStage, DealStage | null> = { Lead: null, Qualified: 'Lead', Proposal: 'Qualified', Negotiation: 'Proposal', Won: 'Negotiation', Lost: 'Negotiation', }; function applyPipelineToolbarFilter(context: CustomToolbarFormContext) { const owner = context.formData.owner as string | undefined; const stage = context.formData.stage as string | undefined; const openOnly = Boolean(context.formData.openOnly); const expressions: string[] = []; if (owner && owner !== 'All') { expressions.push(`[owner] = "${owner}"`); } if (stage && stage !== 'All') { expressions.push(`[stage] = "${stage}"`); } if (openOnly) { expressions.push('QUERY("Open Pipeline")'); } context.adaptableApi.filterApi.gridFilterApi.setGridFilterExpression( expressions.length ? expressions.join(' AND ') : null ); } function moveStageForward(context: ActionColumnContext) { const row = context.rowNode?.data as PipelineDeal | undefined; if (!row) { return; } const nextStage = FORWARD_STAGE[row.stage]; if (!nextStage) { return; } context.adaptableApi.gridApi.setCellValue({ columnId: 'stage', newValue: nextStage, primaryKeyValue: context.primaryKeyValue, rowNode: context.rowNode, }); } function moveStageBack(context: ActionColumnContext) { const row = context.rowNode?.data as PipelineDeal | undefined; if (!row) { return; } const previousStage = BACK_STAGE[row.stage]; if (!previousStage) { return; } context.adaptableApi.gridApi.setCellValue({ columnId: 'stage', newValue: previousStage, primaryKeyValue: context.primaryKeyValue, rowNode: context.rowNode, }); } export const adaptableOptions: AdaptableOptions<PipelineDeal> = { primaryKey: 'id', adaptableId: 'Showcase: Sales Pipeline', userName: 'Alex Morgan', dashboardOptions: { customToolbars: [ { name: 'PipelineToolbar', title: 'Pipeline', toolbarForm: { fields: [ { name: 'owner', label: 'Owner', fieldType: 'select', defaultValue: 'All', options: [ {label: 'All owners', value: 'All'}, {label: 'Alex Morgan', value: 'Alex Morgan'}, {label: 'Jordan Lee', value: 'Jordan Lee'}, {label: 'Sam Patel', value: 'Sam Patel'}, {label: 'Riley Chen', value: 'Riley Chen'}, ], onValueChange: (_value, ctx) => applyPipelineToolbarFilter(ctx as CustomToolbarFormContext), }, { name: 'stage', label: 'Stage', fieldType: 'select', defaultValue: 'All', options: [ {label: 'All stages', value: 'All'}, {label: 'Lead', value: 'Lead'}, {label: 'Qualified', value: 'Qualified'}, {label: 'Proposal', value: 'Proposal'}, {label: 'Negotiation', value: 'Negotiation'}, {label: 'Won', value: 'Won'}, {label: 'Lost', value: 'Lost'}, ], onValueChange: (_value, ctx) => applyPipelineToolbarFilter(ctx as CustomToolbarFormContext), }, { name: 'openOnly', label: 'Open deals only', fieldType: 'checkbox', defaultValue: false, onValueChange: (_value, ctx) => applyPipelineToolbarFilter(ctx as CustomToolbarFormContext), }, ], buttons: [ { label: 'Clear', buttonStyle: {tone: 'neutral', variant: 'text'}, onClick: ( _button: AdaptableButton<CustomToolbarFormContext>, context: CustomToolbarFormContext ) => { context.adaptableApi.filterApi.gridFilterApi.clearGridFilter(); context.adaptableApi.dashboardApi.resetCustomToolbarFormData( 'PipelineToolbar' ); }, }, ], }, }, ], }, actionColumnOptions: { actionColumns: [ { columnId: 'change_stage', friendlyName: 'Change', actionColumnSettings: { autoWidth: true, minWidth: 120, }, actionColumnButton: [ { label: 'Back', buttonStyle: {variant: 'outlined', tone: 'neutral'}, icon: { style: {height: 20, width: 20}, name: 'arrow-left', }, disabled: (_button, context: ActionColumnContext) => { const stage = (context.data as PipelineDeal | undefined)?.stage; return stage == null || BACK_STAGE[stage] == null; }, onClick: (_button: unknown, context: ActionColumnContext) => { moveStageBack(context); }, }, { label: 'Forward', iconPosition: 'end', buttonStyle: {variant: 'outlined', tone: 'accent'}, icon: { style: {height: 20, width: 20}, name: 'arrow-right', }, disabled: (_button, context: ActionColumnContext) => { const stage = (context.data as PipelineDeal | undefined)?.stage; return stage == null || FORWARD_STAGE[stage] == null; }, onClick: (_button: unknown, context: ActionColumnContext) => { moveStageForward(context); }, }, ], }, ], }, initialState: { Dashboard: { Tabs: [ { Name: 'Pipeline', Toolbars: [ 'PipelineToolbar', 'Layout', 'GridFilter', 'QuickSearch', 'Alert', ], }, ], ModuleButtons: [ 'Layout', 'StyledColumn', 'Alert', 'Note', 'SettingsPanel', ], }, StatusBar: { StatusBars: [ { Key: 'Center Panel', StatusBarPanels: ['Layout', 'Alert'], }, ], }, Theme: {CurrentTheme: 'dark'}, NamedQuery: { NamedQueries: [ { Name: 'Open Pipeline', BooleanExpression: '[stage] != "Won" AND [stage] != "Lost"', }, { Name: 'Closing Soon', BooleanExpression: '([stage] = "Proposal" OR [stage] = "Negotiation") AND [winProbability] >= 0.6', }, { Name: 'Stale Deals', BooleanExpression: '[lastActivityDays] > 30 AND [stage] != "Won" AND [stage] != "Lost"', }, { Name: 'My Deals', BooleanExpression: '[owner] = "Alex Morgan"', }, ], }, Layout: { CurrentLayout: 'Pipeline', Layouts: [ { Name: 'Pipeline', AutoSizeColumns: true, TableColumns: [ 'account', 'contact', 'owner', 'stage', 'change_stage', 'dealValue', 'winProbability', 'priority', 'lastActivityDays', 'closeDate', ], ColumnSizing: { change_stage: {Width: 158}, dealValue: {Width: 100}, winProbability: {Width: 76}, }, }, { Name: 'My Deals', AutoSizeColumns: true, TableColumns: [ 'account', 'contact', 'stage', 'change_stage', 'dealValue', 'winProbability', 'priority', 'lastActivityDays', 'closeDate', ], GridFilter: { Expression: 'QUERY("My Deals")', }, }, { Name: 'Closing Soon', AutoSizeColumns: true, TableColumns: [ 'account', 'owner', 'stage', 'change_stage', 'dealValue', 'winProbability', 'closeDate', ], GridFilter: { Expression: 'QUERY("Closing Soon")', }, }, { Name: 'Stale', AutoSizeColumns: true, TableColumns: [ 'account', 'owner', 'stage', 'change_stage', 'lastActivityDays', 'dealValue', 'winProbability', ], GridFilter: { Expression: 'QUERY("Stale Deals")', }, }, { Name: 'By Stage', AutoSizeColumns: true, SuppressAggFuncInHeader: true, RowGroupedColumns: ['stage'], RowGroupValues: { RowGroupDefaultBehavior: 'expanded', }, ColumnPinning: {'ag-Grid-AutoColumn': 'left'}, TableAggregationColumns: [ {ColumnId: 'dealValue', AggFunc: 'sum'}, {ColumnId: 'winProbability', AggFunc: 'avg'}, {ColumnId: 'id', AggFunc: 'count'}, ], TableColumns: [ 'ag-Grid-AutoColumn', 'account', 'contact', 'owner', 'dealValue', 'winProbability', 'priority', 'lastActivityDays', 'closeDate', 'change_stage', ], }, ], }, StyledColumn: { StyledColumns: [ { Name: 'stage Badge', ColumnId: 'stage', BadgeStyle: { Badges: [ { Predicate: {PredicateId: 'Is', Inputs: ['Lead']}, PillStyle: { BackColor: 'var(--ab-color-palette-9)', ForeColor: 'var(--ab-color-palette-10)', }, }, { Predicate: {PredicateId: 'Is', Inputs: ['Qualified']}, PillStyle: { BackColor: 'var(--ab-color-palette-7)', ForeColor: 'var(--ab-color-palette-8)', }, }, { Predicate: {PredicateId: 'Is', Inputs: ['Proposal']}, PillStyle: { BackColor: 'var(--ab-color-palette-3)', ForeColor: 'var(--ab-color-palette-4)', }, }, { Predicate: {PredicateId: 'Is', Inputs: ['Negotiation']}, PillStyle: { BackColor: 'var(--ab-color-palette-11)', ForeColor: 'var(--ab-color-palette-12)', }, }, { Predicate: {PredicateId: 'Is', Inputs: ['Won']}, PillStyle: { BackColor: 'var(--ab-color-palette-5)', ForeColor: 'var(--ab-color-palette-6)', }, }, { Predicate: {PredicateId: 'Is', Inputs: ['Lost']}, PillStyle: { BackColor: 'var(--ab-color-palette-1)', ForeColor: 'var(--ab-color-palette-2)', }, }, ], }, }, { Name: 'priority Badge', ColumnId: 'priority', BadgeStyle: { Badges: [ { Predicate: {PredicateId: 'Is', Inputs: ['High']}, PillStyle: { BackColor: 'var(--ab-color-palette-1)', ForeColor: 'var(--ab-color-palette-2)', FontWeight: 'Bold', }, }, { Predicate: {PredicateId: 'Is', Inputs: ['Medium']}, PillStyle: { BackColor: 'var(--ab-color-palette-3)', ForeColor: 'var(--ab-color-palette-4)', }, }, { Predicate: {PredicateId: 'Is', Inputs: ['Low']}, PillStyle: { BackColor: 'var(--ab-color-palette-9)', ForeColor: 'var(--ab-color-palette-10)', }, }, ], }, }, ], }, FormatColumn: { FormatColumns: [ { Name: 'format-stale', Scope: {ColumnIds: ['lastActivityDays']}, Rule: {BooleanExpression: '[lastActivityDays] > 30'}, Style: { ForeColor: 'var(--ab-color-destructive)', FontWeight: 'Bold', }, }, { Name: 'format-deal-value', Scope: {ColumnIds: ['dealValue']}, DisplayFormat: { Formatter: 'NumberFormatter', Options: {Prefix: '$', FractionDigits: 0}, }, }, { Name: 'format-win-probability', Scope: {ColumnIds: ['winProbability']}, DisplayFormat: 'Percentage', }, { Name: 'format-close-date', Scope: {ColumnIds: ['closeDate']}, DisplayFormat: { Formatter: 'DateFormatter', Options: {Pattern: 'dd MMM yyyy'}, }, }, ], }, Alert: { AlertDefinitions: [ { Name: 'Closed deal locked', MessageType: 'Warning', MessageText: 'Won and lost deals cannot be edited.', Scope: {ColumnIds: ['dealValue', 'winProbability', 'stage']}, Rule: { BooleanExpression: '[stage] = "Won" OR [stage] = "Lost"', }, AlertProperties: { PreventEdit: true, DisplayNotification: true, }, }, { Name: 'Stale deal reminder', MessageType: 'Info', MessageHeader: 'Follow up', MessageText: 'No activity in over 30 days.', Scope: {All: true}, Rule: { BooleanExpression: 'QUERY("Stale Deals")', }, AlertProperties: { DisplayNotification: true, }, }, ], }, Note: { Notes: [ { PrimaryKeyValue: 1, ColumnId: 'account', Text: 'Security review scheduled — expect sign-off next week.', Timestamp: Date.now() - 86_400_000, }, { PrimaryKeyValue: 8, ColumnId: 'account', Text: 'Champion on leave until the 15th; follow up with procurement.', Timestamp: Date.now() - 172_800_000, }, { PrimaryKeyValue: 15, ColumnId: 'account', Text: 'Competitor undercut on year-one pricing — need exec sponsor.', Timestamp: Date.now() - 259_200_000, }, ], }, }, };