@contractspec/example.analytics-dashboard 3.7.6 → 3.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +66 -271
  2. package/dist/browser/dashboard.feature.js +592 -0
  3. package/dist/browser/docs/analytics-dashboard.docblock.js +2 -1
  4. package/dist/browser/docs/index.js +2 -1
  5. package/dist/browser/events.js +1 -1
  6. package/dist/browser/index.js +1260 -627
  7. package/dist/browser/ui/AnalyticsDashboard.js +915 -194
  8. package/dist/browser/ui/AnalyticsDashboard.widgets.js +94 -0
  9. package/dist/browser/ui/AnalyticsQueriesTable.js +188 -0
  10. package/dist/browser/ui/hooks/index.js +594 -3
  11. package/dist/browser/ui/hooks/useAnalyticsData.js +594 -3
  12. package/dist/browser/ui/index.js +1053 -440
  13. package/dist/browser/ui/renderers/analytics.markdown.js +620 -138
  14. package/dist/browser/ui/renderers/index.js +620 -138
  15. package/dist/browser/visualizations/catalog.js +457 -0
  16. package/dist/browser/visualizations/index.js +611 -0
  17. package/dist/browser/visualizations/specs.breakdown.js +140 -0
  18. package/dist/browser/visualizations/specs.performance.js +198 -0
  19. package/dist/browser/visualizations/widgets.js +595 -0
  20. package/dist/dashboard/index.d.ts +3 -3
  21. package/dist/dashboard.feature.js +592 -0
  22. package/dist/docs/analytics-dashboard.docblock.js +2 -1
  23. package/dist/docs/index.js +2 -1
  24. package/dist/events.js +1 -1
  25. package/dist/index.d.ts +4 -3
  26. package/dist/index.js +1260 -627
  27. package/dist/node/dashboard.feature.js +592 -0
  28. package/dist/node/docs/analytics-dashboard.docblock.js +2 -1
  29. package/dist/node/docs/index.js +2 -1
  30. package/dist/node/events.js +1 -1
  31. package/dist/node/index.js +1260 -627
  32. package/dist/node/ui/AnalyticsDashboard.js +915 -194
  33. package/dist/node/ui/AnalyticsDashboard.widgets.js +94 -0
  34. package/dist/node/ui/AnalyticsQueriesTable.js +188 -0
  35. package/dist/node/ui/hooks/index.js +594 -3
  36. package/dist/node/ui/hooks/useAnalyticsData.js +594 -3
  37. package/dist/node/ui/index.js +1053 -440
  38. package/dist/node/ui/renderers/analytics.markdown.js +620 -138
  39. package/dist/node/ui/renderers/index.js +620 -138
  40. package/dist/node/visualizations/catalog.js +457 -0
  41. package/dist/node/visualizations/index.js +611 -0
  42. package/dist/node/visualizations/specs.breakdown.js +140 -0
  43. package/dist/node/visualizations/specs.performance.js +198 -0
  44. package/dist/node/visualizations/widgets.js +595 -0
  45. package/dist/query/index.d.ts +1 -1
  46. package/dist/query-engine/index.d.ts +1 -1
  47. package/dist/ui/AnalyticsDashboard.js +915 -194
  48. package/dist/ui/AnalyticsDashboard.widgets.d.ts +5 -0
  49. package/dist/ui/AnalyticsDashboard.widgets.js +95 -0
  50. package/dist/ui/AnalyticsQueriesTable.d.ts +4 -0
  51. package/dist/ui/AnalyticsQueriesTable.js +189 -0
  52. package/dist/ui/AnalyticsQueriesTable.smoke.test.d.ts +1 -0
  53. package/dist/ui/hooks/index.d.ts +1 -1
  54. package/dist/ui/hooks/index.js +594 -3
  55. package/dist/ui/hooks/useAnalyticsData.js +594 -3
  56. package/dist/ui/index.d.ts +1 -1
  57. package/dist/ui/index.js +1053 -440
  58. package/dist/ui/renderers/analytics.markdown.d.ts +0 -9
  59. package/dist/ui/renderers/analytics.markdown.js +620 -138
  60. package/dist/ui/renderers/index.js +620 -138
  61. package/dist/visualizations/catalog.d.ts +9 -0
  62. package/dist/visualizations/catalog.js +458 -0
  63. package/dist/visualizations/index.d.ts +4 -0
  64. package/dist/visualizations/index.js +612 -0
  65. package/dist/visualizations/specs.breakdown.d.ts +4 -0
  66. package/dist/visualizations/specs.breakdown.js +141 -0
  67. package/dist/visualizations/specs.performance.d.ts +5 -0
  68. package/dist/visualizations/specs.performance.js +199 -0
  69. package/dist/visualizations/widgets.d.ts +24 -0
  70. package/dist/visualizations/widgets.js +596 -0
  71. package/dist/visualizations/widgets.test.d.ts +1 -0
  72. package/package.json +111 -11
package/dist/ui/index.js CHANGED
@@ -1,277 +1,876 @@
1
1
  // @bun
