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,
},
],
},
},
};Read-onlyLoading demo…