demo D263
Support Desk Board: One Ticket Stream, Many Queues in ESM
One ticket stream, a live lane per queue and an open-tickets chart, with no backend
createDataRouter · MockWebSocket
This is a ready-made service desk: one ticket stream drives a live lane per queue and an open-tickets chart, with no backend. It shows a support view built from a single feed, ready to connect to a real ticketing source.
This is the ES module version. The grid is imported straight from a CDN as a module, the same configuration as the vanilla tab without a global script tag, so it drops into any bundler or a bare module script.
The configuration
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/lattice-grid.min.css">
<div id="billing" style="height: 320px"></div>
<div id="technical" style="height: 320px"></div>
<div id="onboarding" style="height: 320px"></div>
<div id="chart" style="height: 200px"></div>
<script type="module">
import { createDataRouter } from '@toclocoinc/lattice-grid/modules/data-router';
import { createChart } from '@toclocoinc/lattice-grid/modules/charts';
import { MockWebSocket } from '@toclocoinc/lattice-grid/modules/mock-socket';
const cols = [
{ field: 'id', title: 'Ticket', layout: { width: 92 } },
{ field: 'subject', title: 'Subject', layout: { flex: 1, min: 120 } },
{ field: 'priority', title: 'Priority', filter: { type: 'set' } },
{ field: 'agent', title: 'Agent', filter: { type: 'set' } },
{ field: 'waitMins', title: 'Wait', type: 'number', total: 'max' },
];
const lane = (id) => createGrid(document.getElementById(id), {
rowKey: 'id', selection: 'single', highlightOnChange: { duration: 400 }, columns: cols,
});
const billing = lane('billing'), technical = lane('technical'), onboarding = lane('onboarding');
const metrics = createHeadlessGrid({
rowKey: 't', columns: [{ field: 't', type: 'number' }, { field: 'open', type: 'number' }],
});
// Partition on the queue: a resolved ticket leaves its lane as a delete, a new
// one drops in as an upsert, with no refresh.
const router = createDataRouter({ key: 'queue', rowKey: 'id' });
router.attach(billing, 'billing');
router.attach(technical, 'technical');
router.attach(onboarding, 'onboarding');
router.attach(metrics, 'metric', { rowKey: 't' });
createChart({ grid: metrics, container: document.getElementById('chart'), type: 'area', x: 't', y: 'open', title: 'Open tickets across the desk' });
// The vendored mock-socket module ships opsFeed and priceFeed only, so a
// desk feed is written here, to the same contract MockWebSocket expects.
function* deskFeed() {
const queues = ['billing', 'technical', 'onboarding'];
const agents = ['Ana', 'Bo', 'Cy', 'Dee'];
let nextId = 1;
const ticket = (queue) => ({
id: 'K-' + String(1000 + nextId++), queue, subject: 'Ticket ' + nextId,
priority: ['low', 'normal', 'high'][Math.floor(Math.random() * 3)],
agent: agents[Math.floor(Math.random() * agents.length)],
waitMins: Math.round(Math.random() * 90),
});
const rows = [];
for (const q of queues) for (let i = 0; i < 6; i++) rows.push(ticket(q));
yield { kind: 'snapshot', rows };
while (true) {
// A resolved ticket leaves its lane as a delete, a new one drops in as
// an upsert, with no refresh.
if (Math.random() < 0.3 && rows.length) {
const gone = rows.splice(Math.floor(Math.random() * rows.length), 1)[0];
yield { kind: 'delta', changes: [{ op: 'delete', row: { id: gone.id } }] };
} else {
const row = ticket(queues[Math.floor(Math.random() * queues.length)]);
rows.push(row);
yield { kind: 'delta', changes: [{ op: 'upsert', row }] };
}
}
}
const socket = new MockWebSocket({ feed: deskFeed(), rate: 1100, jitter: 300 });
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.kind === 'snapshot') router.load(message.rows);
else router.apply(message.changes);
};
</script>