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, }, }; };
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()