2
- // src/ui/renderers/analytics.markdown.ts
3
- var mockDashboards = [
4
- {
5
- id: "dash-1",
6
- name: "Sales Overview",
7
- slug: "sales-overview",
8
- status: "PUBLISHED",
9
- widgetCount: 8,
10
- viewCount: 1250,
11
- lastViewedAt: "2024-01-16T12:00:00Z"
2
+ // src/visualizations/specs.breakdown.ts
3
+ import { defineVisualization } from "@contractspec/lib.contracts-spec/visualizations";
4
+ var QUERY_REF = { key: "analytics.query.execute", version: "1.0.0" };
5
+ var META = {
6
+ version: "1.0.0",
7
+ domain: "analytics",
8
+ stability: "experimental",
9
+ owners: ["@example.analytics-dashboard"],
10
+ tags: ["analytics", "dashboard", "visualization"]
11
+ };
12
+ var ChannelMixVisualization = defineVisualization({
13
+ meta: {
14
+ ...META,
15
+ key: "analytics.visualization.channel-mix",
16
+ title: "Channel Mix",
17
+ description: "Session distribution across acquisition channels.",
18
+ goal: "Explain which channels currently drive the largest share of traffic.",
19
+ context: "Marketing attribution dashboard."
12
20
  },
13
- {
14
- id: "dash-2",
15
- name: "User Engagement",
16
- slug: "user-engagement",
17
- status: "PUBLISHED",
18
- widgetCount: 6,
19
- viewCount: 890,
20
- lastViewedAt: "2024-01-16T10:00:00Z"
21
+ source: { primary: QUERY_REF, resultPath: "data" },
22
+ visualization: {
23
+ kind: "pie",
24
+ nameDimension: "channel",
25
+ valueMeasure: "sessions",
26
+ dimensions: [
27
+ {
28
+ key: "channel",
29
+ label: "Channel",
30
+ dataPath: "channel",
31
+ type: "category"
32
+ }
33
+ ],
34
+ measures: [{ key: "sessions", label: "Sessions", dataPath: "sessions" }],
35
+ table: { caption: "Sessions by acquisition channel." }
36
+ }
37
+ });
38
+ var EngagementHeatmapVisualization = defineVisualization({
39
+ meta: {
40
+ ...META,
41
+ key: "analytics.visualization.engagement-heatmap",
42
+ title: "Engagement Heatmap",
43
+ description: "Average engagement score by weekday and time band.",
44
+ goal: "Reveal the highest-engagement time windows for product activity.",
45
+ context: "Usage analytics dashboard."
21
46
  },
22
- {
23
- id: "dash-3",
24
- name: "Product Analytics",
25
- slug: "product-analytics",
26
- status: "PUBLISHED",
27
- widgetCount: 10,
28
- viewCount: 560,
29
- lastViewedAt: "2024-01-15T14:00:00Z"
47
+ source: { primary: QUERY_REF, resultPath: "data" },
48
+ visualization: {
49
+ kind: "heatmap",
50
+ xDimension: "timeBand",
51
+ yDimension: "weekday",
52
+ valueMeasure: "engagementScore",
53
+ dimensions: [
54
+ {
55
+ key: "timeBand",
56
+ label: "Time Band",
57
+ dataPath: "timeBand",
58
+ type: "category"
59
+ },
60
+ {
61
+ key: "weekday",
62
+ label: "Weekday",
63
+ dataPath: "weekday",
64
+ type: "category"
65
+ }
66
+ ],
67
+ measures: [
68
+ {
69
+ key: "engagementScore",
70
+ label: "Engagement",
71
+ dataPath: "engagementScore"
72
+ }
73
+ ],
74
+ table: { caption: "Engagement score by weekday and time band." }
75
+ }
76
+ });
77
+ var ConversionFunnelVisualization = defineVisualization({
78
+ meta: {
79
+ ...META,
80
+ key: "analytics.visualization.conversion-funnel",
81
+ title: "Conversion Funnel",
82
+ description: "Progression through the main acquisition funnel.",
83
+ goal: "Show where the product is losing the largest share of prospects.",
84
+ context: "Growth dashboard."
30
85
  },
31
- {
32
- id: "dash-4",
33
- name: "Finance Report",
34
- slug: "finance-report",
35
- status: "DRAFT",
36
- widgetCount: 4,
37
- viewCount: 0,
38
- lastViewedAt: null
86
+ source: { primary: QUERY_REF, resultPath: "data" },
87
+ visualization: {
88
+ kind: "funnel",
89
+ nameDimension: "stage",
90
+ valueMeasure: "users",
91
+ sort: "descending",
92
+ dimensions: [
93
+ { key: "stage", label: "Stage", dataPath: "stage", type: "category" }
94
+ ],
95
+ measures: [{ key: "users", label: "Users", dataPath: "users" }],
96
+ table: { caption: "Users per conversion stage." }
39
97
  }
40
- ];
41
- var mockWidgets = [
42
- {
43
- id: "w-1",
44
- dashboardId: "dash-1",
45
- name: "Total Revenue",
46
- type: "METRIC",
47
- value: 125000,
48
- change: 12.5
98
+ });
99
+ var AccountCoverageGeoVisualization = defineVisualization({
100
+ meta: {
101
+ ...META,
102
+ key: "analytics.visualization.account-coverage-geo",
103
+ title: "Account Coverage",
104
+ description: "High-value accounts plotted on a slippy-map surface.",
105
+ goal: "Locate where active commercial concentration is strongest.",
106
+ context: "Territory coverage dashboard."
49
107
  },
50
- {
51
- id: "w-2",
52
- dashboardId: "dash-1",
53
- name: "Active Users",
54
- type: "METRIC",
55
- value: 4500,
56
- change: 8.2
108
+ source: { primary: QUERY_REF, resultPath: "data" },
109
+ visualization: {
110
+ kind: "geo",
111
+ mode: "slippy-map",
112
+ variant: "scatter",
113
+ nameDimension: "city",
114
+ latitudeDimension: "latitude",
115
+ longitudeDimension: "longitude",
116
+ valueMeasure: "accounts",
117
+ dimensions: [
118
+ { key: "city", label: "City", dataPath: "city", type: "category" },
119
+ {
120
+ key: "latitude",
121
+ label: "Latitude",
122
+ dataPath: "latitude",
123
+ type: "latitude"
124
+ },
125
+ {
126
+ key: "longitude",
127
+ label: "Longitude",
128
+ dataPath: "longitude",
129
+ type: "longitude"
130
+ }
131
+ ],
132
+ measures: [{ key: "accounts", label: "Accounts", dataPath: "accounts" }],
133
+ table: { caption: "Account concentration by city." }
134
+ }
135
+ });
136
+
137
+ // src/visualizations/specs.performance.ts
138
+ import { defineVisualization as defineVisualization2 } from "@contractspec/lib.contracts-spec/visualizations";
139
+ var QUERY_REF2 = { key: "analytics.query.execute", version: "1.0.0" };
140
+ var META2 = {
141
+ version: "1.0.0",
142
+ domain: "analytics",
143
+ stability: "experimental",
144
+ owners: ["@example.analytics-dashboard"],
145
+ tags: ["analytics", "dashboard", "visualization"]
146
+ };
147
+ var RevenueMetricVisualization = defineVisualization2({
148
+ meta: {
149
+ ...META2,
150
+ key: "analytics.visualization.revenue-metric",
151
+ title: "Revenue Snapshot",
152
+ description: "Current recurring revenue with prior-period comparison.",
153
+ goal: "Highlight the headline commercial metric for the dashboard.",
154
+ context: "Executive revenue overview."
57
155
  },
58
- {
59
- id: "w-3",
60
- dashboardId: "dash-1",
61
- name: "Revenue Trend",
62
- type: "LINE_CHART",
63
- dataPoints: 30
156
+ source: { primary: QUERY_REF2, resultPath: "data" },
157
+ visualization: {
158
+ kind: "metric",
159
+ measure: "totalRevenue",
160
+ comparisonMeasure: "priorRevenue",
161
+ dimensions: [
162
+ { key: "period", label: "Period", dataPath: "period", type: "time" }
163
+ ],
164
+ measures: [
165
+ {
166
+ key: "totalRevenue",
167
+ label: "Revenue",
168
+ dataPath: "totalRevenue",
169
+ format: "currency"
170
+ },
171
+ {
172
+ key: "priorRevenue",
173
+ label: "Prior Revenue",
174
+ dataPath: "priorRevenue",
175
+ format: "currency"
176
+ }
177
+ ],
178
+ sparkline: { dimension: "period", measure: "totalRevenue" },
179
+ table: { caption: "Revenue trend and prior period values." }
180
+ }
181
+ });
182
+ var RevenueTrendVisualization = defineVisualization2({
183
+ meta: {
184
+ ...META2,
185
+ key: "analytics.visualization.revenue-trend",
186
+ title: "Revenue Trend",
187
+ description: "Monthly revenue progression for the current quarter.",
188
+ goal: "Track whether revenue growth is accelerating or stalling.",
189
+ context: "Quarterly commercial dashboard."
64
190
  },
65
- {
66
- id: "w-4",
67
- dashboardId: "dash-1",
68
- name: "Top Products",
69
- type: "BAR_CHART",
70
- dataPoints: 10
191
+ source: { primary: QUERY_REF2, resultPath: "data" },
192
+ visualization: {
193
+ kind: "cartesian",
194
+ variant: "line",
195
+ xDimension: "date",
196
+ yMeasures: ["revenue"],
197
+ dimensions: [
198
+ { key: "date", label: "Month", dataPath: "date", type: "time" }
199
+ ],
200
+ measures: [
201
+ {
202
+ key: "revenue",
203
+ label: "Revenue",
204
+ dataPath: "revenue",
205
+ format: "currency",
206
+ color: "#0f766e"
207
+ }
208
+ ],
209
+ thresholds: [
210
+ { key: "target", value: 140000, label: "Target", color: "#dc2626" }
211
+ ],
212
+ table: { caption: "Monthly revenue values." }
213
+ }
214
+ });
215
+ var RegionalRevenueVisualization = defineVisualization2({
216
+ meta: {
217
+ ...META2,
218
+ key: "analytics.visualization.regional-revenue",
219
+ title: "Regional Revenue",
220
+ description: "Revenue split by sales territory.",
221
+ goal: "Compare the strongest and weakest performing territories.",
222
+ context: "Territory planning and commercial comparison."
71
223
  },
72
- {
73
- id: "w-5",
74
- dashboardId: "dash-2",
75
- name: "Daily Active Users",
76
- type: "LINE_CHART",
77
- dataPoints: 30
224
+ source: { primary: QUERY_REF2, resultPath: "data" },
225
+ visualization: {
226
+ kind: "cartesian",
227
+ variant: "bar",
228
+ xDimension: "region",
229
+ yMeasures: ["revenue"],
230
+ dimensions: [
231
+ { key: "region", label: "Region", dataPath: "region", type: "category" }
232
+ ],
233
+ measures: [
234
+ {
235
+ key: "revenue",
236
+ label: "Revenue",
237
+ dataPath: "revenue",
238
+ format: "currency",
239
+ color: "#1d4ed8"
240
+ }
241
+ ],
242
+ table: { caption: "Revenue by region." }
243
+ }
244
+ });
245
+ var RetentionAreaVisualization = defineVisualization2({
246
+ meta: {
247
+ ...META2,
248
+ key: "analytics.visualization.retention-area",
249
+ title: "Retention Curve",
250
+ description: "Weekly retention progression across the active cohort.",
251
+ goal: "Show whether user retention remains above the desired floor.",
252
+ context: "Product health dashboard."
78
253
  },
79
- {
80
- id: "w-6",
81
- dashboardId: "dash-2",
82
- name: "Session Duration",
83
- type: "METRIC",
84
- value: 245,
85
- change: -3.1
254
+ source: { primary: QUERY_REF2, resultPath: "data" },
255
+ visualization: {
256
+ kind: "cartesian",
257
+ variant: "area",
258
+ xDimension: "week",
259
+ yMeasures: ["retentionRate"],
260
+ dimensions: [
261
+ { key: "week", label: "Week", dataPath: "week", type: "category" }
262
+ ],
263
+ measures: [
264
+ {
265
+ key: "retentionRate",
266
+ label: "Retention",
267
+ dataPath: "retentionRate",
268
+ format: "percentage",
269
+ color: "#16a34a"
270
+ }
271
+ ],
272
+ thresholds: [
273
+ { key: "floor", value: 0.5, label: "Floor", color: "#f59e0b" }
274
+ ],
275
+ table: { caption: "Weekly retention rate." }
276
+ }
277
+ });
278
+ var PipelineScatterVisualization = defineVisualization2({
279
+ meta: {
280
+ ...META2,
281
+ key: "analytics.visualization.pipeline-scatter",
282
+ title: "Pipeline Velocity",
283
+ description: "Deal-cycle length against win rate for active accounts.",
284
+ goal: "Spot outliers where the sales cycle is long but conversion remains weak.",
285
+ context: "Commercial operations dashboard."
286
+ },
287
+ source: { primary: QUERY_REF2, resultPath: "data" },
288
+ visualization: {
289
+ kind: "cartesian",
290
+ variant: "scatter",
291
+ xDimension: "cycleDays",
292
+ yMeasures: ["winRate"],
293
+ dimensions: [
294
+ {
295
+ key: "cycleDays",
296
+ label: "Cycle Days",
297
+ dataPath: "cycleDays",
298
+ type: "number"
299
+ }
300
+ ],
301
+ measures: [
302
+ {
303
+ key: "winRate",
304
+ label: "Win Rate",
305
+ dataPath: "winRate",
306
+ format: "percentage",
307
+ color: "#7c3aed"
308
+ },
309
+ {
310
+ key: "arr",
311
+ label: "ARR",
312
+ dataPath: "arr",
313
+ format: "currency"
314
+ }
315
+ ],
316
+ series: [
317
+ {
318
+ key: "pipeline",
319
+ label: "Accounts",
320
+ measure: "winRate",
321
+ type: "scatter",
322
+ color: "#7c3aed"
323
+ }
324
+ ],
325
+ table: { caption: "Sales cycle and win rate per account." }
86
326
  }
327
+ });
328
+
329
+ // src/visualizations/catalog.ts
330
+ import { VisualizationRegistry } from "@contractspec/lib.contracts-spec/visualizations";
331
+ var AnalyticsVisualizationSpecs = [
332
+ RevenueMetricVisualization,
333
+ RevenueTrendVisualization,
334
+ RegionalRevenueVisualization,
335
+ RetentionAreaVisualization,
336
+ PipelineScatterVisualization,
337
+ ChannelMixVisualization,
338
+ EngagementHeatmapVisualization,
339
+ ConversionFunnelVisualization,
340
+ AccountCoverageGeoVisualization
87
341
  ];
88
- var mockQueries = [
89
- {
90
- id: "q-1",
91
- name: "Monthly Revenue",
92
- type: "AGGREGATION",
93
- isShared: true,
94
- executionCount: 1500
342
+ var AnalyticsVisualizationRegistry = new VisualizationRegistry([
343
+ ...AnalyticsVisualizationSpecs
344
+ ]);
345
+ var AnalyticsVisualizationRefs = AnalyticsVisualizationSpecs.map((spec) => refOf(spec));
346
+ var AnalyticsVisualizationSpecMap = new Map(AnalyticsVisualizationSpecs.map((spec) => [
347
+ visualizationRefKey(spec.meta),
348
+ spec
349
+ ]));
350
+ var AnalyticsVisualizationSampleData = {
351
+ [visualizationRefKey(RevenueMetricVisualization.meta)]: {
352
+ data: [
353
+ { period: "2025-11-01", totalRevenue: 112000, priorRevenue: 103000 },
354
+ { period: "2025-12-01", totalRevenue: 119000, priorRevenue: 110000 },
355
+ { period: "2026-01-01", totalRevenue: 126500, priorRevenue: 116000 },
356
+ { period: "2026-02-01", totalRevenue: 133000, priorRevenue: 124000 },
357
+ { period: "2026-03-01", totalRevenue: 145500, priorRevenue: 133000 }
358
+ ]
95
359
  },
96
- {
97
- id: "q-2",
98
- name: "User Growth",
99
- type: "METRIC",
100
- isShared: true,
101
- executionCount: 890
360
+ [visualizationRefKey(RevenueTrendVisualization.meta)]: {
361
+ data: [
362
+ { date: "2025-11-01", revenue: 112000 },
363
+ { date: "2025-12-01", revenue: 119000 },
364
+ { date: "2026-01-01", revenue: 126500 },
365
+ { date: "2026-02-01", revenue: 133000 },
366
+ { date: "2026-03-01", revenue: 145500 }
367
+ ]
102
368
  },
103
- {
104
- id: "q-3",
105
- name: "Product Sales",
106
- type: "SQL",
107
- isShared: false,
108
- executionCount: 340
369
+ [visualizationRefKey(RegionalRevenueVisualization.meta)]: {
370
+ data: [
371
+ { region: "North America", revenue: 210000 },
372
+ { region: "EMEA", revenue: 174000 },
373
+ { region: "APAC", revenue: 132000 },
374
+ { region: "LATAM", revenue: 88000 }
375
+ ]
109
376
  },
110
- {
111
- id: "q-4",
112
- name: "Conversion Funnel",
113
- type: "AGGREGATION",
114
- isShared: true,
115
- executionCount: 450
116
- }
117
- ];
118
- function formatNumber(value) {
119
- if (value >= 1e6) {
120
- return `${(value / 1e6).toFixed(1)}M`;
121
- }
122
- if (value >= 1000) {
123
- return `${(value / 1000).toFixed(1)}K`;
377
+ [visualizationRefKey(RetentionAreaVisualization.meta)]: {
378
+ data: [
379
+ { week: "Week 1", retentionRate: 0.71 },
380
+ { week: "Week 2", retentionRate: 0.66 },
381
+ { week: "Week 3", retentionRate: 0.62 },
382
+ { week: "Week 4", retentionRate: 0.58 },
383
+ { week: "Week 5", retentionRate: 0.55 },
384
+ { week: "Week 6", retentionRate: 0.53 }
385
+ ]
386
+ },
387
+ [visualizationRefKey(PipelineScatterVisualization.meta)]: {
388
+ data: [
389
+ { cycleDays: 18, winRate: 0.31, arr: 82000 },
390
+ { cycleDays: 26, winRate: 0.44, arr: 65000 },
391
+ { cycleDays: 33, winRate: 0.27, arr: 91000 },
392
+ { cycleDays: 14, winRate: 0.56, arr: 47000 },
393
+ { cycleDays: 21, winRate: 0.48, arr: 59000 },
394
+ { cycleDays: 39, winRate: 0.22, arr: 114000 }
395
+ ]
396
+ },
397
+ [visualizationRefKey(ChannelMixVisualization.meta)]: {
398
+ data: [
399
+ { channel: "Direct", sessions: 4200 },
400
+ { channel: "Organic Search", sessions: 3600 },
401
+ { channel: "Paid Search", sessions: 2100 },
402
+ { channel: "Partner", sessions: 1400 },
403
+ { channel: "Referral", sessions: 900 }
404
+ ]
405
+ },
406
+ [visualizationRefKey(EngagementHeatmapVisualization.meta)]: {
407
+ data: [
408
+ { weekday: "Mon", timeBand: "09:00", engagementScore: 74 },
409
+ { weekday: "Mon", timeBand: "13:00", engagementScore: 82 },
410
+ { weekday: "Tue", timeBand: "09:00", engagementScore: 69 },
411
+ { weekday: "Tue", timeBand: "13:00", engagementScore: 88 },
412
+ { weekday: "Wed", timeBand: "09:00", engagementScore: 77 },
413
+ { weekday: "Wed", timeBand: "13:00", engagementScore: 91 },
414
+ { weekday: "Thu", timeBand: "09:00", engagementScore: 72 },
415
+ { weekday: "Thu", timeBand: "13:00", engagementScore: 86 },
416
+ { weekday: "Fri", timeBand: "09:00", engagementScore: 65 },
417
+ { weekday: "Fri", timeBand: "13:00", engagementScore: 79 }
418
+ ]
419
+ },
420
+ [visualizationRefKey(ConversionFunnelVisualization.meta)]: {
421
+ data: [
422
+ { stage: "Visited Site", users: 12000 },
423
+ { stage: "Started Trial", users: 4200 },
424
+ { stage: "Activated Workspace", users: 2400 },
425
+ { stage: "Requested Demo", users: 980 },
426
+ { stage: "Closed Won", users: 310 }
427
+ ]
428
+ },
429
+ [visualizationRefKey(AccountCoverageGeoVisualization.meta)]: {
430
+ data: [
431
+ { city: "Paris", latitude: 48.8566, longitude: 2.3522, accounts: 48 },
432
+ { city: "London", latitude: 51.5072, longitude: -0.1276, accounts: 62 },
433
+ { city: "New York", latitude: 40.7128, longitude: -74.006, accounts: 71 },
434
+ { city: "Toronto", latitude: 43.6532, longitude: -79.3832, accounts: 36 },
435
+ {
436
+ city: "Singapore",
437
+ latitude: 1.3521,
438
+ longitude: 103.8198,
439
+ accounts: 29
440
+ }
441
+ ]
124
442
  }
125
- return value.toString();
443
+ };
444
+ function refOf(spec) {
445
+ return { key: spec.meta.key, version: spec.meta.version };
126
446
  }
127
- function formatChange(change) {
128
- const icon = change >= 0 ? "\uD83D\uDCC8" : "\uD83D\uDCC9";
129
- const sign = change >= 0 ? "+" : "";
130
- return `${icon} ${sign}${change.toFixed(1)}%`;
447
+ function visualizationRefKey(ref) {
448
+ return `${ref.key}.v${ref.version}`;
131
449
  }
132
- var analyticsDashboardMarkdownRenderer = {
133
- target: "markdown",
134
- render: async (desc) => {
135
- if (desc.source.type !== "component" || desc.source.componentKey !== "AnalyticsDashboard") {
136
- throw new Error("analyticsDashboardMarkdownRenderer: not AnalyticsDashboard");
137
- }
138
- const dashboards = mockDashboards;
139
- const widgets = mockWidgets;
140
- const queries = mockQueries;
141
- const dashboard = dashboards[0];
142
- if (!dashboard) {
143
- return {
144
- mimeType: "text/markdown",
145
- body: `# No Dashboards
146
450
 
147
- No dashboards available.`
148
- };
149
- }
150
- const dashboardWidgets = widgets.filter((w) => w.dashboardId === dashboard.id);
151
- const publishedDashboards = dashboards.filter((d) => d.status === "PUBLISHED");
152
- const totalViews = dashboards.reduce((sum, d) => sum + d.viewCount, 0);
153
- const sharedQueries = queries.filter((q) => q.isShared);
154
- const lines = [
155
- `# ${dashboard.name}`,
156
- "",
157
- "> Analytics dashboard overview",
158
- "",
159
- "## Key Metrics",
160
- ""
161
- ];
162
- const metricWidgets = dashboardWidgets.filter((w) => w.type === "METRIC");
163
- for (const widget of metricWidgets) {
164
- const w = widget;
165
- lines.push(`### ${w.name}`);
166
- lines.push(`**${formatNumber(w.value)}** ${formatChange(w.change)}`);
167
- lines.push("");
168
- }
169
- lines.push("## Visualizations");
170
- lines.push("");
171
- const chartWidgets = dashboardWidgets.filter((w) => w.type !== "METRIC");
172
- for (const widget of chartWidgets) {
173
- const w = widget;
174
- lines.push(`- **${w.name}** (${w.type.replace("_", " ")}) - ${w.dataPoints} data points`);
175
- }
176
- lines.push("");
177
- lines.push("## Dashboard Stats");
178
- lines.push("");
179
- lines.push("| Metric | Value |");
180
- lines.push("|--------|-------|");
181
- lines.push(`| Total Dashboards | ${dashboards.length} |`);
182
- lines.push(`| Published | ${publishedDashboards.length} |`);
183
- lines.push(`| Total Views | ${totalViews.toLocaleString()} |`);
184
- lines.push(`| Shared Queries | ${sharedQueries.length} |`);
185
- return {
186
- mimeType: "text/markdown",
187
- body: lines.join(`
188
- `)
189
- };
190
- }
451
+ // src/visualizations/widgets.ts
452
+ var LEGACY_VISUALIZATION_REFS = {
453
+ METRIC: refOf(RevenueMetricVisualization),
454
+ LINE_CHART: refOf(RevenueTrendVisualization),
455
+ BAR_CHART: refOf(RegionalRevenueVisualization),
456
+ AREA_CHART: refOf(RetentionAreaVisualization),
457
+ SCATTER_PLOT: refOf(PipelineScatterVisualization),
458
+ PIE_CHART: refOf(ChannelMixVisualization),
459
+ HEATMAP: refOf(EngagementHeatmapVisualization),
460
+ FUNNEL: refOf(ConversionFunnelVisualization),
461
+ MAP: refOf(AccountCoverageGeoVisualization)
191
462
  };
192
- var dashboardListMarkdownRenderer = {
193
- target: "markdown",
194
- render: async (desc) => {
195
- if (desc.source.type !== "component" || desc.source.componentKey !== "DashboardList") {
196
- throw new Error("dashboardListMarkdownRenderer: not DashboardList");
197
- }
198
- const dashboards = mockDashboards;
199
- const lines = [
200
- "# Dashboards",
201
- "",
202
- "> Browse and manage analytics dashboards",
203
- "",
204
- "| Dashboard | Widgets | Views | Status | Last Viewed |",
205
- "|-----------|---------|-------|--------|-------------|"
206
- ];
207
- for (const dashboard of dashboards) {
208
- const lastViewed = dashboard.lastViewedAt ? new Date(dashboard.lastViewedAt).toLocaleDateString() : "Never";
209
- const statusIcon = dashboard.status === "PUBLISHED" ? "\uD83D\uDFE2" : "\u26AB";
210
- lines.push(`| [${dashboard.name}](/dashboards/${dashboard.slug}) | ${dashboard.widgetCount} | ${dashboard.viewCount.toLocaleString()} | ${statusIcon} ${dashboard.status} | ${lastViewed} |`);
211
- }
212
- lines.push("");
213
- lines.push("## Quick Actions");
214
- lines.push("");
215
- lines.push("- **Create Dashboard** - Start with a blank canvas");
216
- lines.push("- **Import Template** - Use a pre-built template");
217
- lines.push("- **Clone Dashboard** - Duplicate an existing dashboard");
463
+ function createExampleWidgets(dashboardId) {
464
+ return [
465
+ widget(dashboardId, "widget_revenue_metric", "Revenue Snapshot", "METRIC", 0, 0, 4, 2, {
466
+ layout: "single",
467
+ bindings: [binding(RevenueMetricVisualization, 200)]
468
+ }),
469
+ widget(dashboardId, "widget_revenue_trend", "Revenue Trend", "LINE_CHART", 4, 0, 8, 4, {
470
+ layout: "single",
471
+ bindings: [binding(RevenueTrendVisualization)]
472
+ }),
473
+ widget(dashboardId, "widget_regional_revenue", "Regional Revenue", "BAR_CHART", 0, 2, 6, 4, {
474
+ layout: "single",
475
+ bindings: [binding(RegionalRevenueVisualization)]
476
+ }),
477
+ widget(dashboardId, "widget_channel_mix", "Channel Mix", "PIE_CHART", 6, 2, 6, 4, {
478
+ layout: "single",
479
+ bindings: [binding(ChannelMixVisualization)]
480
+ }),
481
+ widget(dashboardId, "widget_retention", "Retention Curve", "AREA_CHART", 0, 6, 6, 4, {
482
+ layout: "single",
483
+ bindings: [binding(RetentionAreaVisualization)]
484
+ }),
485
+ widget(dashboardId, "widget_pipeline", "Pipeline Velocity", "SCATTER_PLOT", 6, 6, 6, 4, {
486
+ layout: "single",
487
+ bindings: [binding(PipelineScatterVisualization)]
488
+ }),
489
+ widget(dashboardId, "widget_heatmap", "Engagement Heatmap", "HEATMAP", 0, 10, 8, 4, {
490
+ layout: "single",
491
+ bindings: [binding(EngagementHeatmapVisualization)]
492
+ }),
493
+ widget(dashboardId, "widget_funnel", "Conversion Funnel", "FUNNEL", 8, 10, 4, 4, {
494
+ layout: "single",
495
+ bindings: [binding(ConversionFunnelVisualization)]
496
+ }),
497
+ widget(dashboardId, "widget_geo", "Account Coverage", "MAP", 0, 14, 12, 5, {
498
+ layout: "single",
499
+ bindings: [binding(AccountCoverageGeoVisualization, 360)]
500
+ }),
501
+ widget(dashboardId, "widget_comparison", "Commercial Comparison", "EMBED", 0, 19, 12, 6, {
502
+ layout: "comparison",
503
+ description: "Compare regional distribution, channel balance, and funnel shape.",
504
+ bindings: [
505
+ binding(RegionalRevenueVisualization, 240),
506
+ binding(ChannelMixVisualization, 240),
507
+ binding(ConversionFunnelVisualization, 240)
508
+ ]
509
+ }),
510
+ widget(dashboardId, "widget_timeline", "Growth Timeline", "EMBED", 0, 25, 12, 6, {
511
+ layout: "timeline",
512
+ description: "Track revenue and retention over the same reporting cadence.",
513
+ bindings: [
514
+ binding(RevenueTrendVisualization, 220),
515
+ binding(RetentionAreaVisualization, 220)
516
+ ]
517
+ })
518
+ ];
519
+ }
520
+ function resolveAnalyticsWidget(widget) {
521
+ const config = parseWidgetConfig(widget);
522
+ const bindings = config.bindings.map((binding) => {
523
+ const spec = AnalyticsVisualizationSpecMap.get(visualizationRefKey(binding.ref));
524
+ if (!spec)
525
+ return null;
218
526
  return {
219
- mimeType: "text/markdown",
220
- body: lines.join(`
221
- `)
527
+ key: `${widget.id}:${visualizationRefKey(binding.ref)}`,
528
+ spec,
529
+ data: binding.data,
530
+ title: binding.title ?? spec.meta.title,
531
+ description: binding.description ?? spec.meta.description,
532
+ height: binding.height
222
533
  };
534
+ }).filter((binding) => Boolean(binding));
535
+ if (!bindings.length)
536
+ return null;
537
+ return {
538
+ id: widget.id,
539
+ name: widget.name,
540
+ description: config.description,
541
+ layout: config.layout,
542
+ gridX: widget.gridX,
543
+ gridY: widget.gridY,
544
+ gridWidth: widget.gridWidth,
545
+ gridHeight: widget.gridHeight,
546
+ bindings
547
+ };
548
+ }
549
+ function parseWidgetConfig(widget) {
550
+ if (isAnalyticsWidgetConfig(widget.config)) {
551
+ return widget.config;
223
552
  }
224
- };
225
- var queryBuilderMarkdownRenderer = {
226
- target: "markdown",
227
- render: async (desc) => {
228
- if (desc.source.type !== "component" || desc.source.componentKey !== "QueryBuilder") {
229
- throw new Error("queryBuilderMarkdownRenderer: not QueryBuilder");
230
- }
231
- const queries = mockQueries;
232
- const lines = [
233
- "# Query Builder",
234
- "",
235
- "> Create and manage data queries",
236
- "",
237
- "## Saved Queries",
238
- "",
239
- "| Query | Type | Shared | Executions |",
240
- "|-------|------|--------|------------|"
241
- ];
242
- for (const query of queries) {
243
- const sharedIcon = query.isShared ? "\uD83C\uDF10" : "\uD83D\uDD12";
244
- lines.push(`| ${query.name} | ${query.type} | ${sharedIcon} | ${query.executionCount.toLocaleString()} |`);
245
- }
246
- lines.push("");
247
- lines.push("## Query Types");
248
- lines.push("");
249
- lines.push("### METRIC");
250
- lines.push("Query usage metrics from the metering system.");
251
- lines.push("");
252
- lines.push("### AGGREGATION");
253
- lines.push("Build aggregations with measures and dimensions without writing SQL.");
254
- lines.push("");
255
- lines.push("### SQL");
256
- lines.push("Write custom SQL queries for advanced analysis.");
257
- lines.push("");
258
- lines.push("## Features");
259
- lines.push("");
260
- lines.push("- Visual query builder");
261
- lines.push("- Auto-complete for fields and functions");
262
- lines.push("- Query validation and optimization");
263
- lines.push("- Result caching");
264
- lines.push("- Query sharing and collaboration");
265
- return {
266
- mimeType: "text/markdown",
267
- body: lines.join(`
268
- `)
269
- };
553
+ const legacyRef = LEGACY_VISUALIZATION_REFS[widget.type];
554
+ return legacyRef ? {
555
+ layout: "single",
556
+ bindings: [
557
+ {
558
+ ref: legacyRef,
559
+ data: AnalyticsVisualizationSampleData[visualizationRefKey(legacyRef)]
560
+ }
561
+ ]
562
+ } : { layout: "single", bindings: [] };
563
+ }
564
+ function binding(spec, height = 280) {
565
+ return {
566
+ ref: refOf(spec),
567
+ data: AnalyticsVisualizationSampleData[visualizationRefKey(spec.meta)],
568
+ height
569
+ };
570
+ }
571
+ function widget(dashboardId, id, name, type, gridX, gridY, gridWidth, gridHeight, config) {
572
+ const now = new Date;
573
+ return {
574
+ id,
575
+ dashboardId,
576
+ name,
577
+ type,
578
+ gridX,
579
+ gridY,
580
+ gridWidth,
581
+ gridHeight,
582
+ config,
583
+ createdAt: now,
584
+ updatedAt: now
585
+ };
586
+ }
587
+ function isAnalyticsWidgetConfig(value) {
588
+ if (!value || typeof value !== "object")
589
+ return false;
590
+ const candidate = value;
591
+ return (candidate.layout === "single" || candidate.layout === "comparison" || candidate.layout === "timeline") && Array.isArray(candidate.bindings);
592
+ }
593
+ // src/ui/AnalyticsDashboard.widgets.tsx
594
+ import {
595
+ ComparisonView,
596
+ TimelineView,
597
+ VisualizationCard,
598
+ VisualizationGrid
599
+ } from "@contractspec/lib.design-system";
600
+ import { jsxDEV } from "react/jsx-dev-runtime";
601
+ "use client";
602
+ function AnalyticsWidgetBoard({
603
+ dashboardName,
604
+ widgets
605
+ }) {
606
+ if (!widgets.length) {
607
+ return /* @__PURE__ */ jsxDEV("div", {
608
+ className: "rounded-lg border border-dashed p-10 text-center text-muted-foreground",
609
+ children: [
610
+ 'No visualization widgets configured for "',
611
+ dashboardName,
612
+ '".'
613
+ ]
614
+ }, undefined, true, undefined, this);
270
615
  }
616
+ return /* @__PURE__ */ jsxDEV("div", {
617
+ children: [
618
+ /* @__PURE__ */ jsxDEV("h3", {
619
+ className: "mb-4 font-semibold text-lg",
620
+ children: [
621
+ 'Widgets in "',
622
+ dashboardName,
623
+ '"'
624
+ ]
625
+ }, undefined, true, undefined, this),
626
+ /* @__PURE__ */ jsxDEV(VisualizationGrid, {
627
+ children: widgets.map((widget2) => /* @__PURE__ */ jsxDEV("div", {
628
+ className: gridSpanClass(widget2.gridWidth),
629
+ children: renderVisualizationWidget(widget2)
630
+ }, widget2.id, false, undefined, this))
631
+ }, undefined, false, undefined, this)
632
+ ]
633
+ }, undefined, true, undefined, this);
634
+ }
635
+ function renderVisualizationWidget(widget2) {
636
+ const footer = /* @__PURE__ */ jsxDEV("span", {
637
+ className: "text-muted-foreground text-xs",
638
+ children: [
639
+ "Position: (",
640
+ widget2.gridX,
641
+ ", ",
642
+ widget2.gridY,
643
+ ") \u2022 ",
644
+ widget2.gridWidth,
645
+ "x",
646
+ widget2.gridHeight
647
+ ]
648
+ }, undefined, true, undefined, this);
649
+ if (widget2.layout === "comparison") {
650
+ return /* @__PURE__ */ jsxDEV(ComparisonView, {
651
+ description: widget2.description,
652
+ items: widget2.bindings.map((binding3) => ({ ...binding3, footer })),
653
+ title: widget2.name
654
+ }, undefined, false, undefined, this);
655
+ }
656
+ if (widget2.layout === "timeline") {
657
+ return /* @__PURE__ */ jsxDEV(TimelineView, {
658
+ description: widget2.description,
659
+ items: widget2.bindings.map((binding3) => ({ ...binding3, footer })),
660
+ title: widget2.name
661
+ }, undefined, false, undefined, this);
662
+ }
663
+ const binding2 = widget2.bindings[0];
664
+ if (!binding2)
665
+ return null;
666
+ return /* @__PURE__ */ jsxDEV(VisualizationCard, {
667
+ data: binding2.data,
668
+ description: widget2.description ?? binding2.description,
669
+ footer,
670
+ height: binding2.height,
671
+ spec: binding2.spec,
672
+ title: widget2.name
673
+ }, undefined, false, undefined, this);
674
+ }
675
+ function gridSpanClass(gridWidth) {
676
+ if (gridWidth >= 12)
677
+ return "md:col-span-2 xl:col-span-3";
678
+ if (gridWidth >= 8)
679
+ return "xl:col-span-2";
680
+ if (gridWidth >= 6)
681
+ return "md:col-span-2";
682
+ return "";
683
+ }
684
+
685
+ // src/ui/AnalyticsQueriesTable.tsx
686
+ import { DataTable } from "@contractspec/lib.design-system";
687
+ import { useContractTable } from "@contractspec/lib.presentation-runtime-react";
688
+ import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
689
+ import { HStack, VStack } from "@contractspec/lib.ui-kit-web/ui/stack";
690
+ import { Text } from "@contractspec/lib.ui-kit-web/ui/text";
691
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
692
+ "use client";
693
+ var QUERY_TYPE_COLORS = {
694
+ SQL: "default",
695
+ METRIC: "secondary",
696
+ AGGREGATION: "secondary",
697
+ CUSTOM: "outline"
271
698
  };
699
+ function formatJson(value) {
700
+ return JSON.stringify(value, null, 2);
701
+ }
702
+ function AnalyticsQueriesTable({ queries }) {
703
+ const controller = useContractTable({
704
+ data: queries,
705
+ columns: [
706
+ {
707
+ id: "query",
708
+ header: "Query",
709
+ label: "Query",
710
+ accessor: (query) => query.name,
711
+ cell: ({ item }) => /* @__PURE__ */ jsxDEV2(VStack, {
712
+ gap: "xs",
713
+ children: [
714
+ /* @__PURE__ */ jsxDEV2(Text, {
715
+ className: "font-medium text-sm",
716
+ children: item.name
717
+ }, undefined, false, undefined, this),
718
+ /* @__PURE__ */ jsxDEV2(Text, {
719
+ className: "text-muted-foreground text-xs",
720
+ children: [
721
+ "Updated ",
722
+ item.updatedAt.toLocaleDateString()
723
+ ]
724
+ }, undefined, true, undefined, this)
725
+ ]
726
+ }, undefined, true, undefined, this),
727
+ size: 240,
728
+ minSize: 180,
729
+ canSort: true,
730
+ canPin: true,
731
+ canResize: true
732
+ },
733
+ {
734
+ id: "description",
735
+ header: "Description",
736
+ label: "Description",
737
+ accessor: (query) => query.description ?? "",
738
+ cell: ({ value }) => /* @__PURE__ */ jsxDEV2(Text, {
739
+ className: "line-clamp-2 text-muted-foreground text-sm",
740
+ children: String(value || "No description")
741
+ }, undefined, false, undefined, this),
742
+ size: 300,
743
+ minSize: 220,
744
+ canSort: false,
745
+ canHide: true,
746
+ canResize: true
747
+ },
748
+ {
749
+ id: "type",
750
+ header: "Type",
751
+ label: "Type",
752
+ accessorKey: "type",
753
+ cell: ({ value }) => /* @__PURE__ */ jsxDEV2(Badge, {
754
+ variant: QUERY_TYPE_COLORS[String(value)],
755
+ children: String(value)
756
+ }, undefined, false, undefined, this),
757
+ size: 150,
758
+ canSort: true,
759
+ canHide: true,
760
+ canResize: true
761
+ },
762
+ {
763
+ id: "cacheTtlSeconds",
764
+ header: "Cache TTL",
765
+ label: "Cache TTL",
766
+ accessorKey: "cacheTtlSeconds",
767
+ cell: ({ value }) => `${String(value)}s`,
768
+ align: "right",
769
+ size: 140,
770
+ canSort: true,
771
+ canResize: true
772
+ },
773
+ {
774
+ id: "isShared",
775
+ header: "Shared",
776
+ label: "Shared",
777
+ accessorKey: "isShared",
778
+ cell: ({ value }) => /* @__PURE__ */ jsxDEV2(Badge, {
779
+ variant: value ? "default" : "outline",
780
+ children: value ? "Shared" : "Private"
781
+ }, undefined, false, undefined, this),
782
+ size: 140,
783
+ canSort: true,
784
+ canHide: true,
785
+ canResize: true
786
+ }
787
+ ],
788
+ initialState: {
789
+ pagination: { pageIndex: 0, pageSize: 3 },
790
+ columnVisibility: { description: false },
791
+ columnPinning: { left: ["query"], right: [] }
792
+ },
793
+ renderExpandedContent: (query) => /* @__PURE__ */ jsxDEV2(VStack, {
794
+ gap: "sm",
795
+ className: "py-2",
796
+ children: [
797
+ /* @__PURE__ */ jsxDEV2(VStack, {
798
+ gap: "xs",
799
+ children: [
800
+ /* @__PURE__ */ jsxDEV2(Text, {
801
+ className: "font-medium text-sm",
802
+ children: "Description"
803
+ }, undefined, false, undefined, this),
804
+ /* @__PURE__ */ jsxDEV2(Text, {
805
+ className: "text-muted-foreground text-sm",
806
+ children: query.description ?? "No description"
807
+ }, undefined, false, undefined, this)
808
+ ]
809
+ }, undefined, true, undefined, this),
810
+ query.sql ? /* @__PURE__ */ jsxDEV2(VStack, {
811
+ gap: "xs",
812
+ children: [
813
+ /* @__PURE__ */ jsxDEV2(Text, {
814
+ className: "font-medium text-sm",
815
+ children: "SQL"
816
+ }, undefined, false, undefined, this),
817
+ /* @__PURE__ */ jsxDEV2("pre", {
818
+ className: "overflow-auto rounded-md bg-muted/40 p-3 text-xs",
819
+ children: query.sql
820
+ }, undefined, false, undefined, this)
821
+ ]
822
+ }, undefined, true, undefined, this) : null,
823
+ /* @__PURE__ */ jsxDEV2(VStack, {
824
+ gap: "xs",
825
+ children: [
826
+ /* @__PURE__ */ jsxDEV2(Text, {
827
+ className: "font-medium text-sm",
828
+ children: "Definition"
829
+ }, undefined, false, undefined, this),
830
+ /* @__PURE__ */ jsxDEV2("pre", {
831
+ className: "overflow-auto rounded-md bg-muted/40 p-3 text-xs",
832
+ children: formatJson(query.definition)
833
+ }, undefined, false, undefined, this)
834
+ ]
835
+ }, undefined, true, undefined, this)
836
+ ]
837
+ }, undefined, true, undefined, this),
838
+ getCanExpand: () => true
839
+ });
840
+ return /* @__PURE__ */ jsxDEV2(DataTable, {
841
+ controller,
842
+ title: "Saved Queries",
843
+ description: "Client-mode table using the shared ContractSpec controller and renderer.",
844
+ toolbar: /* @__PURE__ */ jsxDEV2(HStack, {
845
+ gap: "sm",
846
+ className: "flex-wrap",
847
+ children: [
848
+ /* @__PURE__ */ jsxDEV2(Text, {
849
+ className: "text-muted-foreground text-sm",
850
+ children: [
851
+ queries.length,
852
+ " queries"
853
+ ]
854
+ }, undefined, true, undefined, this),
855
+ /* @__PURE__ */ jsxDEV2(Text, {
856
+ className: "text-muted-foreground text-sm",
857
+ children: [
858
+ queries.filter((query) => query.isShared).length,
859
+ " shared"
860
+ ]
861
+ }, undefined, true, undefined, this)
862
+ ]
863
+ }, undefined, true, undefined, this),
864
+ emptyState: /* @__PURE__ */ jsxDEV2("div", {
865
+ className: "rounded-md border border-dashed p-8 text-center text-muted-foreground text-sm",
866
+ children: "No queries saved"
867
+ }, undefined, false, undefined, this)
868
+ }, undefined, false, undefined, this);
869
+ }
870
+
272
871
  // src/ui/hooks/useAnalyticsData.ts
273
- import { useCallback, useEffect, useState } from "react";
274
872
  import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
873
+ import { useCallback, useEffect, useState } from "react";
275
874
  "use client";
276
875
  function useAnalyticsData(projectId = "local-project") {
277
876
  const { handlers } = useTemplateRuntime();
@@ -297,7 +896,7 @@ function useAnalyticsData(projectId = "local-project") {
297
896
  if (first) {
298
897
  setSelectedDashboard(first);
299
898
  const dashboardWidgets = await analytics.getWidgets(first.id);
300
- setWidgets(dashboardWidgets);
899
+ setWidgets(dashboardWidgets.length > 0 ? dashboardWidgets : createExampleWidgets(first.id));
301
900
  }
302
901
  }
303
902
  } catch (err) {
@@ -312,7 +911,7 @@ function useAnalyticsData(projectId = "local-project") {
312
911
  const selectDashboard = useCallback(async (dashboard) => {
313
912
  setSelectedDashboard(dashboard);
314
913
  const dashboardWidgets = await analytics.getWidgets(dashboard.id);
315
- setWidgets(dashboardWidgets);
914
+ setWidgets(dashboardWidgets.length > 0 ? dashboardWidgets : createExampleWidgets(dashboard.id));
316
915
  }, [analytics]);
317
916
  const stats = {
318
917
  totalDashboards: dashboards.length,
@@ -334,7 +933,6 @@ function useAnalyticsData(projectId = "local-project") {
334
933
  }
335
934
 
336
935
  // src/ui/AnalyticsDashboard.tsx
337
- import { useState as useState2 } from "react";
338
936
  import {
339
937
  Button,
340
938
  ErrorState,
@@ -342,33 +940,14 @@ import {
342
940
  StatCard,
343
941
  StatCardGroup
344
942
  } from "@contractspec/lib.design-system";
345
- import { jsxDEV } from "react/jsx-dev-runtime";
943
+ import { useMemo, useState as useState2 } from "react";
944
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
346
945
  "use client";
347
946
  var STATUS_COLORS = {
348
947
  PUBLISHED: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400",
349
948
  DRAFT: "bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400",
350
949
  ARCHIVED: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400"
351
950
  };
352
- var WIDGET_ICONS = {
353
- LINE_CHART: "\uD83D\uDCC8",
354
- BAR_CHART: "\uD83D\uDCCA",
355
- PIE_CHART: "\uD83E\uDD67",
356
- AREA_CHART: "\uD83D\uDCC9",
357
- SCATTER_PLOT: "\u26AC",
358
- METRIC: "\uD83D\uDD22",
359
- TABLE: "\uD83D\uDCCB",
360
- HEATMAP: "\uD83D\uDDFA\uFE0F",
361
- FUNNEL: "\u23EC",
362
- MAP: "\uD83C\uDF0D",
363
- TEXT: "\uD83D\uDCDD",
364
- EMBED: "\uD83D\uDD17"
365
- };
366
- var QUERY_TYPE_COLORS = {
367
- SQL: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400",
368
- METRIC: "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400",
369
- AGGREGATION: "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400",
370
- CUSTOM: "bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400"
371
- };
372
951
  function AnalyticsDashboard() {
373
952
  const [activeTab, setActiveTab] = useState2("dashboards");
374
953
  const {
@@ -386,33 +965,34 @@ function AnalyticsDashboard() {
386
965
  { id: "dashboards", label: "Dashboards", icon: "\uD83D\uDCCA" },
387
966
  { id: "queries", label: "Queries", icon: "\uD83D\uDD0D" }
388
967
  ];
968
+ const resolvedWidgets = useMemo(() => widgets.map((widget2) => resolveAnalyticsWidget(widget2)).filter((widget2) => Boolean(widget2)), [widgets]);
389
969
  if (loading) {
390
- return /* @__PURE__ */ jsxDEV(LoaderBlock, {
970
+ return /* @__PURE__ */ jsxDEV3(LoaderBlock, {
391
971
  label: "Loading Analytics..."
392
972
  }, undefined, false, undefined, this);
393
973
  }
394
974
  if (error) {
395
- return /* @__PURE__ */ jsxDEV(ErrorState, {
975
+ return /* @__PURE__ */ jsxDEV3(ErrorState, {
396
976
  title: "Failed to load Analytics",
397
977
  description: error.message,
398
978
  onRetry: refetch,
399
979
  retryLabel: "Retry"
400
980
  }, undefined, false, undefined, this);
401
981
  }
402
- return /* @__PURE__ */ jsxDEV("div", {
982
+ return /* @__PURE__ */ jsxDEV3("div", {
403
983
  className: "space-y-6",
404
984
  children: [
405
- /* @__PURE__ */ jsxDEV("div", {
985
+ /* @__PURE__ */ jsxDEV3("div", {
406
986
  className: "flex items-center justify-between",
407
987
  children: [
408
- /* @__PURE__ */ jsxDEV("h2", {
409
- className: "text-2xl font-bold",
988
+ /* @__PURE__ */ jsxDEV3("h2", {
989
+ className: "font-bold text-2xl",
410
990
  children: "Analytics Dashboard"
411
991
  }, undefined, false, undefined, this),
412
- /* @__PURE__ */ jsxDEV(Button, {
992
+ /* @__PURE__ */ jsxDEV3(Button, {
413
993
  onClick: () => alert("Create dashboard modal"),
414
994
  children: [
415
- /* @__PURE__ */ jsxDEV("span", {
995
+ /* @__PURE__ */ jsxDEV3("span", {
416
996
  className: "mr-2",
417
997
  children: "+"
418
998
  }, undefined, false, undefined, this),
@@ -421,55 +1001,55 @@ function AnalyticsDashboard() {
421
1001
  }, undefined, true, undefined, this)
422
1002
  ]
423
1003
  }, undefined, true, undefined, this),
424
- /* @__PURE__ */ jsxDEV(StatCardGroup, {
1004
+ /* @__PURE__ */ jsxDEV3(StatCardGroup, {
425
1005
  children: [
426
- /* @__PURE__ */ jsxDEV(StatCard, {
1006
+ /* @__PURE__ */ jsxDEV3(StatCard, {
427
1007
  label: "Dashboards",
428
1008
  value: stats.totalDashboards,
429
1009
  hint: `${stats.publishedDashboards} published`
430
1010
  }, undefined, false, undefined, this),
431
- /* @__PURE__ */ jsxDEV(StatCard, {
1011
+ /* @__PURE__ */ jsxDEV3(StatCard, {
432
1012
  label: "Queries",
433
1013
  value: stats.totalQueries,
434
1014
  hint: `${stats.sharedQueries} shared`
435
1015
  }, undefined, false, undefined, this),
436
- /* @__PURE__ */ jsxDEV(StatCard, {
1016
+ /* @__PURE__ */ jsxDEV3(StatCard, {
437
1017
  label: "Widgets",
438
1018
  value: widgets.length,
439
1019
  hint: "on current dashboard"
440
1020
  }, undefined, false, undefined, this)
441
1021
  ]
442
1022
  }, undefined, true, undefined, this),
443
- /* @__PURE__ */ jsxDEV("nav", {
444
- className: "bg-muted flex gap-1 rounded-lg p-1",
1023
+ /* @__PURE__ */ jsxDEV3("nav", {
1024
+ className: "flex gap-1 rounded-lg bg-muted p-1",
445
1025
  role: "tablist",
446
- children: tabs.map((tab) => /* @__PURE__ */ jsxDEV(Button, {
1026
+ children: tabs.map((tab) => /* @__PURE__ */ jsxDEV3(Button, {
447
1027
  type: "button",
448
1028
  role: "tab",
449
1029
  "aria-selected": activeTab === tab.id,
450
1030
  onClick: () => setActiveTab(tab.id),
451
- className: `flex flex-1 items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-colors ${activeTab === tab.id ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
1031
+ className: `flex flex-1 items-center justify-center gap-2 rounded-md px-4 py-2 font-medium text-sm transition-colors ${activeTab === tab.id ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
452
1032
  children: [
453
- /* @__PURE__ */ jsxDEV("span", {
1033
+ /* @__PURE__ */ jsxDEV3("span", {
454
1034
  children: tab.icon
455
1035
  }, undefined, false, undefined, this),
456
1036
  tab.label
457
1037
  ]
458
1038
  }, tab.id, true, undefined, this))
459
1039
  }, undefined, false, undefined, this),
460
- /* @__PURE__ */ jsxDEV("div", {
1040
+ /* @__PURE__ */ jsxDEV3("div", {
461
1041
  className: "min-h-[400px]",
462
1042
  role: "tabpanel",
463
1043
  children: [
464
- activeTab === "dashboards" && /* @__PURE__ */ jsxDEV("div", {
1044
+ activeTab === "dashboards" && /* @__PURE__ */ jsxDEV3("div", {
465
1045
  className: "space-y-6",
466
1046
  children: [
467
- /* @__PURE__ */ jsxDEV("div", {
1047
+ /* @__PURE__ */ jsxDEV3("div", {
468
1048
  className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
469
1049
  children: [
470
- dashboards.map((dashboard) => /* @__PURE__ */ jsxDEV("div", {
1050
+ dashboards.map((dashboard) => /* @__PURE__ */ jsxDEV3("div", {
471
1051
  onClick: () => selectDashboard(dashboard),
472
- className: `border-border bg-card cursor-pointer rounded-lg border p-4 transition-all ${selectedDashboard?.id === dashboard.id ? "ring-primary ring-2" : "hover:bg-muted/50"}`,
1052
+ className: `cursor-pointer rounded-lg border border-border bg-card p-4 transition-all ${selectedDashboard?.id === dashboard.id ? "ring-2 ring-primary" : "hover:bg-muted/50"}`,
473
1053
  role: "button",
474
1054
  tabIndex: 0,
475
1055
  onKeyDown: (e) => {
@@ -477,33 +1057,33 @@ function AnalyticsDashboard() {
477
1057
  selectDashboard(dashboard);
478
1058
  },
479
1059
  children: [
480
- /* @__PURE__ */ jsxDEV("div", {
1060
+ /* @__PURE__ */ jsxDEV3("div", {
481
1061
  className: "mb-2 flex items-center justify-between",
482
1062
  children: [
483
- /* @__PURE__ */ jsxDEV("h3", {
1063
+ /* @__PURE__ */ jsxDEV3("h3", {
484
1064
  className: "font-medium",
485
1065
  children: dashboard.name
486
1066
  }, undefined, false, undefined, this),
487
- /* @__PURE__ */ jsxDEV("span", {
488
- className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${STATUS_COLORS[dashboard.status] ?? ""}`,
1067
+ /* @__PURE__ */ jsxDEV3("span", {
1068
+ className: `inline-flex rounded-full px-2 py-0.5 font-medium text-xs ${STATUS_COLORS[dashboard.status] ?? ""}`,
489
1069
  children: dashboard.status
490
1070
  }, undefined, false, undefined, this)
491
1071
  ]
492
1072
  }, undefined, true, undefined, this),
493
- /* @__PURE__ */ jsxDEV("p", {
494
- className: "text-muted-foreground mb-3 text-sm",
1073
+ /* @__PURE__ */ jsxDEV3("p", {
1074
+ className: "mb-3 text-muted-foreground text-sm",
495
1075
  children: dashboard.description
496
1076
  }, undefined, false, undefined, this),
497
- /* @__PURE__ */ jsxDEV("div", {
498
- className: "text-muted-foreground flex items-center justify-between text-xs",
1077
+ /* @__PURE__ */ jsxDEV3("div", {
1078
+ className: "flex items-center justify-between text-muted-foreground text-xs",
499
1079
  children: [
500
- /* @__PURE__ */ jsxDEV("span", {
1080
+ /* @__PURE__ */ jsxDEV3("span", {
501
1081
  children: [
502
1082
  "/",
503
1083
  dashboard.slug
504
1084
  ]
505
1085
  }, undefined, true, undefined, this),
506
- dashboard.isPublic && /* @__PURE__ */ jsxDEV("span", {
1086
+ dashboard.isPublic && /* @__PURE__ */ jsxDEV3("span", {
507
1087
  className: "text-green-600 dark:text-green-400",
508
1088
  children: "\uD83C\uDF10 Public"
509
1089
  }, undefined, false, undefined, this)
@@ -511,149 +1091,20 @@ function AnalyticsDashboard() {
511
1091
  }, undefined, true, undefined, this)
512
1092
  ]
513
1093
  }, dashboard.id, true, undefined, this)),
514
- dashboards.length === 0 && /* @__PURE__ */ jsxDEV("div", {
515
- className: "text-muted-foreground col-span-full flex h-64 items-center justify-center",
1094
+ dashboards.length === 0 && /* @__PURE__ */ jsxDEV3("div", {
1095
+ className: "col-span-full flex h-64 items-center justify-center text-muted-foreground",
516
1096
  children: "No dashboards created yet"
517
1097
  }, undefined, false, undefined, this)
518
1098
  ]
519
1099
  }, undefined, true, undefined, this),
520
- selectedDashboard && widgets.length > 0 && /* @__PURE__ */ jsxDEV("div", {
521
- children: [
522
- /* @__PURE__ */ jsxDEV("h3", {
523
- className: "mb-4 text-lg font-semibold",
524
- children: [
525
- 'Widgets in "',
526
- selectedDashboard.name,
527
- '"'
528
- ]
529
- }, undefined, true, undefined, this),
530
- /* @__PURE__ */ jsxDEV("div", {
531
- className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
532
- children: widgets.map((widget) => /* @__PURE__ */ jsxDEV("div", {
533
- className: "border-border bg-card rounded-lg border p-4",
534
- children: [
535
- /* @__PURE__ */ jsxDEV("div", {
536
- className: "mb-2 flex items-center gap-2",
537
- children: [
538
- /* @__PURE__ */ jsxDEV("span", {
539
- className: "text-xl",
540
- children: WIDGET_ICONS[widget.type] ?? "\uD83D\uDCCA"
541
- }, undefined, false, undefined, this),
542
- /* @__PURE__ */ jsxDEV("span", {
543
- className: "font-medium",
544
- children: widget.name
545
- }, undefined, false, undefined, this)
546
- ]
547
- }, undefined, true, undefined, this),
548
- /* @__PURE__ */ jsxDEV("div", {
549
- className: "text-muted-foreground text-sm",
550
- children: widget.type.replace(/_/g, " ")
551
- }, undefined, false, undefined, this),
552
- /* @__PURE__ */ jsxDEV("div", {
553
- className: "text-muted-foreground mt-2 text-xs",
554
- children: [
555
- "Position: (",
556
- widget.gridX,
557
- ", ",
558
- widget.gridY,
559
- ") \u2022",
560
- " ",
561
- widget.gridWidth,
562
- "x",
563
- widget.gridHeight
564
- ]
565
- }, undefined, true, undefined, this)
566
- ]
567
- }, widget.id, true, undefined, this))
568
- }, undefined, false, undefined, this)
569
- ]
570
- }, undefined, true, undefined, this)
1100
+ selectedDashboard ? /* @__PURE__ */ jsxDEV3(AnalyticsWidgetBoard, {
1101
+ dashboardName: selectedDashboard.name,
1102
+ widgets: resolvedWidgets
1103
+ }, undefined, false, undefined, this) : null
571
1104
  ]
572
1105
  }, undefined, true, undefined, this),
573
- activeTab === "queries" && /* @__PURE__ */ jsxDEV("div", {
574
- className: "border-border rounded-lg border",
575
- children: /* @__PURE__ */ jsxDEV("table", {
576
- className: "w-full",
577
- children: [
578
- /* @__PURE__ */ jsxDEV("thead", {
579
- className: "border-border bg-muted/30 border-b",
580
- children: /* @__PURE__ */ jsxDEV("tr", {
581
- children: [
582
- /* @__PURE__ */ jsxDEV("th", {
583
- className: "px-4 py-3 text-left text-sm font-medium",
584
- children: "Query"
585
- }, undefined, false, undefined, this),
586
- /* @__PURE__ */ jsxDEV("th", {
587
- className: "px-4 py-3 text-left text-sm font-medium",
588
- children: "Type"
589
- }, undefined, false, undefined, this),
590
- /* @__PURE__ */ jsxDEV("th", {
591
- className: "px-4 py-3 text-left text-sm font-medium",
592
- children: "Cache TTL"
593
- }, undefined, false, undefined, this),
594
- /* @__PURE__ */ jsxDEV("th", {
595
- className: "px-4 py-3 text-left text-sm font-medium",
596
- children: "Shared"
597
- }, undefined, false, undefined, this)
598
- ]
599
- }, undefined, true, undefined, this)
600
- }, undefined, false, undefined, this),
601
- /* @__PURE__ */ jsxDEV("tbody", {
602
- className: "divide-border divide-y",
603
- children: [
604
- queries.map((query) => /* @__PURE__ */ jsxDEV("tr", {
605
- className: "hover:bg-muted/50",
606
- children: [
607
- /* @__PURE__ */ jsxDEV("td", {
608
- className: "px-4 py-3",
609
- children: [
610
- /* @__PURE__ */ jsxDEV("div", {
611
- className: "font-medium",
612
- children: query.name
613
- }, undefined, false, undefined, this),
614
- /* @__PURE__ */ jsxDEV("div", {
615
- className: "text-muted-foreground text-sm",
616
- children: query.description
617
- }, undefined, false, undefined, this)
618
- ]
619
- }, undefined, true, undefined, this),
620
- /* @__PURE__ */ jsxDEV("td", {
621
- className: "px-4 py-3",
622
- children: /* @__PURE__ */ jsxDEV("span", {
623
- className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${QUERY_TYPE_COLORS[query.type] ?? ""}`,
624
- children: query.type
625
- }, undefined, false, undefined, this)
626
- }, undefined, false, undefined, this),
627
- /* @__PURE__ */ jsxDEV("td", {
628
- className: "text-muted-foreground px-4 py-3 text-sm",
629
- children: [
630
- query.cacheTtlSeconds,
631
- "s"
632
- ]
633
- }, undefined, true, undefined, this),
634
- /* @__PURE__ */ jsxDEV("td", {
635
- className: "px-4 py-3",
636
- children: query.isShared ? /* @__PURE__ */ jsxDEV("span", {
637
- className: "text-green-600 dark:text-green-400",
638
- children: "\u2713"
639
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV("span", {
640
- className: "text-muted-foreground",
641
- children: "\u2014"
642
- }, undefined, false, undefined, this)
643
- }, undefined, false, undefined, this)
644
- ]
645
- }, query.id, true, undefined, this)),
646
- queries.length === 0 && /* @__PURE__ */ jsxDEV("tr", {
647
- children: /* @__PURE__ */ jsxDEV("td", {
648
- colSpan: 4,
649
- className: "text-muted-foreground px-4 py-8 text-center",
650
- children: "No queries saved"
651
- }, undefined, false, undefined, this)
652
- }, undefined, false, undefined, this)
653
- ]
654
- }, undefined, true, undefined, this)
655
- ]
656
- }, undefined, true, undefined, this)
1106
+ activeTab === "queries" && /* @__PURE__ */ jsxDEV3(AnalyticsQueriesTable, {
1107
+ queries
657
1108
  }, undefined, false, undefined, this)
658
1109
  ]
659
1110
  }, undefined, true, undefined, this)
@@ -663,6 +1114,168 @@ function AnalyticsDashboard() {
663
1114
 
664
1115
  // src/ui/hooks/index.ts
665
1116
  "use client";
1117
+
1118
+ // src/ui/renderers/analytics.markdown.ts
1119
+ var mockDashboards = [
1120
+ {
1121
+ id: "dash-1",
1122
+ name: "Sales Overview",
1123
+ slug: "sales-overview",
1124
+ status: "PUBLISHED",
1125
+ widgetCount: 11,
1126
+ viewCount: 1250,
1127
+ lastViewedAt: "2026-03-18T12:00:00Z"
1128
+ },
1129
+ {
1130
+ id: "dash-2",
1131
+ name: "User Engagement",
1132
+ slug: "user-engagement",
1133
+ status: "PUBLISHED",
1134
+ widgetCount: 8,
1135
+ viewCount: 890,
1136
+ lastViewedAt: "2026-03-18T10:00:00Z"
1137
+ }
1138
+ ];
1139
+ var mockQueries = [
1140
+ {
1141
+ id: "q-1",
1142
+ name: "Monthly Revenue",
1143
+ type: "AGGREGATION",
1144
+ isShared: true,
1145
+ executionCount: 1500
1146
+ },
1147
+ {
1148
+ id: "q-2",
1149
+ name: "User Growth",
1150
+ type: "METRIC",
1151
+ isShared: true,
1152
+ executionCount: 890
1153
+ },
1154
+ {
1155
+ id: "q-3",
1156
+ name: "Product Sales",
1157
+ type: "SQL",
1158
+ isShared: false,
1159
+ executionCount: 340
1160
+ },
1161
+ {
1162
+ id: "q-4",
1163
+ name: "Conversion Funnel",
1164
+ type: "AGGREGATION",
1165
+ isShared: true,
1166
+ executionCount: 450
1167
+ }
1168
+ ];
1169
+ function dashboardWidgets(dashboardId) {
1170
+ return createExampleWidgets(dashboardId).map((widget2) => resolveAnalyticsWidget(widget2)).filter((widget2) => Boolean(widget2));
1171
+ }
1172
+ var analyticsDashboardMarkdownRenderer = {
1173
+ target: "markdown",
1174
+ render: async (desc) => {
1175
+ if (desc.source.type !== "component" || desc.source.componentKey !== "AnalyticsDashboard") {
1176
+ throw new Error("analyticsDashboardMarkdownRenderer: not AnalyticsDashboard");
1177
+ }
1178
+ const dashboard = mockDashboards[0];
1179
+ if (!dashboard) {
1180
+ return {
1181
+ mimeType: "text/markdown",
1182
+ body: `# No Dashboards
1183
+
1184
+ No dashboards available.`
1185
+ };
1186
+ }
1187
+ const widgets = dashboardWidgets(dashboard.id);
1188
+ const metricWidgets = widgets.filter((widget2) => widget2.bindings[0]?.spec.visualization.kind === "metric");
1189
+ const lines = [
1190
+ `# ${dashboard.name}`,
1191
+ "",
1192
+ "> Contract-backed analytics dashboard overview.",
1193
+ "",
1194
+ "## Key Metrics",
1195
+ ""
1196
+ ];
1197
+ for (const widget2 of metricWidgets) {
1198
+ const binding2 = widget2.bindings[0];
1199
+ if (!binding2)
1200
+ continue;
1201
+ lines.push(`- **${widget2.name}** via \`${binding2.spec.meta.key}\``);
1202
+ }
1203
+ lines.push("");
1204
+ lines.push("## Visual Blocks");
1205
+ lines.push("");
1206
+ for (const widget2 of widgets) {
1207
+ const kinds = widget2.bindings.map((binding2) => binding2.spec.visualization.kind).join(", ");
1208
+ lines.push(`- **${widget2.name}** (${widget2.layout}) \u2192 ${kinds}`);
1209
+ }
1210
+ lines.push("");
1211
+ lines.push("## Dashboard Stats");
1212
+ lines.push("");
1213
+ lines.push("| Metric | Value |");
1214
+ lines.push("|--------|-------|");
1215
+ lines.push(`| Total Dashboards | ${mockDashboards.length} |`);
1216
+ lines.push(`| Published | ${mockDashboards.filter((item) => item.status === "PUBLISHED").length} |`);
1217
+ lines.push(`| Shared Queries | ${mockQueries.filter((query) => query.isShared).length} |`);
1218
+ return {
1219
+ mimeType: "text/markdown",
1220
+ body: lines.join(`
1221
+ `)
1222
+ };
1223
+ }
1224
+ };
1225
+ var dashboardListMarkdownRenderer = {
1226
+ target: "markdown",
1227
+ render: async (desc) => {
1228
+ if (desc.source.type !== "component" || desc.source.componentKey !== "DashboardList") {
1229
+ throw new Error("dashboardListMarkdownRenderer: not DashboardList");
1230
+ }
1231
+ const lines = [
1232
+ "# Dashboards",
1233
+ "",
1234
+ "> Browse and manage analytics dashboards",
1235
+ "",
1236
+ "| Dashboard | Widgets | Views | Status | Last Viewed |",
1237
+ "|-----------|---------|-------|--------|-------------|"
1238
+ ];
1239
+ for (const dashboard of mockDashboards) {
1240
+ const lastViewed = dashboard.lastViewedAt ? new Date(dashboard.lastViewedAt).toLocaleDateString() : "Never";
1241
+ const statusIcon = dashboard.status === "PUBLISHED" ? "\uD83D\uDFE2" : "\u26AB";
1242
+ lines.push(`| [${dashboard.name}](/dashboards/${dashboard.slug}) | ${dashboard.widgetCount} | ${dashboard.viewCount.toLocaleString()} | ${statusIcon} ${dashboard.status} | ${lastViewed} |`);
1243
+ }
1244
+ return {
1245
+ mimeType: "text/markdown",
1246
+ body: lines.join(`
1247
+ `)
1248
+ };
1249
+ }
1250
+ };
1251
+ var queryBuilderMarkdownRenderer = {
1252
+ target: "markdown",
1253
+ render: async (desc) => {
1254
+ if (desc.source.type !== "component" || desc.source.componentKey !== "QueryBuilder") {
1255
+ throw new Error("queryBuilderMarkdownRenderer: not QueryBuilder");
1256
+ }
1257
+ const lines = [
1258
+ "# Query Builder",
1259
+ "",
1260
+ "> Create and manage reusable data queries.",
1261
+ "",
1262
+ "| Query | Type | Shared | Executions |",
1263
+ "|-------|------|--------|------------|"
1264
+ ];
1265
+ for (const query of mockQueries) {
1266
+ lines.push(`| ${query.name} | ${query.type} | ${query.isShared ? "\uD83C\uDF10" : "\uD83D\uDD12"} | ${query.executionCount.toLocaleString()} |`);
1267
+ }
1268
+ lines.push("");
1269
+ lines.push("## Visualization Contracts");
1270
+ lines.push("");
1271
+ lines.push("Widgets reference `VisualizationSpec` contracts instead of rendering ad-hoc widget types.");
1272
+ return {
1273
+ mimeType: "text/markdown",
1274
+ body: lines.join(`
1275
+ `)
1276
+ };
1277
+ }
1278
+ };
666
1279
  export {
667
1280
  useAnalyticsData,
668
1281
  queryBuilderMarkdownRenderer,