# Morning Corrections

Canonical page: https://www.adaptabletools.com/docs/showcase-demos-morning-corrections

**Example: Showcase: Morning Corrections**

Demo project (Vanilla); the files without a marker are shown below:

```text
src/
  adaptableOptions.ts
  agGridModules.ts     [template, with charts]
  columnDefs.ts
  gridOptions.ts
  index.ts             [template]
  onAdaptableReady.ts
  rowData.ts
  styles.css           [template + shown below]
index.html             [template]
package.json           [template] + ag-charts-enterprise
tsconfig.json          [template]
```

Files marked `[template]` are the same in every demo; see the [demo template](https://www.adaptabletools.com/llms/demo-template.md#vanilla).

**`src/adaptableOptions.ts`**

```ts
import {
  ActionColumnContext,
  AdaptableButton,
  AdaptableOptions,
  CustomToolbarFormContext,
} from '@adaptabletools/adaptable';
import {MorningQueueItem} from './rowData';

function applyMorningToolbarFilter(context: CustomToolbarFormContext) {
  const source = context.formData.source as string | undefined;
  const openOnly = Boolean(context.formData.openOnly);

  const expressions: string[] = [];
  if (source && source !== 'All') {
    expressions.push(`[source] = "${source}"`);
  }
  if (openOnly) {
    expressions.push('QUERY("Morning Queue")');
  }

  context.adaptableApi.filterApi.gridFilterApi.setGridFilterExpression(
    expressions.length ? expressions.join(' AND ') : null
  );
}

export const adaptableOptions: AdaptableOptions<MorningQueueItem> = {
  primaryKey: 'id',
  adaptableId: 'Showcase: Morning Corrections',
  userName: 'Desk Lead',
  actionColumnOptions: {
    actionColumns: [
      {
        columnId: 'queue_status',
        friendlyName: 'Status',
        settings: {
          width: 140,
          suppressMenu: true,
          resizable: false,
        },
        controls: [
          {
            type: 'select',
            placeholder: 'Status',
            value: context => context.data?.status ?? '',
            options: [
              {label: 'Open', value: 'Open'},
              {label: 'In Progress', value: 'In Progress'},
              {label: 'Done', value: 'Done'},
            ],
            onChange: (value: string, context: ActionColumnContext<MorningQueueItem>) => {
              context.adaptableApi.gridApi.setCellValue({
                columnId: 'status',
                newValue: value,
                primaryKeyValue: context.primaryKeyValue,
                rowNode: context.rowNode,
              });
            },
          },
        ],
      },
    ],
  },
  chartingOptions: {
    chartContainers: [
      {
        name: 'Top Container',
        chartsDisplay: 'multiple',
        element: '#demoOutputAbove',
      },
    ],
  },
  notificationsOptions: {
    showSystemStatusMessageNotifications: true,
    position: 'TopRight',
  },
  dashboardOptions: {
    customToolbars: [
      {
        name: 'MorningToolbar',
        title: 'Morning',
        toolbarForm: {
          fields: [
            {
              name: 'source',
              label: 'Source',
              fieldType: 'select',
              defaultValue: 'All',
              options: [
                {label: 'All sources', value: 'All'},
                {label: 'Blotter', value: 'Blotter'},
                {label: 'Risk', value: 'Risk'},
                {label: 'Settlement', value: 'Settlement'},
              ],
              onValueChange: (_value, ctx) =>
                applyMorningToolbarFilter(ctx as CustomToolbarFormContext),
            },
            {
              name: 'openOnly',
              label: 'Open items only',
              fieldType: 'checkbox',
              defaultValue: true,
              onValueChange: (_value, ctx) =>
                applyMorningToolbarFilter(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(
                  'MorningToolbar'
                );
              },
            },
          ],
        },
      },
    ],
  },
  initialState: {
    Dashboard: {
      Tabs: [
        {
          Name: 'Morning',
          Toolbars: [
            'MorningToolbar',
            'Layout',
            'GridFilter',
            'SystemStatus',
            'Alert',
          ],
        },
        {
          Name: 'Charts',
          Toolbars: ['Charting', 'Layout'],
        },
      ],
      ModuleButtons: [
        'Layout',
        'Alert',
      ],
    },
    StatusBar: {
      StatusBars: [
        {
          Key: 'Center Panel',
          StatusBarPanels: ['SystemStatus', 'Layout'],
        },
      ],
    },
    Theme: {CurrentTheme: 'dark'},
    NamedQuery: {
      NamedQueries: [
        {
          Name: 'Morning Queue',
          Expression:
            '[status] = "Open" OR [status] = "In Progress"',
        },
        {
          Name: 'High Priority',
          Expression: '[priority] = "High"',
        },
      ],
    },
    Layout: {
      CurrentLayout: 'Morning Dashboard',
      Layouts: [
        {
          Name: 'Morning Dashboard',
          AutoSizeColumns: true,
          TableColumns: [
            'queueRef',
            'source',
            'symbol',
            'summary',
            'priority',
            'queue_status',
            'assignee',
            'detectedAt',
          ],
          OpenCharts: [
            {ChartName: 'queue-by-source', ContainerName: 'Top Container'},
            {ChartName: 'priority-mix', ContainerName: 'Top Container'},
          ],
        },
        {
          Name: 'Corrections Queue',
          AutoSizeColumns: true,
          TableColumns: [
            'queueRef',
            'source',
            'symbol',
            'summary',
            'priority',
            'queue_status',
            'assignee',
          ],
          GridFilter: {
            Expression: 'QUERY("Morning Queue")',
          },
        },
        {
          Name: 'High Priority',
          AutoSizeColumns: true,
          TableColumns: [
            'queueRef',
            'source',
            'symbol',
            'summary',
            'queue_status',
            'assignee',
            'detectedAt',
          ],
          GridFilter: {
            Expression: 'QUERY("High Priority")',
          },
        },
      ],
    },
    FormatColumn: {
      FormatColumns: [
        {
          Name: 'format-priority-high',
          Scope: {ColumnIds: ['priority']},
          Rule: '[priority] = "High"',
          Style: {
            BackColor: 'var(--ab-color-palette-1)',
            ForeColor: 'var(--ab-color-palette-2)',
            FontWeight: 'Bold',
          },
        },
        {
          Name: 'format-status-open',
          Scope: {ColumnIds: ['status']},
          Rule: '[status] = "Open"',
          Style: {ForeColor: 'var(--ab-color-palette-4)'},
        },
        {
          Name: 'format-status-progress',
          Scope: {ColumnIds: ['status']},
          Rule: '[status] = "In Progress"',
          Style: {ForeColor: 'var(--ab-color-palette-8)'},
        },
        {
          Name: 'format-detected',
          Scope: {ColumnIds: ['detectedAt']},
          DisplayFormat: {
            Formatter: 'DateFormatter',
            Options: {Pattern: 'dd MMM yyyy'},
          },
        },
      ],
    },
    Alert: {
      AlertDefinitions: [
        {
          Name: '09:00 Morning review',
          MessageType: 'Info',
          MessageHeader: 'Morning corrections',
          MessageText: 'Review overnight queue before market open',
          Schedule: {
            IsOneOff: false,
            CronExpression: '0 9 * * 1-5',
          },
          AlertProperties: {
            DisplayNotification: true,
            DisplaySystemStatusMessage: true,
          },
        },
      ],
    },
    Charting: {
      ChartDefinitions: [
        {
          Name: 'queue-by-source',
          Model: {
            modelType: 'range',
            chartId: 'morning-queue-source',
            chartType: 'pie',
            chartThemeName: 'ag-vivid-dark',
            aggFunc: 'count',
            chartOptions: {
              pie: {
                title: {text: 'Items by source'},
              },
            },
            cellRange: {
              rowStartIndex: 0,
              rowEndIndex: 35,
              columns: ['source', 'id'],
            },
            suppressChartRanges: false,
            unlinkChart: false,
          },
        },
        {
          Name: 'priority-mix',
          Model: {
            modelType: 'range',
            chartId: 'morning-queue-priority',
            chartType: 'bar',
            chartThemeName: 'ag-vivid-dark',
            aggFunc: 'count',
            chartOptions: {
              bar: {
                title: {text: 'Items by priority'},
              },
            },
            cellRange: {
              rowStartIndex: 0,
              rowEndIndex: 35,
              columns: ['priority', 'id'],
            },
            suppressChartRanges: false,
            unlinkChart: false,
          },
        },
      ],
    },
  },
};
```

**`src/rowData.ts`**

```ts
export type QueueSource = 'Blotter' | 'Risk' | 'Settlement';
export type QueueStatus = 'Open' | 'In Progress' | 'Done';
export type QueuePriority = 'High' | 'Medium' | 'Low';

export interface MorningQueueItem {
  id: number;
  queueRef: string;
  source: QueueSource;
  symbol: string;
  summary: string;
  priority: QueuePriority;
  status: QueueStatus;
  assignee: string;
  detectedAt: string;
}

const ASSIGNEES = ['Chen', 'Patel', 'Okafor', 'Smith'] as const;
const SOURCES: QueueSource[] = ['Blotter', 'Risk', 'Settlement'];
const PRIORITIES: QueuePriority[] = ['High', 'Medium', 'Low'];
const STATUSES: QueueStatus[] = ['Open', 'In Progress', 'Done'];

const SUMMARIES: Record<QueueSource, string[]> = {
  Blotter: [
    'Zero price on import',
    'Quantity mismatch vs confirms',
    'Duplicate trade id',
    'Side field blank',
    'Amend awaiting booking',
  ],
  Risk: [
    'Limit utilisation breach',
    'Concentration above desk threshold',
    'Stale VAR contribution',
    'Missing rating on new line',
    'Sector limit warning',
  ],
  Settlement: [
    'Fails to deliver flag',
    'SSI mismatch',
    'Unsettled cash break',
    'Corporate action pending',
    'CSDR buy-in risk',
  ],
};

function pseudoRandom(seed: number): number {
  const x = Math.sin(seed * 5151) * 10000;
  return x - Math.floor(x);
}

function toDateString(hoursAgo: number): string {
  const d = new Date();
  d.setHours(d.getHours() - hoursAgo);
  return d.toDateString();
}

const SYMBOLS = [
  'AAPL',
  'MSFT',
  'NVDA',
  'JPM',
  'XOM',
  'BP',
  'SAP',
  'BABA',
  'TSM',
  'HSBA',
];

export const rowData: MorningQueueItem[] = Array.from({length: 36}, (_, index) => {
  const id = index + 1;
  const source = SOURCES[Math.floor(pseudoRandom(id * 3) * SOURCES.length)];
  const status = STATUSES[Math.floor(pseudoRandom(id * 7) * STATUSES.length)];
  const priority = PRIORITIES[Math.floor(pseudoRandom(id * 11) * PRIORITIES.length)];
  const summaries = SUMMARIES[source];

  return {
    id,
    queueRef: `MQ-${2400 + id}`,
    source,
    symbol: SYMBOLS[Math.floor(pseudoRandom(id * 5) * SYMBOLS.length)],
    summary: summaries[Math.floor(pseudoRandom(id * 13) * summaries.length)],
    priority,
    status,
    assignee: ASSIGNEES[Math.floor(pseudoRandom(id * 17) * ASSIGNEES.length)],
    detectedAt: toDateString(Math.floor(pseudoRandom(id * 19) * 14)),
  };
});

rowData[2] = {
  ...rowData[2],
  source: 'Blotter',
  status: 'Open',
  priority: 'High',
  summary: 'Zero price on import',
  symbol: 'NVDA',
};
rowData[7] = {
  ...rowData[7],
  source: 'Risk',
  status: 'Open',
  priority: 'High',
  summary: 'Limit utilisation breach',
  symbol: 'JPM',
};
rowData[14] = {
  ...rowData[14],
  source: 'Settlement',
  status: 'In Progress',
  priority: 'High',
  summary: 'Fails to deliver flag',
  symbol: 'BP',
};
rowData[21] = {
  ...rowData[21],
  source: 'Blotter',
  status: 'Open',
  priority: 'Medium',
  summary: 'Amend awaiting booking',
};
rowData[28] = {
  ...rowData[28],
  source: 'Risk',
  status: 'Open',
  priority: 'Medium',
  summary: 'Concentration above desk threshold',
};
```

**`src/columnDefs.ts`**

```ts
import {ColDef} from 'ag-grid-enterprise';

export const columnDefs: ColDef[] = [
  {field: 'queueRef', headerName: 'Ref', cellDataType: 'text', width: 100},
  {field: 'source', cellDataType: 'text', enableRowGroup: true},
  {field: 'symbol', cellDataType: 'text', width: 90},
  {
    field: 'summary',
    cellDataType: 'text',
    flex: 1,
    minWidth: 200,
  },
  {field: 'priority', cellDataType: 'text', width: 100},
  {field: 'status', cellDataType: 'text', width: 110},
  {field: 'assignee', cellDataType: 'text'},
  {
    field: 'detectedAt',
    headerName: 'Detected',
    cellDataType: 'date',
  },
  {field: 'id', hide: true, editable: false},
];
```

**`src/gridOptions.ts`**

```ts
import {GridOptions, themeBalham} from 'ag-grid-enterprise';

import {columnDefs} from './columnDefs';
import {MorningQueueItem, rowData as morningQueueRowData} from './rowData';

export const gridOptions: GridOptions<MorningQueueItem> = {
  theme: themeBalham,
  defaultColDef: {
    resizable: true,
    sortable: true,
    editable: false,
    filter: true,
    floatingFilter: true,
  },
  columnDefs: columnDefs,
  rowData: morningQueueRowData,
  enableCharts: true,
  chartThemes: ['ag-vivid', 'ag-vivid-dark'],
  sideBar: true,
  suppressMenuHide: true,
  columnMenu: 'new',
  cellSelection: true,
  statusBar: {
    statusPanels: [
      {statusPanel: 'agTotalRowCountComponent', align: 'left'},
      {statusPanel: 'agFilteredRowCountComponent'},
      {
        key: 'Center Panel',
        statusPanel: 'AdaptableStatusPanel',
        align: 'center',
      },
    ],
  },
};
```

**`src/onAdaptableReady.ts`**

```ts
import {AdaptableReadyInfo} from '@adaptabletools/adaptable';

export const onAdaptableReady = ({adaptableApi}: AdaptableReadyInfo) => {
  adaptableApi.systemStatusApi.setSuccessSystemStatus(
    'Pricing feeds connected',
    'All primary venues reporting'
  );
  adaptableApi.systemStatusApi.setInfoSystemStatus(
    'Overnight batch complete',
    'Morning queue loaded — review before 09:00'
  );
  adaptableApi.systemStatusApi.setWarningSystemStatus(
    '2 stale limit checks',
    'Risk recalc pending on EM desk'
  );
};
```

**`src/styles.css`**

```css
#demoOutputAbove {
  display: flex;
  flex: none;
}
```
