|
2 | 2 |
|
3 | 3 | import type { Dashboard } from '@objectstack/spec/ui'; |
4 | 4 |
|
5 | | -const task = 'showcase_task'; |
6 | | -const project = 'showcase_project'; |
7 | 5 | const taskDs = 'showcase_task_metrics'; |
8 | 6 | const projectDs = 'showcase_project_metrics'; |
9 | 7 |
|
10 | 8 | /** |
11 | | - * Chart Gallery — one widget per chart family so the dashboard renderer can be |
12 | | - * exercised against every visualisation type. Covers the full `ChartTypeSchema` |
13 | | - * taxonomy (comparison, trend, distribution, relationship, composition, |
14 | | - * performance, tabular) — every type here renders; the taxonomy intentionally |
15 | | - * excludes families the renderer cannot draw (geo maps, OHLC, distributions). |
| 9 | + * Chart Gallery — one widget per chart family the dashboard renderer can draw |
| 10 | + * DISTINCTLY, so the showcase honestly reflects what the platform implements. |
16 | 11 | * |
17 | | - * ADR-0021 Phase 2: every widget is bound to a semantic dataset |
18 | | - * (`showcase_task_metrics` / `showcase_project_metrics`) and selects |
19 | | - * dimensions/measures BY NAME, side-by-side with the legacy inline query during |
20 | | - * the dual-form window. The reconciliation harness asserts both forms return |
21 | | - * identical numbers (scripts/analytics-reconcile). Date-bucketed trend widgets |
22 | | - * (`created_at` + `categoryGranularity`) stay inline-only for now — dataset |
23 | | - * timeDimension reconciliation is deferred (see the CRM trend widget). |
| 12 | + * Every widget binds a semantic dataset (`showcase_task_metrics` / |
| 13 | + * `showcase_project_metrics`, ADR-0021) and selects dimensions/measures BY NAME; |
| 14 | + * the analytics layer resolves dimension display labels (select option labels, |
| 15 | + * lookup names, month-bucketed dates) and carries each measure's label + format. |
| 16 | + * |
| 17 | + * Only types with a real, distinct renderer are shown. The chart families that |
| 18 | + * currently fall back to a near-relative (grouped/stacked/bi-polar bars → bar, |
| 19 | + * stacked-area → area, step-line/spline → line, pyramid → funnel, bubble → |
| 20 | + * scatter) and the single-value performance variants without a dial |
| 21 | + * (gauge/solid-gauge/bullet, which render the same as `metric`) are intentionally |
| 22 | + * NOT duplicated here — advertising a type that renders as something else is |
| 23 | + * worse than not offering it. (Follow-up: trim those from `ChartTypeSchema`.) |
24 | 24 | */ |
25 | 25 | export const ChartGalleryDashboard: Dashboard = { |
26 | 26 | name: 'showcase_chart_gallery', |
27 | 27 | label: 'Chart Gallery', |
28 | | - description: 'A representative widget for every chart family — visual coverage of the dashboard renderer.', |
| 28 | + description: 'One widget per chart family the renderer draws distinctly — honest visual coverage.', |
29 | 29 | columns: 12, |
30 | 30 | widgets: [ |
31 | | - // ── Performance / KPI ──────────────────────────────────────────────── |
32 | | - { id: 'kpi_total_tasks', type: 'metric', title: 'Total Tasks', dataset: taskDs, values: ['task_count'], layout: { x: 0, y: 0, w: 3, h: 2 } }, |
33 | | - { id: 'kpi_open_tasks', type: 'kpi', title: 'Open Tasks', filter: { done: false }, dataset: taskDs, values: ['task_count'], layout: { x: 3, y: 0, w: 3, h: 2 } }, |
34 | | - { id: 'gauge_progress', type: 'gauge', title: 'Avg Progress', dataset: taskDs, values: ['avg_progress'], layout: { x: 6, y: 0, w: 3, h: 2 } }, |
35 | | - { id: 'bullet_budget', type: 'bullet', title: 'Budget vs Spend', dataset: projectDs, values: ['spent_sum'], layout: { x: 9, y: 0, w: 3, h: 2 } }, |
| 31 | + // ── Performance / KPI (one measure value; demonstrates label + format) ── |
| 32 | + { id: 'kpi_total_tasks', type: 'metric', title: 'Total Tasks', dataset: taskDs, values: ['task_count'], layout: { x: 0, y: 0, w: 4, h: 2 } }, |
| 33 | + { id: 'kpi_avg_progress', type: 'metric', title: 'Avg Progress', dataset: taskDs, values: ['avg_progress'], layout: { x: 4, y: 0, w: 4, h: 2 } }, |
| 34 | + { id: 'kpi_total_spent', type: 'metric', title: 'Total Spent', dataset: projectDs, values: ['spent_sum'], layout: { x: 8, y: 0, w: 4, h: 2 } }, |
36 | 35 |
|
37 | | - // ── Comparison ─────────────────────────────────────────────────────── |
| 36 | + // ── Comparison ───────────────────────────────────────────────────────── |
38 | 37 | { id: 'bar_by_status', type: 'bar', title: 'Tasks by Status', dataset: taskDs, dimensions: ['status'], values: ['task_count'], layout: { x: 0, y: 2, w: 4, h: 4 } }, |
39 | 38 | { id: 'column_by_priority', type: 'column', title: 'Tasks by Priority', dataset: taskDs, dimensions: ['priority'], values: ['task_count'], layout: { x: 4, y: 2, w: 4, h: 4 } }, |
40 | 39 | { id: 'hbar_hours', type: 'horizontal-bar', title: 'Hours by Status', dataset: taskDs, dimensions: ['status'], values: ['est_hours'], layout: { x: 8, y: 2, w: 4, h: 4 } }, |
41 | | - { id: 'stacked_bar', type: 'stacked-bar', title: 'Status × Priority', dataset: taskDs, dimensions: ['status'], values: ['task_count'], layout: { x: 0, y: 6, w: 4, h: 4 } }, |
42 | | - { id: 'grouped_bar', type: 'grouped-bar', title: 'Grouped Status', dataset: taskDs, dimensions: ['status'], values: ['task_count'], layout: { x: 4, y: 6, w: 4, h: 4 } }, |
43 | | - |
44 | | - // ── Trend (date-bucketed via timeDimension granularity) ────────────── |
45 | | - { id: 'line_created', type: 'line', title: 'Tasks Created (monthly)', dataset: taskDs, dimensions: ['created_at'], values: ['task_count'], layout: { x: 8, y: 6, w: 4, h: 4 } }, |
46 | | - { id: 'area_created', type: 'area', title: 'Cumulative (area)', dataset: taskDs, dimensions: ['created_at'], values: ['task_count'], layout: { x: 0, y: 10, w: 4, h: 4 } }, |
47 | | - { id: 'stacked_area', type: 'stacked-area', title: 'Stacked Area', dataset: taskDs, dimensions: ['created_at'], values: ['task_count'], layout: { x: 4, y: 10, w: 4, h: 4 } }, |
48 | | - { id: 'spline_trend', type: 'spline', title: 'Smoothed Trend', dataset: taskDs, dimensions: ['created_at'], values: ['task_count'], layout: { x: 8, y: 10, w: 4, h: 4 } }, |
49 | | - |
50 | | - // ── Distribution ───────────────────────────────────────────────────── |
51 | | - { id: 'pie_status', type: 'pie', title: 'Status Split', dataset: taskDs, dimensions: ['status'], values: ['task_count'], layout: { x: 0, y: 14, w: 3, h: 4 } }, |
52 | | - { id: 'donut_priority', type: 'donut', title: 'Priority Split', dataset: taskDs, dimensions: ['priority'], values: ['task_count'], layout: { x: 3, y: 14, w: 3, h: 4 } }, |
53 | | - { id: 'funnel_pipeline', type: 'funnel', title: 'Task Funnel', dataset: taskDs, dimensions: ['status'], values: ['task_count'], layout: { x: 6, y: 14, w: 3, h: 4 } }, |
54 | | - { id: 'pyramid_priority', type: 'pyramid', title: 'Priority Pyramid', dataset: taskDs, dimensions: ['priority'], values: ['task_count'], layout: { x: 9, y: 14, w: 3, h: 4 } }, |
55 | 40 |
|
56 | | - // ── Relationship ───────────────────────────────────────────────────── |
57 | | - { id: 'scatter_estimate', type: 'scatter', title: 'Estimate vs Progress', dataset: taskDs, dimensions: ['progress'], values: ['avg_estimate'], layout: { x: 0, y: 18, w: 4, h: 4 } }, |
58 | | - { id: 'bubble_budget', type: 'bubble', title: 'Budget Bubble', dataset: projectDs, dimensions: ['account'], values: ['budget_sum'], layout: { x: 4, y: 18, w: 4, h: 4 } }, |
| 41 | + // ── Trend (month-bucketed via the dataset's created_at granularity) ────── |
| 42 | + { id: 'line_created', type: 'line', title: 'Tasks Created (monthly)', dataset: taskDs, dimensions: ['created_at'], values: ['task_count'], layout: { x: 0, y: 6, w: 6, h: 4 } }, |
| 43 | + { id: 'area_created', type: 'area', title: 'Tasks Created (area)', dataset: taskDs, dimensions: ['created_at'], values: ['task_count'], layout: { x: 6, y: 6, w: 6, h: 4 } }, |
59 | 44 |
|
60 | | - // ── Composition ────────────────────────────────────────────────────── |
61 | | - { id: 'treemap_hours', type: 'treemap', title: 'Hours Treemap', dataset: taskDs, dimensions: ['status'], values: ['est_hours'], layout: { x: 8, y: 18, w: 4, h: 4 } }, |
62 | | - { id: 'sankey_flow', type: 'sankey', title: 'Status Flow (Sankey)', dataset: taskDs, dimensions: ['status'], values: ['task_count'], layout: { x: 0, y: 22, w: 4, h: 4 } }, |
63 | | - { id: 'radar_priority', type: 'radar', title: 'Priority Radar', dataset: taskDs, dimensions: ['priority'], values: ['task_count'], layout: { x: 4, y: 22, w: 4, h: 4 } }, |
| 45 | + // ── Distribution ───────────────────────────────────────────────────────── |
| 46 | + { id: 'pie_status', type: 'pie', title: 'Status Split', dataset: taskDs, dimensions: ['status'], values: ['task_count'], layout: { x: 0, y: 10, w: 4, h: 4 } }, |
| 47 | + { id: 'donut_priority', type: 'donut', title: 'Priority Split', dataset: taskDs, dimensions: ['priority'], values: ['task_count'], layout: { x: 4, y: 10, w: 4, h: 4 } }, |
| 48 | + { id: 'funnel_status', type: 'funnel', title: 'Status Funnel', dataset: taskDs, dimensions: ['status'], values: ['task_count'], layout: { x: 8, y: 10, w: 4, h: 4 } }, |
64 | 49 |
|
65 | | - // ── Performance ────────────────────────────────────────────────────── |
66 | | - { id: 'solid_gauge', type: 'solid-gauge', title: 'Solid Gauge', dataset: taskDs, values: ['avg_progress'], layout: { x: 8, y: 22, w: 4, h: 4 } }, |
| 50 | + // ── Relationship + Advanced ────────────────────────────────────────────── |
| 51 | + { id: 'scatter_estimate', type: 'scatter', title: 'Estimate vs Progress', dataset: taskDs, dimensions: ['progress'], values: ['avg_estimate'], layout: { x: 0, y: 14, w: 6, h: 4 } }, |
| 52 | + { id: 'radar_priority', type: 'radar', title: 'Priority Radar', dataset: taskDs, dimensions: ['priority'], values: ['task_count'], layout: { x: 6, y: 14, w: 6, h: 4 } }, |
67 | 53 |
|
68 | | - // ── Comparison / trend variants ────────────────────────────────────── |
69 | | - { id: 'bipolar_bar', type: 'bi-polar-bar', title: 'Bi-polar Bar', dataset: taskDs, dimensions: ['status'], values: ['task_count'], layout: { x: 0, y: 26, w: 6, h: 4 } }, |
70 | | - { id: 'step_line', type: 'step-line', title: 'Step Line', dataset: taskDs, dimensions: ['created_at'], values: ['task_count'], layout: { x: 6, y: 26, w: 6, h: 4 } }, |
| 54 | + // ── Composition ────────────────────────────────────────────────────────── |
| 55 | + { id: 'treemap_hours', type: 'treemap', title: 'Hours Treemap', dataset: taskDs, dimensions: ['status'], values: ['est_hours'], layout: { x: 0, y: 18, w: 6, h: 4 } }, |
| 56 | + { id: 'sankey_flow', type: 'sankey', title: 'Status Flow (Sankey)', dataset: taskDs, dimensions: ['status'], values: ['task_count'], layout: { x: 6, y: 18, w: 6, h: 4 } }, |
71 | 57 |
|
72 | | - // ── Tabular ────────────────────────────────────────────────────────── |
73 | | - { id: 'table_projects', type: 'table', title: 'Projects Table', dataset: projectDs, values: ['project_count'], layout: { x: 0, y: 30, w: 6, h: 4 } }, |
74 | | - { id: 'pivot_tasks', type: 'pivot', title: 'Tasks Pivot', dataset: taskDs, dimensions: ['status'], values: ['task_count'], layout: { x: 6, y: 30, w: 6, h: 4 } }, |
| 58 | + // ── Tabular (real grouped tables, multiple measures) ───────────────────── |
| 59 | + { id: 'table_projects', type: 'table', title: 'Projects by Account', dataset: projectDs, dimensions: ['account'], values: ['project_count', 'budget_sum', 'spent_sum'], layout: { x: 0, y: 22, w: 6, h: 4 } }, |
| 60 | + { id: 'pivot_tasks', type: 'pivot', title: 'Tasks by Status × Priority', dataset: taskDs, dimensions: ['status', 'priority'], values: ['task_count'], layout: { x: 6, y: 22, w: 6, h: 4 } }, |
75 | 61 | ], |
76 | 62 | }; |
0 commit comments