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