@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
@@ -1,7 +1,876 @@
1
1
  // @bun
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."
20
+ },
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."
46
+ },
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."
85
+ },
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." }
97
+ }
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."
107
+ },
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."
155
+ },
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."
190
+ },
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."
223
+ },
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."
253
+ },
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." }
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
341
+ ];
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
+ ]
359
+ },
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
+ ]
368
+ },
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
+ ]
376
+ },
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
+ ]
442
+ }
443
+ };
444
+ function refOf(spec) {
445
+ return { key: spec.meta.key, version: spec.meta.version };
446
+ }
447
+ function visualizationRefKey(ref) {
448
+ return `${ref.key}.v${ref.version}`;
449
+ }
450
+
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)
462
+ };
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;
526
+ return {
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
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;
552
+ }
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);
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"
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
+
2
871
  // src/ui/hooks/useAnalyticsData.ts
3
- import { useCallback, useEffect, useState } from "react";
4
872
  import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
873
+ import { useCallback, useEffect, useState } from "react";
5
874
  "use client";
6
875
  function useAnalyticsData(projectId = "local-project") {
7
876
  const { handlers } = useTemplateRuntime();
@@ -27,7 +896,7 @@ function useAnalyticsData(projectId = "local-project") {
27
896
  if (first) {
28
897
  setSelectedDashboard(first);
29
898
  const dashboardWidgets = await analytics.getWidgets(first.id);
30
- setWidgets(dashboardWidgets);
899
+ setWidgets(dashboardWidgets.length > 0 ? dashboardWidgets : createExampleWidgets(first.id));
31
900
  }
32
901
  }
33
902
  } catch (err) {
@@ -42,7 +911,7 @@ function useAnalyticsData(projectId = "local-project") {
42
911
  const selectDashboard = useCallback(async (dashboard) => {
43
912
  setSelectedDashboard(dashboard);
44
913
  const dashboardWidgets = await analytics.getWidgets(dashboard.id);
45
- setWidgets(dashboardWidgets);
914
+ setWidgets(dashboardWidgets.length > 0 ? dashboardWidgets : createExampleWidgets(dashboard.id));
46
915
  }, [analytics]);
47
916
  const stats = {
48
917
  totalDashboards: dashboards.length,
@@ -64,7 +933,6 @@ function useAnalyticsData(projectId = "local-project") {
64
933
  }
65
934
 
66
935
  // src/ui/AnalyticsDashboard.tsx
67
- import { useState as useState2 } from "react";
68
936
  import {
69
937
  Button,
70
938
  ErrorState,
@@ -72,33 +940,14 @@ import {
72
940
  StatCard,
73
941
  StatCardGroup
74
942
  } from "@contractspec/lib.design-system";
75
- 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";
76
945
  "use client";
77
946
  var STATUS_COLORS = {
78
947
  PUBLISHED: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400",
79
948
  DRAFT: "bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400",
80
949
  ARCHIVED: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400"
81
950
  };
82
- var WIDGET_ICONS = {
83
- LINE_CHART: "\uD83D\uDCC8",
84
- BAR_CHART: "\uD83D\uDCCA",
85
- PIE_CHART: "\uD83E\uDD67",
86
- AREA_CHART: "\uD83D\uDCC9",
87
- SCATTER_PLOT: "\u26AC",
88
- METRIC: "\uD83D\uDD22",
89
- TABLE: "\uD83D\uDCCB",
90
- HEATMAP: "\uD83D\uDDFA\uFE0F",
91
- FUNNEL: "\u23EC",
92
- MAP: "\uD83C\uDF0D",
93
- TEXT: "\uD83D\uDCDD",
94
- EMBED: "\uD83D\uDD17"
95
- };
96
- var QUERY_TYPE_COLORS = {
97
- SQL: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400",
98
- METRIC: "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400",
99
- AGGREGATION: "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400",
100
- CUSTOM: "bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400"
101
- };
102
951
  function AnalyticsDashboard() {
103
952
  const [activeTab, setActiveTab] = useState2("dashboards");
104
953
  const {
@@ -116,33 +965,34 @@ function AnalyticsDashboard() {
116
965
  { id: "dashboards", label: "Dashboards", icon: "\uD83D\uDCCA" },
117
966
  { id: "queries", label: "Queries", icon: "\uD83D\uDD0D" }
118
967
  ];
968
+ const resolvedWidgets = useMemo(() => widgets.map((widget2) => resolveAnalyticsWidget(widget2)).filter((widget2) => Boolean(widget2)), [widgets]);
119
969
  if (loading) {
120
- return /* @__PURE__ */ jsxDEV(LoaderBlock, {
970
+ return /* @__PURE__ */ jsxDEV3(LoaderBlock, {
121
971
  label: "Loading Analytics..."
122
972
  }, undefined, false, undefined, this);
123
973
  }
124
974
  if (error) {
125
- return /* @__PURE__ */ jsxDEV(ErrorState, {
975
+ return /* @__PURE__ */ jsxDEV3(ErrorState, {
126
976
  title: "Failed to load Analytics",
127
977
  description: error.message,
128
978
  onRetry: refetch,
129
979
  retryLabel: "Retry"
130
980
  }, undefined, false, undefined, this);
131
981
  }
132
- return /* @__PURE__ */ jsxDEV("div", {
982
+ return /* @__PURE__ */ jsxDEV3("div", {
133
983
  className: "space-y-6",
134
984
  children: [
135
- /* @__PURE__ */ jsxDEV("div", {
985
+ /* @__PURE__ */ jsxDEV3("div", {
136
986
  className: "flex items-center justify-between",
137
987
  children: [
138
- /* @__PURE__ */ jsxDEV("h2", {
139
- className: "text-2xl font-bold",
988
+ /* @__PURE__ */ jsxDEV3("h2", {
989
+ className: "font-bold text-2xl",
140
990
  children: "Analytics Dashboard"
141
991
  }, undefined, false, undefined, this),
142
- /* @__PURE__ */ jsxDEV(Button, {
992
+ /* @__PURE__ */ jsxDEV3(Button, {
143
993
  onClick: () => alert("Create dashboard modal"),
144
994
  children: [
145
- /* @__PURE__ */ jsxDEV("span", {
995
+ /* @__PURE__ */ jsxDEV3("span", {
146
996
  className: "mr-2",
147
997
  children: "+"
148
998
  }, undefined, false, undefined, this),
@@ -151,55 +1001,55 @@ function AnalyticsDashboard() {
151
1001
  }, undefined, true, undefined, this)
152
1002
  ]
153
1003
  }, undefined, true, undefined, this),
154
- /* @__PURE__ */ jsxDEV(StatCardGroup, {
1004
+ /* @__PURE__ */ jsxDEV3(StatCardGroup, {
155
1005
  children: [
156
- /* @__PURE__ */ jsxDEV(StatCard, {
1006
+ /* @__PURE__ */ jsxDEV3(StatCard, {
157
1007
  label: "Dashboards",
158
1008
  value: stats.totalDashboards,
159
1009
  hint: `${stats.publishedDashboards} published`
160
1010
  }, undefined, false, undefined, this),
161
- /* @__PURE__ */ jsxDEV(StatCard, {
1011
+ /* @__PURE__ */ jsxDEV3(StatCard, {
162
1012
  label: "Queries",
163
1013
  value: stats.totalQueries,
164
1014
  hint: `${stats.sharedQueries} shared`
165
1015
  }, undefined, false, undefined, this),
166
- /* @__PURE__ */ jsxDEV(StatCard, {
1016
+ /* @__PURE__ */ jsxDEV3(StatCard, {
167
1017
  label: "Widgets",
168
1018
  value: widgets.length,
169
1019
  hint: "on current dashboard"
170
1020
  }, undefined, false, undefined, this)
171
1021
  ]
172
1022
  }, undefined, true, undefined, this),
173
- /* @__PURE__ */ jsxDEV("nav", {
174
- 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",
175
1025
  role: "tablist",
176
- children: tabs.map((tab) => /* @__PURE__ */ jsxDEV(Button, {
1026
+ children: tabs.map((tab) => /* @__PURE__ */ jsxDEV3(Button, {
177
1027
  type: "button",
178
1028
  role: "tab",
179
1029
  "aria-selected": activeTab === tab.id,
180
1030
  onClick: () => setActiveTab(tab.id),
181
- 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"}`,
182
1032
  children: [
183
- /* @__PURE__ */ jsxDEV("span", {
1033
+ /* @__PURE__ */ jsxDEV3("span", {
184
1034
  children: tab.icon
185
1035
  }, undefined, false, undefined, this),
186
1036
  tab.label
187
1037
  ]
188
1038
  }, tab.id, true, undefined, this))
189
1039
  }, undefined, false, undefined, this),
190
- /* @__PURE__ */ jsxDEV("div", {
1040
+ /* @__PURE__ */ jsxDEV3("div", {
191
1041
  className: "min-h-[400px]",
192
1042
  role: "tabpanel",
193
1043
  children: [
194
- activeTab === "dashboards" && /* @__PURE__ */ jsxDEV("div", {
1044
+ activeTab === "dashboards" && /* @__PURE__ */ jsxDEV3("div", {
195
1045
  className: "space-y-6",
196
1046
  children: [
197
- /* @__PURE__ */ jsxDEV("div", {
1047
+ /* @__PURE__ */ jsxDEV3("div", {
198
1048
  className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
199
1049
  children: [
200
- dashboards.map((dashboard) => /* @__PURE__ */ jsxDEV("div", {
1050
+ dashboards.map((dashboard) => /* @__PURE__ */ jsxDEV3("div", {
201
1051
  onClick: () => selectDashboard(dashboard),
202
- 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"}`,
203
1053
  role: "button",
204
1054
  tabIndex: 0,
205
1055
  onKeyDown: (e) => {
@@ -207,33 +1057,33 @@ function AnalyticsDashboard() {
207
1057
  selectDashboard(dashboard);
208
1058
  },
209
1059
  children: [
210
- /* @__PURE__ */ jsxDEV("div", {
1060
+ /* @__PURE__ */ jsxDEV3("div", {
211
1061
  className: "mb-2 flex items-center justify-between",
212
1062
  children: [
213
- /* @__PURE__ */ jsxDEV("h3", {
1063
+ /* @__PURE__ */ jsxDEV3("h3", {
214
1064
  className: "font-medium",
215
1065
  children: dashboard.name
216
1066
  }, undefined, false, undefined, this),
217
- /* @__PURE__ */ jsxDEV("span", {
218
- 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] ?? ""}`,
219
1069
  children: dashboard.status
220
1070
  }, undefined, false, undefined, this)
221
1071
  ]
222
1072
  }, undefined, true, undefined, this),
223
- /* @__PURE__ */ jsxDEV("p", {
224
- className: "text-muted-foreground mb-3 text-sm",
1073
+ /* @__PURE__ */ jsxDEV3("p", {
1074
+ className: "mb-3 text-muted-foreground text-sm",
225
1075
  children: dashboard.description
226
1076
  }, undefined, false, undefined, this),
227
- /* @__PURE__ */ jsxDEV("div", {
228
- 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",
229
1079
  children: [
230
- /* @__PURE__ */ jsxDEV("span", {
1080
+ /* @__PURE__ */ jsxDEV3("span", {
231
1081
  children: [
232
1082
  "/",
233
1083
  dashboard.slug
234
1084
  ]
235
1085
  }, undefined, true, undefined, this),
236
- dashboard.isPublic && /* @__PURE__ */ jsxDEV("span", {
1086
+ dashboard.isPublic && /* @__PURE__ */ jsxDEV3("span", {
237
1087
  className: "text-green-600 dark:text-green-400",
238
1088
  children: "\uD83C\uDF10 Public"
239
1089
  }, undefined, false, undefined, this)
@@ -241,149 +1091,20 @@ function AnalyticsDashboard() {
241
1091
  }, undefined, true, undefined, this)
242
1092
  ]
243
1093
  }, dashboard.id, true, undefined, this)),
244
- dashboards.length === 0 && /* @__PURE__ */ jsxDEV("div", {
245
- 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",
246
1096
  children: "No dashboards created yet"
247
1097
  }, undefined, false, undefined, this)
248
1098
  ]
249
1099
  }, undefined, true, undefined, this),
250
- selectedDashboard && widgets.length > 0 && /* @__PURE__ */ jsxDEV("div", {
251
- children: [
252
- /* @__PURE__ */ jsxDEV("h3", {
253
- className: "mb-4 text-lg font-semibold",
254
- children: [
255
- 'Widgets in "',
256
- selectedDashboard.name,
257
- '"'
258
- ]
259
- }, undefined, true, undefined, this),
260
- /* @__PURE__ */ jsxDEV("div", {
261
- className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
262
- children: widgets.map((widget) => /* @__PURE__ */ jsxDEV("div", {
263
- className: "border-border bg-card rounded-lg border p-4",
264
- children: [
265
- /* @__PURE__ */ jsxDEV("div", {
266
- className: "mb-2 flex items-center gap-2",
267
- children: [
268
- /* @__PURE__ */ jsxDEV("span", {
269
- className: "text-xl",
270
- children: WIDGET_ICONS[widget.type] ?? "\uD83D\uDCCA"
271
- }, undefined, false, undefined, this),
272
- /* @__PURE__ */ jsxDEV("span", {
273
- className: "font-medium",
274
- children: widget.name
275
- }, undefined, false, undefined, this)
276
- ]
277
- }, undefined, true, undefined, this),
278
- /* @__PURE__ */ jsxDEV("div", {
279
- className: "text-muted-foreground text-sm",
280
- children: widget.type.replace(/_/g, " ")
281
- }, undefined, false, undefined, this),
282
- /* @__PURE__ */ jsxDEV("div", {
283
- className: "text-muted-foreground mt-2 text-xs",
284
- children: [
285
- "Position: (",
286
- widget.gridX,
287
- ", ",
288
- widget.gridY,
289
- ") \u2022",
290
- " ",
291
- widget.gridWidth,
292
- "x",
293
- widget.gridHeight
294
- ]
295
- }, undefined, true, undefined, this)
296
- ]
297
- }, widget.id, true, undefined, this))
298
- }, undefined, false, undefined, this)
299
- ]
300
- }, undefined, true, undefined, this)
1100
+ selectedDashboard ? /* @__PURE__ */ jsxDEV3(AnalyticsWidgetBoard, {
1101
+ dashboardName: selectedDashboard.name,
1102
+ widgets: resolvedWidgets
1103
+ }, undefined, false, undefined, this) : null
301
1104
  ]
302
1105
  }, undefined, true, undefined, this),
303
- activeTab === "queries" && /* @__PURE__ */ jsxDEV("div", {
304
- className: "border-border rounded-lg border",
305
- children: /* @__PURE__ */ jsxDEV("table", {
306
- className: "w-full",
307
- children: [
308
- /* @__PURE__ */ jsxDEV("thead", {
309
- className: "border-border bg-muted/30 border-b",
310
- children: /* @__PURE__ */ jsxDEV("tr", {
311
- children: [
312
- /* @__PURE__ */ jsxDEV("th", {
313
- className: "px-4 py-3 text-left text-sm font-medium",
314
- children: "Query"
315
- }, undefined, false, undefined, this),
316
- /* @__PURE__ */ jsxDEV("th", {
317
- className: "px-4 py-3 text-left text-sm font-medium",
318
- children: "Type"
319
- }, undefined, false, undefined, this),
320
- /* @__PURE__ */ jsxDEV("th", {
321
- className: "px-4 py-3 text-left text-sm font-medium",
322
- children: "Cache TTL"
323
- }, undefined, false, undefined, this),
324
- /* @__PURE__ */ jsxDEV("th", {
325
- className: "px-4 py-3 text-left text-sm font-medium",
326
- children: "Shared"
327
- }, undefined, false, undefined, this)
328
- ]
329
- }, undefined, true, undefined, this)
330
- }, undefined, false, undefined, this),
331
- /* @__PURE__ */ jsxDEV("tbody", {
332
- className: "divide-border divide-y",
333
- children: [
334
- queries.map((query) => /* @__PURE__ */ jsxDEV("tr", {
335
- className: "hover:bg-muted/50",
336
- children: [
337
- /* @__PURE__ */ jsxDEV("td", {
338
- className: "px-4 py-3",
339
- children: [
340
- /* @__PURE__ */ jsxDEV("div", {
341
- className: "font-medium",
342
- children: query.name
343
- }, undefined, false, undefined, this),
344
- /* @__PURE__ */ jsxDEV("div", {
345
- className: "text-muted-foreground text-sm",
346
- children: query.description
347
- }, undefined, false, undefined, this)
348
- ]
349
- }, undefined, true, undefined, this),
350
- /* @__PURE__ */ jsxDEV("td", {
351
- className: "px-4 py-3",
352
- children: /* @__PURE__ */ jsxDEV("span", {
353
- className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${QUERY_TYPE_COLORS[query.type] ?? ""}`,
354
- children: query.type
355
- }, undefined, false, undefined, this)
356
- }, undefined, false, undefined, this),
357
- /* @__PURE__ */ jsxDEV("td", {
358
- className: "text-muted-foreground px-4 py-3 text-sm",
359
- children: [
360
- query.cacheTtlSeconds,
361
- "s"
362
- ]
363
- }, undefined, true, undefined, this),
364
- /* @__PURE__ */ jsxDEV("td", {
365
- className: "px-4 py-3",
366
- children: query.isShared ? /* @__PURE__ */ jsxDEV("span", {
367
- className: "text-green-600 dark:text-green-400",
368
- children: "\u2713"
369
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV("span", {
370
- className: "text-muted-foreground",
371
- children: "\u2014"
372
- }, undefined, false, undefined, this)
373
- }, undefined, false, undefined, this)
374
- ]
375
- }, query.id, true, undefined, this)),
376
- queries.length === 0 && /* @__PURE__ */ jsxDEV("tr", {
377
- children: /* @__PURE__ */ jsxDEV("td", {
378
- colSpan: 4,
379
- className: "text-muted-foreground px-4 py-8 text-center",
380
- children: "No queries saved"
381
- }, undefined, false, undefined, this)
382
- }, undefined, false, undefined, this)
383
- ]
384
- }, undefined, true, undefined, this)
385
- ]
386
- }, undefined, true, undefined, this)
1106
+ activeTab === "queries" && /* @__PURE__ */ jsxDEV3(AnalyticsQueriesTable, {
1107
+ queries
387
1108
  }, undefined, false, undefined, this)
388
1109
  ]
389
1110
  }, undefined, true, undefined, this)