Business scenario
General business workflow showing vendor onboarding with Alice and Bob — comments, team sharing, import, and row forms
Your Role
You are onboarding and reviewing third-party vendors before they can be used for purchase orders.
import {
AdaptableButton,
AdaptableColumn,
AdaptableOptions,
CommentLoadContext,
CommentThread,
CustomToolbarButtonContext,
DashboardButtonContext,
InitialState,
RowFormColumnContext,
} from '@adaptabletools/adaptable-react-aggrid';
import {getSeedComments} from './commentsSeed';
import {PENDING_IMPORT_CSV} from './importSample';
import {Vendor} from './rowData';
import {
importSharedComplianceFormats,
shareComplianceFormats,
} from './teamSharingHelpers';
const COMMENTS_PERSISTENCE_KEY = 'showcase-supplier-registry-comments';
const TEAM_SHARING_STATE_KEY = 'SupplierRegistryShowcase_TeamSharing';
export class CommentsService {
channel = new BroadcastChannel('supplier-registry-comments');
subscribeToComments(callback: (comments: CommentThread[]) => void) {
this.channel.onmessage = event => {
callback(event.data);
};
}
setComments(comments: CommentThread[]) {
localStorage.setItem(COMMENTS_PERSISTENCE_KEY, JSON.stringify(comments));
this.channel.postMessage(comments);
}
getComments(): CommentThread[] {
const commentsString = localStorage.getItem(COMMENTS_PERSISTENCE_KEY);
if (commentsString) {
return JSON.parse(commentsString);
}
const seedComments = getSeedComments();
this.setComments(seedComments);
return seedComments;
}
}
const commentsService = new CommentsService();
/** Bump when dashboard layout in initialState changes (clears stale persisted PinnedToolbars, etc.). */
const DASHBOARD_STATE_REVISION = 4;
const commonInitialState: InitialState = {
Dashboard: {
Revision: DASHBOARD_STATE_REVISION,
PinnedToolbars: [],
Tabs: [
{
Name: 'Registry',
Toolbars: ['CurrentUser', 'VendorActions', 'Layout', 'Alert'],
},
],
ModuleButtons: [
'Layout',
'Comment',
'DataImport',
'TeamSharing',
'Alert',
'FormatColumn',
'StyledColumn',
'SettingsPanel',
],
},
StyledColumn: {
Revision: DASHBOARD_STATE_REVISION,
StyledColumns: [
{
ColumnId: 'certifications',
Name: 'certifications Badge',
BadgeStyle: {
Density: 'Compact',
Spacing: 4,
OverflowMode: 'Wrap',
Badges: [
{
Shape: 'Pill',
Predicate: {PredicateId: 'Is', Inputs: ['GDPR']},
PillStyle: {BackColor: '#4A90D9', ForeColor: 'White'},
},
{
Shape: 'Pill',
Predicate: {PredicateId: 'Is', Inputs: ['SOC 2']},
PillStyle: {BackColor: '#2E8B57', ForeColor: 'White'},
},
{
Shape: 'Pill',
Predicate: {PredicateId: 'Is', Inputs: ['ISO 27001']},
PillStyle: {BackColor: '#6B4C9A', ForeColor: 'White'},
},
{
Shape: 'Pill',
Predicate: {PredicateId: 'Is', Inputs: ['DPA']},
PillStyle: {BackColor: '#2A9D8F', ForeColor: 'White'},
},
{
Shape: 'Pill',
Predicate: {PredicateId: 'Is', Inputs: ['PCI DSS']},
PillStyle: {BackColor: '#E76F51', ForeColor: 'White'},
},
{
Shape: 'Pill',
Predicate: {PredicateId: 'Is', Inputs: ['ISO 45001']},
PillStyle: {BackColor: '#8B7355', ForeColor: 'White'},
},
{
Shape: 'Pill',
PillStyle: {
BackColor: 'var(--ab-color-palette-8)',
ForeColor: 'var(--ab-color-palette-2)',
},
},
],
},
},
],
},
StatusBar: {
StatusBars: [
{
Key: 'Center Panel',
StatusBarPanels: ['Layout', 'Alert', 'TeamSharing'],
},
],
},
NamedQuery: {
NamedQueries: [
{
Name: 'Pending Review',
BooleanExpression: '[status] = "Pending"',
},
{
Name: 'High Risk',
BooleanExpression: '[riskTier] = "High"',
},
],
},
Layout: {
CurrentLayout: 'Registry',
Layouts: [
{
Name: 'Registry',
AutoSizeColumns: true,
TableColumns: [
'vendorName',
'category',
'status',
'riskTier',
'certifications',
'annualSpend',
'insuranceExpiry',
'contractRenewal',
'taxIdOnFile',
'owner',
'lastReviewDate',
],
ColumnSizing: {
certifications: {Width: 280},
},
ColumnSorts: [{ColumnId: 'vendorName', SortOrder: 'Asc'}],
},
{
Name: 'Pending Review',
AutoSizeColumns: true,
TableColumns: [
'vendorName',
'category',
'status',
'riskTier',
'certifications',
'insuranceExpiry',
'taxIdOnFile',
'owner',
],
ColumnSizing: {
certifications: {Width: 280},
},
GridFilter: {
Expression: 'QUERY("Pending Review")',
},
},
{
Name: 'By Category',
AutoSizeColumns: true,
SuppressAggFuncInHeader: true,
RowGroupedColumns: ['category'],
RowGroupValues: {
RowGroupDefaultBehavior: 'expanded',
},
ColumnPinning: {'ag-Grid-AutoColumn': 'left'},
TableAggregationColumns: [
{ColumnId: 'annualSpend', AggFunc: 'sum'},
{ColumnId: 'id', AggFunc: 'count'},
],
TableColumns: [
'ag-Grid-AutoColumn',
'vendorName',
'status',
'riskTier',
'certifications',
'annualSpend',
'insuranceExpiry',
'owner',
],
ColumnSizing: {
certifications: {Width: 260},
},
},
],
},
Alert: {
AlertDefinitions: [
{
Name: 'New vendor queued',
MessageType: 'Info',
MessageHeader: 'New vendor',
MessageText:
'A vendor was added to the registry — review in Pending Review',
Scope: {All: true},
Rule: {
ObservableExpression: 'ROW_ADDED()',
},
AlertProperties: {
DisplayNotification: true,
HighlightRow: {
BackColor: 'rgba(91, 155, 213, 0.35)',
},
},
},
],
},
};
const aliceComplianceFormats: InitialState['FormatColumn'] = {
FormatColumns: [
{
Name: 'shared-format-status-pending',
Scope: {ColumnIds: ['status']},
Rule: {BooleanExpression: '[status] = "Pending"'},
Style: {
BackColor: 'var(--ab-color-palette-3)',
ForeColor: 'var(--ab-color-palette-4)',
FontWeight: 'Bold',
},
},
{
Name: 'shared-format-status-suspended',
Scope: {ColumnIds: ['status']},
Rule: {BooleanExpression: '[status] = "Suspended"'},
Style: {
BackColor: 'var(--ab-color-palette-1)',
ForeColor: 'var(--ab-color-palette-2)',
FontWeight: 'Bold',
},
},
{
Name: 'shared-format-high-risk',
Scope: {ColumnIds: ['riskTier']},
Rule: {BooleanExpression: '[riskTier] = "High"'},
Style: {
ForeColor: 'var(--ab-color-destructive)',
FontWeight: 'Bold',
},
},
{
Name: 'format-spend',
Scope: {ColumnIds: ['annualSpend']},
DisplayFormat: 'Dollar',
},
{
Name: 'format-dates',
Scope: {
ColumnIds: ['insuranceExpiry', 'contractRenewal', 'lastReviewDate'],
},
DisplayFormat: {
Formatter: 'DateFormatter',
Options: {Pattern: 'dd MMM yyyy'},
},
},
],
};
const bobLocalFormats: InitialState['FormatColumn'] = {
FormatColumns: [
{
Name: 'format-spend',
Scope: {ColumnIds: ['annualSpend']},
DisplayFormat: 'Dollar',
},
{
Name: 'format-dates',
Scope: {
ColumnIds: ['insuranceExpiry', 'contractRenewal', 'lastReviewDate'],
},
DisplayFormat: {
Formatter: 'DateFormatter',
Options: {Pattern: 'dd MMM yyyy'},
},
},
],
};
const aliceVendorToolbarButtons: AdaptableButton<CustomToolbarButtonContext>[] =
[
{
label: 'Share formats',
buttonStyle: {tone: 'success', variant: 'raised'},
onClick: async (_button, context) => {
await shareComplianceFormats(context.adaptableApi);
},
},
{
label: 'Import pending batch',
buttonStyle: {tone: 'neutral', variant: 'outlined'},
onClick: async (_button, context) => {
try {
await navigator.clipboard.writeText(PENDING_IMPORT_CSV);
} catch {
// clipboard may be blocked in some browsers
}
context.adaptableApi.dataImportApi.openImportWizard();
},
},
];
const bobVendorToolbarButtons: AdaptableButton<CustomToolbarButtonContext>[] = [
{
label: 'Import formats',
buttonStyle: {tone: 'success', variant: 'raised'},
onClick: async (_button, context) => {
await importSharedComplianceFormats(context.adaptableApi);
},
},
];
const SUPPLIER_REGISTRY_STATE_KEY_PREFIX = 'SupplierRegistryShowcase_';
function resetDemoState(context: DashboardButtonContext) {
localStorage.removeItem(COMMENTS_PERSISTENCE_KEY);
localStorage.removeItem(TEAM_SHARING_STATE_KEY);
['Alice', 'Bob'].forEach(user => {
localStorage.removeItem(`${SUPPLIER_REGISTRY_STATE_KEY_PREFIX}${user}`);
});
context.adaptableApi.stateApi.reloadInitialState();
}
export const getAdaptableOptionsForUser = (
userName: 'Alice' | 'Bob',
updateCurrentUser: (userName: 'Alice' | 'Bob') => void
): AdaptableOptions<Vendor> => {
const userSpecificInitialState: Partial<InitialState> =
userName === 'Alice'
? {
Theme: {CurrentTheme: 'light'},
FormatColumn: aliceComplianceFormats,
}
: {
Theme: {CurrentTheme: 'dark'},
FormatColumn: bobLocalFormats,
};
return {
primaryKey: 'id',
adaptableId: 'Showcase: Approved Supplier Registry',
adaptableStateKey: `${SUPPLIER_REGISTRY_STATE_KEY_PREFIX}${userName}`,
userName,
commentOptions: {
loadCommentThreads: async (commentLoadContext: CommentLoadContext) => {
commentsService.subscribeToComments(comments => {
commentLoadContext.adaptableApi.commentApi.setComments(comments);
});
return commentsService.getComments();
},
persistCommentThreads: async (commentThreads: CommentThread[]) => {
commentsService.setComments(commentThreads);
},
},
teamSharingOptions: {
enableTeamSharing: true,
loadSharedEntities: async () => {
const data = localStorage.getItem(TEAM_SHARING_STATE_KEY);
if (!data) {
return [];
}
try {
return JSON.parse(data);
} catch {
return [];
}
},
persistSharedEntities: entries => {
localStorage.setItem(TEAM_SHARING_STATE_KEY, JSON.stringify(entries));
return Promise.resolve();
},
updateNotification: 'AlertWithNotification',
suppressOverrideConfigWarning: true,
updateInterval: 0.25,
},
rowFormOptions: {
setPrimaryKeyValue: context => {
const rows = context.adaptableApi.gridApi.getGridData() as Vendor[];
const nextId = rows.reduce((max, row) => Math.max(max, row.id), 0) + 1;
return {
...context.rowData,
id: nextId,
status: context.rowData.status ?? 'Pending',
owner: userName,
lastReviewDate: new Date().toISOString().slice(0, 10),
taxIdOnFile: context.rowData.taxIdOnFile ?? false,
certifications: context.rowData.certifications ?? ['GDPR'],
};
},
includeColumnInRowForm: (rowFormColumnContext: RowFormColumnContext) => {
const column: AdaptableColumn = rowFormColumnContext.adaptableColumn;
return column.columnId !== 'id' && column.columnId !== 'certifications';
},
},
dashboardOptions: {
customToolbars: [
{
name: 'CurrentUser',
title: 'Team',
toolbarButtons: [
{
label: 'Alice',
buttonStyle: () => ({
tone: userName === 'Alice' ? 'success' : 'neutral',
variant: userName === 'Alice' ? 'raised' : 'outlined',
}),
onClick: () => updateCurrentUser('Alice'),
},
{
label: 'Bob',
buttonStyle: () => ({
tone: userName === 'Bob' ? 'success' : 'neutral',
variant: userName === 'Bob' ? 'raised' : 'outlined',
}),
onClick: () => updateCurrentUser('Bob'),
},
],
},
{
name: 'VendorActions',
title: 'Vendors',
toolbarButtons: [
{
label: 'Add vendor',
buttonStyle: {tone: 'info', variant: 'raised'},
onClick: (
_button: AdaptableButton<CustomToolbarButtonContext>,
context: CustomToolbarButtonContext
) => {
context.adaptableApi.rowFormApi.displayCreateRowForm();
},
},
...(userName === 'Alice'
? aliceVendorToolbarButtons
: bobVendorToolbarButtons),
],
},
],
customDashboardButtons: [
{
tooltip: 'Reset demo state',
icon: {name: 'refresh'},
buttonStyle: {tone: 'error'},
onClick: (_button, context) => resetDemoState(context),
},
],
},
initialState: {
...commonInitialState,
...userSpecificInitialState,
},
};
};Read-onlyLoading demo…
Deep Dive
How the Demo Works
The Share formats and Import formats toolbar buttons are thin wrappers around adaptableApi.teamSharingApi — the same API you would call from your own dashboard buttons or workflow code.
Logic lives in teamSharingHelpers.ts in the sandpack (hidden file).
The flow is:
Alice — share
teamSharingApi.loadSharedEntities()— refresh the local catalogue from yourloadSharedEntitieshandlerformatColumnApi.getFormatColumnByName(...)— resolve each compliance format column in Alice’s stateteamSharingApi.shareAdaptableEntity(formatColumn, 'FormatColumn', { type: 'Active', ... })— upload as an Active share so colleagues receive updates
Bob — import
teamSharingApi.loadSharedEntities()— fetch Alice’s uploadsteamSharingApi.getLoadedAdaptableSharedEntities()— list available sharesteamSharingApi.importSharedEntry(sharedEntity)— merge eachFormatColumnBob does not already have
In production, loadSharedEntities / persistSharedEntities would call your backend rather than the demo’s localStorage store — see Team Sharing and Custom Team Sharing.
Note
- Import pending batch uses a different API:
dataImportApi.openImportWizard()(with sample CSV copied via the clipboard) - Add vendor uses
rowFormApi.displayCreateRowForm()