demo D223
A sales dashboard in React
Top reps by grouping, most-sold products by unnesting the order lines, and five tiles, one ignoring the filter
groupBy · unnest · scope: 'all' · createStat
This is a sales dashboard built from one filtered source: top reps by grouping, most-sold products by unnesting the order lines, and five figure tiles, one of which deliberately ignores the filter. It shows how a single feed can drive a whole screen of related views that all stay in step.
This is the React version. The grid mounts through the createLatticeGrid adapter, which takes its configuration as ordinary props and hands back the live grid through a ref. The grid below is the same one every other tab runs.
The configuration
<link rel="stylesheet" href="https://cdn.latticegrid.dev/lattice-grid@1.108.0/lattice-grid.min.css">
<div id="app"></div>
<script type="module">
import React from 'https://esm.sh/react@18';
import { createRoot } from 'https://esm.sh/react-dom@18/client';
import { createGrid, createStat } from 'https://cdn.latticegrid.dev/lattice-grid@1.108.0/lattice-grid.esm.min.js';
import createLatticeGrid from 'https://cdn.latticegrid.dev/lattice-grid@1.108.0/modules/react.esm.min.js';
const LatticeGrid = createLatticeGrid({ React, createGrid });
const USD = { style: 'currency', currency: 'USD', decimals: 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' },
];
const rows = [/* each sale carries a lines: [{ sku, product, qty, value }] array */];
function App() {
const bookRef = React.useRef(null);
const tilesRef = React.useRef(null);
const [reps, setReps] = React.useState(null);
const [skus, setSkus] = React.useState(null);
// Both roll-ups derive from the live book and follow its filter, so a region
// narrows the ranking and the SKU league at once. The book is reached through
// its ref once mounted.
React.useEffect(() => {
const book = bookRef.current && bookRef.current.grid;
if (!book) return;
// Group and rank: revenue per rep, top five.
setReps({ 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.
setSkus({ 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 = tilesRef.current.appendChild(document.createElement('div'));
createStat({ grid: book, container: tile, title: 'Company total', of: 'amount', fn: 'sum', scope: 'all' });
}, []);
return (
<>
<LatticeGrid
ref={bookRef}
rowKey="id"
toolPanel={{ side: 'left', panels: ['filters', 'columns'] }}
columns={bookColumns}
rows={rows}
style={{ height: '320px' }}
/>
<div ref={tilesRef} style={{ margin: '16px 0' }} />
{reps && <LatticeGrid autoHeight source={reps} columns={repColumns} style={{ marginTop: '16px' }} />}
{skus && <LatticeGrid autoHeight source={skus} columns={skuColumns} style={{ marginTop: '16px' }} />}
</>
);
}
createRoot(document.getElementById('app')).render(<App />);
</script>