demo D263
Starter kit: service desk
One ticket stream, a live lane per queue and an open-tickets chart, with no backend
createDataRouter · MockWebSocket
Loading a live grid…
The configuration
'starter-service-desk': () => ({
rows: [],
config: {},
foot: [
'one ticket stream, a live lane per queue',
'a resolved ticket leaves its lane; a new one drops in with no refresh',
'the mock socket runs it with no backend; one line points it at a real feed',
],
mount: (el: HTMLElement, LG: any) => {
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 strip = document.createElement('div');
strip.style.cssText = 'display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px';
el.append(strip);
const billingEl = panel(strip, 'Billing', 320);
const technicalEl = panel(strip, 'Technical', 320);
const onboardingEl = panel(strip, 'Onboarding', 320);
const chartEl = chartHost(el);
const mk = (host: HTMLElement) => LG.createGrid(host, {
rowKey: 'id', theme: 'light', selection: 'single', highlightOnChange: { duration: 400 }, columns: cols,
});
const billing = mk(billingEl), technical = mk(technicalEl), onboarding = mk(onboardingEl);
const metrics = LG.createHeadlessGrid({
rowKey: 't', columns: [{ field: 't', type: 'number' }, { field: 'open', type: 'number' }],
});
let chart: any, socket: any;
Promise.all([loadDataRouter(), loadCharts(), loadMockSocket()])
.then(([dr, ch, ms]: any[]) => {
const rng = ms.rng;
function* deskFeed(seed: number) {
const rand = rng(seed);
const QUEUES = ['billing', 'technical', 'onboarding'];
const PRIORITIES = ['low', 'normal', 'high', 'urgent'];
const AGENTS = ['Ada', 'Boris', 'Chen', 'Dara', 'Ege', 'Faye'];
const SUBJECTS: Record<string, string[]> = {
billing: ['Refund request', 'Invoice query', 'Card declined', 'Plan change', 'Duplicate charge'],
technical: ['Login fails', 'Export timeout', 'API 500', 'Slow dashboard', 'Sync stuck'],
onboarding: ['SSO setup', 'Import data', 'Invite team', 'Configure roles', 'First report'],
};
const pick = (list: string[]) => list[Math.floor(rand() * list.length)];
let seq = 0, t = 0;
const open: Record<string, string[]> = { billing: [], technical: [], onboarding: [] };
const ticket = (queue: string) => {
const id = 'T-' + (1000 + seq++);
open[queue].push(id);
return { id, queue, subject: pick(SUBJECTS[queue]), priority: pick(PRIORITIES), status: 'open', agent: pick(AGENTS), waitMins: Math.round(rand() * 90) };
};
const totalOpen = () => QUEUES.reduce((n, q) => n + open[q].length, 0);
const metric = () => ({ id: 'M-' + t, queue: 'metric', t, open: totalOpen() });
const rows: any[] = [];
QUEUES.forEach((q) => { for (let i = 0; i < 8; i++) rows.push(ticket(q)); });
rows.push(metric());
yield { kind: 'snapshot', rows };
for (;;) {
const changes: any[] = [];
const n = 1 + Math.floor(rand() * 3);
for (let i = 0; i < n; i++) changes.push({ op: 'upsert', row: ticket(pick(QUEUES)) });
const q = pick(QUEUES);
if (rand() < 0.6 && open[q].length) {
const id = open[q].shift()!;
changes.push({ op: 'upsert', row: { id, queue: q, subject: 'Resolved', priority: 'normal', status: 'resolved', agent: pick(AGENTS), waitMins: 0 } });
}
t++;
changes.push({ op: 'upsert', row: metric() });
yield { kind: 'delta', changes };
}
}
const router = dr.createDataRouter({ key: 'queue', rowKey: 'id' });
router.attach(billing, 'billing');
router.attach(technical, 'technical');
router.attach(onboarding, 'onboarding');
router.attach(metrics, 'metric', { rowKey: 't' });
chart = ch.createChart({ grid: metrics, container: chartEl, type: 'area', x: 't', y: 'open', title: 'Open tickets across the desk' });
socket = new ms.MockWebSocket({ feed: deskFeed(5), rate: 1100, jitter: 300 });
pipe(socket, router);
})
.catch((err) => console.error('[starter-service-desk]', err));
return () => {
socket?.close?.();
chart?.destroy?.();
billing?.destroy?.(); technical?.destroy?.(); onboarding?.destroy?.(); metrics?.destroy?.();
};
},
})