Lattice Grid Buy a licence

demo D223

A sales dashboard

Top reps by grouping, most-sold products by unnesting the order lines, and five tiles, one ignoring the filter

groupBy · unnest · scope: 'all' · createStat

Building…
Loading a live grid…

The configuration

import * as ng from '@angular/core';
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';
import { createGrid, createStat } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import createLatticeGrid from '@toclocoinc/lattice-grid/modules/angular';

const { LatticeGridComponent } = createLatticeGrid({ ng, createGrid });

const USD = { style: 'currency', currency: 'USD', maximumFractionDigits: 0 };
const bookColumns = [
  { field: 'rep', title: 'Sales rep' },
  { field: 'region', title: 'Region', filter: { type: 'set' } },
  { field: 'amount', title: 'Deal value', type: 'number', format: USD, total: 'sum' },
];
const repColumns = [
  { field: 'rep', title: 'Rep' },
  { field: 'revenue', title: 'Revenue', type: 'number', format: USD },
];
const skuColumns = [
  { field: 'sku', title: 'SKU' },
  { field: 'product', title: 'Product' },
  { field: 'units', title: 'Units', type: 'number' },
];

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, LatticeGridComponent],
  template:
    '<lattice-grid #book [config]="bookGrid" style="display:block;height:320px"></lattice-grid>' +
    '<div #tiles style="margin:16px 0"></div>' +
    '<lattice-grid *ngIf="reps" [config]="reps" style="display:block;margin-top:16px"></lattice-grid>' +
    '<lattice-grid *ngIf="skus" [config]="skus" style="display:block;margin-top:16px"></lattice-grid>',
})
export class AppComponent implements AfterViewInit {
  @ViewChild('book') bookRef!: LatticeGridComponent;
  @ViewChild('tiles') tilesEl!: ng.ElementRef<HTMLElement>;
  reps: any = null;
  skus: any = null;

  bookGrid = { rowKey: 'id', toolPanel: { side: 'left', panels: ['filters', 'columns'] }, columns: bookColumns, rows: [/* each sale carries a lines: [{ sku, product, qty, value }] array */] };

  // Both roll-ups derive from the live book and follow its filter, so a region
  // narrows the ranking and the SKU league at once.
  ngAfterViewInit() {
    const book = this.bookRef.grid;
    // Group and rank: revenue per rep, top five.
    this.reps = { autoHeight: true, columns: repColumns, source: { mode: 'derived', from: book, follow: 'filtered', refresh: 'live', groupBy: 'rep',
      select: { revenue: { of: 'amount', fn: 'sum' }, deals: { fn: 'count' }, biggest: { of: 'amount', fn: 'max' } },
      sort: [{ col: 'revenue', dir: 'desc' }], limit: 5 } };
    // Unnest: the SKUs live in a lines array, so one sale becomes several rows
    // before grouping. groupBy reads the dotted path into the unnested element.
    this.skus = { autoHeight: true, columns: skuColumns, source: { mode: 'derived', from: book, follow: 'filtered', refresh: 'live', unnest: 'lines', groupBy: 'lines.sku',
      select: { product: { of: 'lines.product', fn: 'first' }, units: { of: 'lines.qty', fn: 'sum' } },
      sort: [{ col: 'units', dir: 'desc' }], limit: 5 } };
    // A tile over the source. scope: 'all' holds the company total while the grid
    // and every roll-up follow the filter.
    const tile = this.tilesEl.nativeElement.appendChild(document.createElement('div'));
    createStat({ grid: book, container: tile, title: 'Company total', of: 'amount', fn: 'sum', scope: 'all' });
  }
}

bootstrapApplication(AppComponent);