@stacksjs/ts-analytics 0.1.6

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 (237) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +361 -0
  4. package/bin/cli.ts +169 -0
  5. package/dist/Analytics.d.ts +558 -0
  6. package/dist/api.d.ts +109 -0
  7. package/dist/batching.d.ts +93 -0
  8. package/dist/chunk-2mx7fq49.js +4 -0
  9. package/dist/chunk-3z29508k.js +204 -0
  10. package/dist/chunk-deephkz6.js +56 -0
  11. package/dist/chunk-j261vgyp.js +305 -0
  12. package/dist/chunk-xga17tz7.js +207 -0
  13. package/dist/config.d.ts +132 -0
  14. package/dist/dashboard/components/index.d.ts +80 -0
  15. package/dist/dashboard/composables/useAnalytics.d.ts +99 -0
  16. package/dist/dashboard/index.d.ts +110 -0
  17. package/dist/dashboard/types/index.d.ts +144 -0
  18. package/dist/dashboard/utils/index.d.ts +81 -0
  19. package/dist/dynamodb.d.ts +86 -0
  20. package/dist/funnels.d.ts +104 -0
  21. package/dist/geolocation.d.ts +96 -0
  22. package/dist/index.d.ts +371 -0
  23. package/dist/index.js +10211 -0
  24. package/dist/infrastructure/cdk.d.ts +60 -0
  25. package/dist/infrastructure/cloudformation.d.ts +44 -0
  26. package/dist/infrastructure/index.d.ts +45 -0
  27. package/dist/infrastructure/setup.d.ts +133 -0
  28. package/dist/integrations/cloudflare.d.ts +76 -0
  29. package/dist/integrations/hono.d.ts +60 -0
  30. package/dist/integrations/index.d.ts +24 -0
  31. package/dist/integrations/nuxt.d.ts +7 -0
  32. package/dist/integrations/nuxt.js +42 -0
  33. package/dist/integrations/runtime/use-ts-analytics.d.ts +18 -0
  34. package/dist/integrations/runtime/use-ts-analytics.js +17 -0
  35. package/dist/integrations/stx.d.ts +88 -0
  36. package/dist/integrations/stx.js +18 -0
  37. package/dist/lib/crypto-random.d.ts +4 -0
  38. package/dist/lib/salt.d.ts +16 -0
  39. package/dist/local.d.ts +56 -0
  40. package/dist/model-connector.d.ts +145 -0
  41. package/dist/models/AggregatedStats.d.ts +9 -0
  42. package/dist/models/CampaignStats.d.ts +9 -0
  43. package/dist/models/Conversion.d.ts +11 -0
  44. package/dist/models/CustomEvent.d.ts +11 -0
  45. package/dist/models/DeviceStats.d.ts +9 -0
  46. package/dist/models/EventStats.d.ts +9 -0
  47. package/dist/models/GeoStats.d.ts +9 -0
  48. package/dist/models/Goal.d.ts +9 -0
  49. package/dist/models/GoalStats.d.ts +9 -0
  50. package/dist/models/PageStats.d.ts +11 -0
  51. package/dist/models/PageView.d.ts +13 -0
  52. package/dist/models/RealtimeStats.d.ts +9 -0
  53. package/dist/models/ReferrerStats.d.ts +9 -0
  54. package/dist/models/Session.d.ts +11 -0
  55. package/dist/models/Site.d.ts +11 -0
  56. package/dist/models/index.d.ts +28 -0
  57. package/dist/models/types.d.ts +60 -0
  58. package/dist/sqs-buffering.d.ts +243 -0
  59. package/dist/stacks-integration.d.ts +159 -0
  60. package/dist/tracking-script.d.ts +71 -0
  61. package/dist/tracking.d.ts +17 -0
  62. package/dist/tracking.js +26 -0
  63. package/dist/types.d.ts +595 -0
  64. package/dist/utils/geolocation.d.ts +111 -0
  65. package/dist/utils/user-agent.d.ts +17 -0
  66. package/dist/version.d.ts +7 -0
  67. package/package.json +119 -0
  68. package/src/Analytics.ts +3349 -0
  69. package/src/api.ts +1286 -0
  70. package/src/assets/crosswind.css +2220 -0
  71. package/src/batching.ts +452 -0
  72. package/src/components/dashboard/index.ts +18 -0
  73. package/src/config.ts +456 -0
  74. package/src/dashboard/Dashboard.stx +1517 -0
  75. package/src/dashboard/components/AlertCard.stx +177 -0
  76. package/src/dashboard/components/AnalyticsDashboard.stx +354 -0
  77. package/src/dashboard/components/AnimatedNumber.stx +86 -0
  78. package/src/dashboard/components/BarChart.stx +220 -0
  79. package/src/dashboard/components/BrowserBreakdown.stx +98 -0
  80. package/src/dashboard/components/BrowsersTable.stx +125 -0
  81. package/src/dashboard/components/CampaignBreakdown.stx +163 -0
  82. package/src/dashboard/components/CampaignTable.stx +238 -0
  83. package/src/dashboard/components/CountryList.stx +101 -0
  84. package/src/dashboard/components/DataTable.stx +226 -0
  85. package/src/dashboard/components/DateRangePicker.stx +77 -0
  86. package/src/dashboard/components/DeviceBreakdown.stx +94 -0
  87. package/src/dashboard/components/DevicesTable.stx +163 -0
  88. package/src/dashboard/components/DonutChart.stories.ts +55 -0
  89. package/src/dashboard/components/DonutChart.stx +157 -0
  90. package/src/dashboard/components/EmptyState.stx +90 -0
  91. package/src/dashboard/components/EngagementMetrics.stx +183 -0
  92. package/src/dashboard/components/EventsSection.stx +192 -0
  93. package/src/dashboard/components/FilterBar.stx +104 -0
  94. package/src/dashboard/components/FullAnalyticsDashboard.stx +455 -0
  95. package/src/dashboard/components/FunnelChart.stx +142 -0
  96. package/src/dashboard/components/GeoTable.stx +337 -0
  97. package/src/dashboard/components/GoalsPanel.stx +109 -0
  98. package/src/dashboard/components/Header.stx +39 -0
  99. package/src/dashboard/components/HeatmapChart.stx +140 -0
  100. package/src/dashboard/components/LiveActivityFeed.stx +172 -0
  101. package/src/dashboard/components/MetricComparison.stx +106 -0
  102. package/src/dashboard/components/MiniStats.stx +96 -0
  103. package/src/dashboard/components/OSBreakdown.stx +124 -0
  104. package/src/dashboard/components/PageDetailCard.stx +102 -0
  105. package/src/dashboard/components/PagesTable.stx +127 -0
  106. package/src/dashboard/components/ProgressRing.stx +89 -0
  107. package/src/dashboard/components/RealtimeCounter.stories.ts +45 -0
  108. package/src/dashboard/components/RealtimeCounter.stx +46 -0
  109. package/src/dashboard/components/ReferrersTable.stx +180 -0
  110. package/src/dashboard/components/SparklineChart.stx +160 -0
  111. package/src/dashboard/components/StatCard.stories.ts +58 -0
  112. package/src/dashboard/components/StatCard.stx +90 -0
  113. package/src/dashboard/components/SummaryStats.stx +81 -0
  114. package/src/dashboard/components/TabNav.stx +66 -0
  115. package/src/dashboard/components/ThemeSwitcher.stx +124 -0
  116. package/src/dashboard/components/TimeSeriesChart.stx +106 -0
  117. package/src/dashboard/components/TopList.stories.ts +58 -0
  118. package/src/dashboard/components/TopList.stx +74 -0
  119. package/src/dashboard/components/TrendIndicator.stx +84 -0
  120. package/src/dashboard/components/heatmap/ClickHeatmap.stx +264 -0
  121. package/src/dashboard/components/heatmap/ElementClickList.stx +125 -0
  122. package/src/dashboard/components/heatmap/HeatmapControls.stx +125 -0
  123. package/src/dashboard/components/heatmap/HeatmapLegend.stx +69 -0
  124. package/src/dashboard/components/heatmap/PageHeatmap.stx +264 -0
  125. package/src/dashboard/components/heatmap/ScrollHeatmap.stx +127 -0
  126. package/src/dashboard/components/heatmap/index.ts +12 -0
  127. package/src/dashboard/components/index.ts +86 -0
  128. package/src/dashboard/composables/useAnalytics.ts +465 -0
  129. package/src/dashboard/demo/DemoApp.stx +370 -0
  130. package/src/dashboard/demo/index.ts +8 -0
  131. package/src/dashboard/demo/mockData.ts +234 -0
  132. package/src/dashboard/index.ts +117 -0
  133. package/src/dashboard/stx-shim.d.ts +4 -0
  134. package/src/dashboard/types/index.ts +203 -0
  135. package/src/dashboard/utils/index.ts +426 -0
  136. package/src/dynamodb.ts +344 -0
  137. package/src/funnels.ts +534 -0
  138. package/src/geolocation.ts +515 -0
  139. package/src/handlers/alerts.ts +328 -0
  140. package/src/handlers/annotations.ts +128 -0
  141. package/src/handlers/api-keys.ts +240 -0
  142. package/src/handlers/auth.ts +1020 -0
  143. package/src/handlers/authz.ts +137 -0
  144. package/src/handlers/collect.ts +724 -0
  145. package/src/handlers/data.ts +625 -0
  146. package/src/handlers/experiments.ts +138 -0
  147. package/src/handlers/funnels.ts +216 -0
  148. package/src/handlers/goals.ts +218 -0
  149. package/src/handlers/heatmaps.ts +272 -0
  150. package/src/handlers/index.ts +56 -0
  151. package/src/handlers/lib/read-cache.ts +63 -0
  152. package/src/handlers/misc.ts +486 -0
  153. package/src/handlers/oauth.ts +233 -0
  154. package/src/handlers/performance.ts +388 -0
  155. package/src/handlers/sessions.ts +413 -0
  156. package/src/handlers/sharing.ts +198 -0
  157. package/src/handlers/stats.ts +1368 -0
  158. package/src/handlers/team.ts +161 -0
  159. package/src/handlers/uptime.ts +283 -0
  160. package/src/handlers/views.ts +390 -0
  161. package/src/handlers/webhooks.ts +226 -0
  162. package/src/heatmap/index.ts +32 -0
  163. package/src/heatmap/tracking-script.ts +452 -0
  164. package/src/heatmap/types.ts +79 -0
  165. package/src/index.ts +387 -0
  166. package/src/infrastructure/cdk.ts +496 -0
  167. package/src/infrastructure/cloudformation.ts +595 -0
  168. package/src/infrastructure/index.ts +48 -0
  169. package/src/infrastructure/setup.ts +611 -0
  170. package/src/integrations/cloudflare.ts +732 -0
  171. package/src/integrations/hono.ts +589 -0
  172. package/src/integrations/index.ts +27 -0
  173. package/src/integrations/nuxt.ts +78 -0
  174. package/src/integrations/runtime/use-ts-analytics.ts +32 -0
  175. package/src/integrations/stx.ts +138 -0
  176. package/src/jobs/index.ts +127 -0
  177. package/src/lib/crypto-random.ts +41 -0
  178. package/src/lib/ddb-errors.ts +20 -0
  179. package/src/lib/dynamodb.ts +216 -0
  180. package/src/lib/email.ts +38 -0
  181. package/src/lib/ga-import.ts +471 -0
  182. package/src/lib/ga4-api.ts +244 -0
  183. package/src/lib/goals.ts +205 -0
  184. package/src/lib/index.ts +6 -0
  185. package/src/lib/ingest-counters.ts +123 -0
  186. package/src/lib/log.ts +36 -0
  187. package/src/lib/membership.ts +98 -0
  188. package/src/lib/plans.ts +90 -0
  189. package/src/lib/quota.ts +46 -0
  190. package/src/lib/rate-limit.ts +27 -0
  191. package/src/lib/rollups.ts +472 -0
  192. package/src/lib/salt.ts +97 -0
  193. package/src/lib/scheduler.ts +97 -0
  194. package/src/lib/significance.ts +66 -0
  195. package/src/lib/site-retention.ts +56 -0
  196. package/src/local.ts +360 -0
  197. package/src/model-connector.ts +616 -0
  198. package/src/models/AggregatedStats.ts +162 -0
  199. package/src/models/CampaignStats.ts +141 -0
  200. package/src/models/Conversion.ts +135 -0
  201. package/src/models/CustomEvent.ts +123 -0
  202. package/src/models/DeviceStats.ts +105 -0
  203. package/src/models/EventStats.ts +108 -0
  204. package/src/models/GeoStats.ts +112 -0
  205. package/src/models/Goal.ts +105 -0
  206. package/src/models/GoalStats.ts +106 -0
  207. package/src/models/PageStats.ts +152 -0
  208. package/src/models/PageView.ts +277 -0
  209. package/src/models/RealtimeStats.ts +96 -0
  210. package/src/models/ReferrerStats.ts +115 -0
  211. package/src/models/Session.ts +235 -0
  212. package/src/models/Site.ts +115 -0
  213. package/src/models/index.ts +50 -0
  214. package/src/models/orm/index.ts +2043 -0
  215. package/src/models/types.ts +71 -0
  216. package/src/router.ts +521 -0
  217. package/src/sqs-buffering.ts +806 -0
  218. package/src/stacks-integration.ts +527 -0
  219. package/src/tracking-script.ts +944 -0
  220. package/src/tracking.ts +27 -0
  221. package/src/types/analytics.ts +219 -0
  222. package/src/types/api.ts +75 -0
  223. package/src/types/bun-router.d.ts +15 -0
  224. package/src/types/dashboard.ts +62 -0
  225. package/src/types/index.ts +66 -0
  226. package/src/types/stx.d.ts +27 -0
  227. package/src/types/window.d.ts +101 -0
  228. package/src/types.ts +911 -0
  229. package/src/utils/cache.ts +148 -0
  230. package/src/utils/date.ts +118 -0
  231. package/src/utils/filters.ts +71 -0
  232. package/src/utils/geolocation.ts +323 -0
  233. package/src/utils/index.ts +9 -0
  234. package/src/utils/response.ts +180 -0
  235. package/src/utils/timezone-country.ts +242 -0
  236. package/src/utils/user-agent.ts +110 -0
  237. package/src/version.ts +7 -0
@@ -0,0 +1,183 @@
1
+ <script server>
2
+ /**
3
+ * EngagementMetrics Component
4
+ *
5
+ * Displays user engagement metrics with visual indicators.
6
+ */
7
+ import { defineProps, withDefaults } from 'stx'
8
+ import { formatCompact, formatDuration, formatPercentage } from '../utils'
9
+
10
+ export interface EngagementData {
11
+ avgSessionDuration: number // in seconds
12
+ avgPagesPerSession: number
13
+ bounceRate: number // 0-1
14
+ returningVisitorRate: number // 0-1
15
+ avgTimeOnPage: number // in seconds
16
+ exitRate: number // 0-1
17
+ }
18
+
19
+ interface EngagementMetricsProps {
20
+ data: EngagementData | null
21
+ loading?: boolean
22
+ title?: string
23
+ }
24
+
25
+ const { data, loading, title } = withDefaults(
26
+ defineProps<EngagementMetricsProps>(),
27
+ {
28
+ loading: false,
29
+ title: 'Engagement',
30
+ }
31
+ )
32
+
33
+ interface MetricConfig {
34
+ label: string
35
+ key: keyof EngagementData
36
+ format: 'duration' | 'number' | 'percentage'
37
+ icon: string
38
+ good: 'high' | 'low'
39
+ benchmark?: number
40
+ }
41
+
42
+ const metrics: MetricConfig[] = [
43
+ {
44
+ label: 'Avg. Session Duration',
45
+ key: 'avgSessionDuration',
46
+ format: 'duration',
47
+ icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z',
48
+ good: 'high',
49
+ benchmark: 180,
50
+ },
51
+ {
52
+ label: 'Pages / Session',
53
+ key: 'avgPagesPerSession',
54
+ format: 'number',
55
+ icon: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z',
56
+ good: 'high',
57
+ benchmark: 3,
58
+ },
59
+ {
60
+ label: 'Bounce Rate',
61
+ key: 'bounceRate',
62
+ format: 'percentage',
63
+ icon: 'M13 7h8m0 0v8m0-8l-8 8-4-4-6 6',
64
+ good: 'low',
65
+ benchmark: 0.4,
66
+ },
67
+ {
68
+ label: 'Returning Visitors',
69
+ key: 'returningVisitorRate',
70
+ format: 'percentage',
71
+ icon: 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15',
72
+ good: 'high',
73
+ benchmark: 0.3,
74
+ },
75
+ {
76
+ label: 'Avg. Time on Page',
77
+ key: 'avgTimeOnPage',
78
+ format: 'duration',
79
+ icon: 'M15 12a3 3 0 11-6 0 3 3 0 016 0z M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z',
80
+ good: 'high',
81
+ benchmark: 60,
82
+ },
83
+ {
84
+ label: 'Exit Rate',
85
+ key: 'exitRate',
86
+ format: 'percentage',
87
+ icon: 'M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1',
88
+ good: 'low',
89
+ benchmark: 0.5,
90
+ },
91
+ ]
92
+
93
+ function formatValue(value: number, format: string): string {
94
+ switch (format) {
95
+ case 'duration':
96
+ return formatDuration(value)
97
+ case 'percentage':
98
+ return formatPercentage(value)
99
+ default:
100
+ return formatCompact(value)
101
+ }
102
+ }
103
+
104
+ function getPerformanceClass(metric: MetricConfig, value: number): string {
105
+ if (metric.benchmark === undefined)
106
+ return 'text-gray-600'
107
+
108
+ const isGood = metric.good === 'high'
109
+ ? value >= metric.benchmark
110
+ : value <= metric.benchmark
111
+
112
+ return isGood ? 'text-green-600' : 'text-amber-600'
113
+ }
114
+
115
+ function getBarClass(metric: MetricConfig, value: number): string {
116
+ if (metric.benchmark === undefined)
117
+ return 'bg-gray-600'
118
+
119
+ const isGood = metric.good === 'high'
120
+ ? value >= metric.benchmark
121
+ : value <= metric.benchmark
122
+
123
+ return isGood ? 'bg-green-600' : 'bg-amber-600'
124
+ }
125
+
126
+ function getBarWidth(metric: MetricConfig, value: number): number {
127
+ if (metric.format === 'percentage') {
128
+ return Math.min(value * 100, 100)
129
+ }
130
+ const max = (metric.benchmark ?? value) * 2
131
+ return Math.min((value / max) * 100, 100)
132
+ }
133
+ </script>
134
+
135
+ <div class="engagement-metrics card-hover p-6">
136
+ <h3 class="text-sm font-medium text-gray-900 mb-4">{{ title }}</h3>
137
+
138
+ @if (loading)
139
+ <div class="grid grid-cols-2 gap-4">
140
+ @foreach ([1, 2, 3, 4, 5, 6] as i)
141
+ <div class="animate-pulse">
142
+ <div class="skeleton h-4 w-24 mb-2"></div>
143
+ <div class="skeleton h-8 w-16 mb-2"></div>
144
+ <div class="skeleton h-2 w-full"></div>
145
+ </div>
146
+ @endforeach
147
+ </div>
148
+ @elseif (!data)
149
+ <div class="text-center py-8 text-gray-500">
150
+ No engagement data available
151
+ </div>
152
+ @else
153
+ <div class="grid grid-cols-2 lg:grid-cols-3 gap-6">
154
+ @foreach (metrics as metric)
155
+ <div class="space-y-2">
156
+ <div class="flex items-center gap-2 text-sm text-gray-500">
157
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
158
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="{{ metric.icon }}" />
159
+ </svg>
160
+ <span>{{ metric.label }}</span>
161
+ </div>
162
+
163
+ <div class="text-2xl font-semibold {{ getPerformanceClass(metric, data[metric.key]) }}">
164
+ {{ formatValue(data[metric.key], metric.format) }}
165
+ </div>
166
+
167
+ <div class="h-1.5 bg-gray-100 rounded-full overflow-hidden">
168
+ <div
169
+ class="h-full rounded-full transition-all duration-500 {{ getBarClass(metric, data[metric.key]) }}"
170
+ style="width: {{ getBarWidth(metric, data[metric.key]) }}%"
171
+ ></div>
172
+ </div>
173
+
174
+ @if (metric.benchmark)
175
+ <div class="text-xs text-gray-400">
176
+ Benchmark: {{ formatValue(metric.benchmark, metric.format) }}
177
+ </div>
178
+ @endif
179
+ </div>
180
+ @endforeach
181
+ </div>
182
+ @endif
183
+ </div>
@@ -0,0 +1,192 @@
1
+ {{-- EventsSection Component --}}
2
+ {{-- Props: events (array of {name, count, unique, value}) --}}
3
+ <div class="events-section">
4
+ <div class="events-header">
5
+ <span class="events-title">Events</span>
6
+ @if(events.length > 0)
7
+ <button class="add-event-btn" type="button">
8
+ <Icon name="plus" size="16" />
9
+ Add event
10
+ </button>
11
+ @endif
12
+ </div>
13
+
14
+ @if(events.length > 0)
15
+ <table class="data-table">
16
+ <thead>
17
+ <tr>
18
+ <th>Event Name</th>
19
+ <th class="text-right">Count</th>
20
+ <th class="text-right">Unique</th>
21
+ <th class="text-right">Avg Value</th>
22
+ </tr>
23
+ </thead>
24
+ <tbody>
25
+ @foreach(events as event)
26
+ <tr>
27
+ <td class="name">
28
+ <span class="event-indicator"></span>
29
+ {{ event.name }}
30
+ </td>
31
+ <td class="value">{{ event.count | fmt }}</td>
32
+ <td class="value">{{ event.unique | fmt }}</td>
33
+ <td class="value">
34
+ @if(event.avgValue)
35
+ {{ formatCurrency(event.avgValue) }}
36
+ @else
37
+ -
38
+ @endif
39
+ </td>
40
+ </tr>
41
+ @endforeach
42
+ </tbody>
43
+ </table>
44
+ @else
45
+ <div class="empty-state">
46
+ <div class="empty-icon">
47
+ <Icon name="zap" size="48" />
48
+ </div>
49
+ <p class="empty-title">You haven't created any events yet.</p>
50
+ <p class="empty-description">
51
+ Events let you track custom actions like button clicks, form submissions, and purchases.
52
+ </p>
53
+ <a href="/docs/events" class="learn-link">Learn how to use events</a>
54
+ </div>
55
+ @endif
56
+ </div>
57
+
58
+ @ts
59
+ function formatCurrency(num: number): string {
60
+ return '$' + num.toFixed(2)
61
+ }
62
+ @endts
63
+
64
+ <style>
65
+ .events-section {
66
+ background: var(--bg-secondary);
67
+ border-radius: 8px;
68
+ padding: 1.5rem;
69
+ }
70
+
71
+ .events-header {
72
+ display: flex;
73
+ justify-content: space-between;
74
+ align-items: center;
75
+ margin-bottom: 1rem;
76
+ }
77
+
78
+ .events-title {
79
+ font-size: 0.875rem;
80
+ font-weight: 500;
81
+ color: var(--text-primary);
82
+ }
83
+
84
+ .add-event-btn {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 0.5rem;
88
+ background: transparent;
89
+ border: 1px solid var(--border);
90
+ color: var(--text-secondary);
91
+ padding: 0.5rem 1rem;
92
+ border-radius: 6px;
93
+ font-size: 0.75rem;
94
+ cursor: pointer;
95
+ transition: all 0.15s;
96
+ }
97
+
98
+ .add-event-btn:hover {
99
+ border-color: var(--accent);
100
+ color: var(--accent);
101
+ }
102
+
103
+ .data-table {
104
+ width: 100%;
105
+ font-size: 0.8125rem;
106
+ border-collapse: collapse;
107
+ }
108
+
109
+ .data-table th {
110
+ text-align: left;
111
+ color: var(--text-muted);
112
+ font-weight: 500;
113
+ padding: 0.5rem 0;
114
+ border-bottom: 1px solid var(--border);
115
+ font-size: 0.6875rem;
116
+ text-transform: uppercase;
117
+ letter-spacing: 0.05em;
118
+ }
119
+
120
+ .data-table th.text-right {
121
+ text-align: right;
122
+ }
123
+
124
+ .data-table td {
125
+ padding: 0.75rem 0;
126
+ border-bottom: 1px solid var(--bg-tertiary);
127
+ }
128
+
129
+ .data-table tr:last-child td {
130
+ border-bottom: none;
131
+ }
132
+
133
+ .data-table .name {
134
+ color: var(--text-primary);
135
+ display: flex;
136
+ align-items: center;
137
+ gap: 0.75rem;
138
+ }
139
+
140
+ .event-indicator {
141
+ width: 8px;
142
+ height: 8px;
143
+ background: var(--accent);
144
+ border-radius: 50%;
145
+ }
146
+
147
+ .data-table .value {
148
+ color: var(--text-secondary);
149
+ text-align: right;
150
+ }
151
+
152
+ .empty-state {
153
+ text-align: center;
154
+ padding: 3rem 2rem;
155
+ }
156
+
157
+ .empty-icon {
158
+ color: var(--text-muted);
159
+ margin-bottom: 1rem;
160
+ opacity: 0.5;
161
+ }
162
+
163
+ .empty-title {
164
+ color: var(--text-primary);
165
+ font-size: 1rem;
166
+ font-weight: 500;
167
+ margin-bottom: 0.5rem;
168
+ }
169
+
170
+ .empty-description {
171
+ color: var(--text-muted);
172
+ font-size: 0.875rem;
173
+ margin-bottom: 1.5rem;
174
+ max-width: 400px;
175
+ margin-left: auto;
176
+ margin-right: auto;
177
+ }
178
+
179
+ .learn-link {
180
+ display: inline-flex;
181
+ align-items: center;
182
+ gap: 0.5rem;
183
+ color: var(--accent);
184
+ text-decoration: none;
185
+ font-size: 0.875rem;
186
+ font-weight: 500;
187
+ }
188
+
189
+ .learn-link:hover {
190
+ text-decoration: underline;
191
+ }
192
+ </style>
@@ -0,0 +1,104 @@
1
+ <script server>
2
+ /**
3
+ * FilterBar Component
4
+ *
5
+ * Search and filter bar for analytics data.
6
+ */
7
+ import { defineProps, withDefaults } from 'stx'
8
+
9
+ export interface FilterOption {
10
+ id: string
11
+ label: string
12
+ options: { value: string, label: string }[]
13
+ }
14
+
15
+ interface FilterBarProps {
16
+ placeholder?: string
17
+ filters?: FilterOption[]
18
+ showExport?: boolean
19
+ }
20
+
21
+ const { placeholder, filters, showExport } = withDefaults(
22
+ defineProps<FilterBarProps>(),
23
+ {
24
+ placeholder: 'Search...',
25
+ filters: () => [],
26
+ showExport: false,
27
+ }
28
+ )
29
+
30
+ const filtersJson = JSON.stringify(filters)
31
+ </script>
32
+
33
+ <script client>
34
+ const searchQuery = state('')
35
+ const activeFilters = state({})
36
+ const showExportMenu = state(false)
37
+
38
+ const hasActiveFilters = derived(() => Object.values(activeFilters()).some(v => v !== ''))
39
+
40
+ function clearFilters() {
41
+ searchQuery.set('')
42
+ activeFilters.set({})
43
+ }
44
+ </script>
45
+ <div class="filter-bar flex flex-wrap items-center gap-3 p-4 bg-white rounded-lg border border-gray-200">
46
+ <div class="relative flex-1 min-w-48">
47
+ <Icon name="search" size="16" class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
48
+ <input
49
+ type="text"
50
+ :model="searchQuery"
51
+ placeholder="{{ placeholder }}"
52
+ class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
53
+ >
54
+ </div>
55
+
56
+ @foreach (filters as filter)
57
+ <div class="relative">
58
+ <select
59
+ :model="activeFilters"
60
+ class="appearance-none pl-3 pr-8 py-2 border border-gray-300 rounded-lg text-sm bg-white focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent cursor-pointer"
61
+ >
62
+ <option value="">{{ filter.label }}</option>
63
+ @foreach (filter.options as option)
64
+ <option value="{{ option.value }}">{{ option.label }}</option>
65
+ @endforeach
66
+ </select>
67
+ <Icon name="chevron-down" size="16" class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none" />
68
+ </div>
69
+ @endforeach
70
+
71
+ <button
72
+ :show="hasActiveFilters() || searchQuery()"
73
+ type="button"
74
+ class="px-3 py-2 text-sm text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg transition-colors"
75
+ @click="clearFilters()"
76
+ >
77
+ Clear
78
+ </button>
79
+
80
+ @if (showExport)
81
+ <div class="relative">
82
+ <button
83
+ type="button"
84
+ class="btn-secondary flex items-center gap-2"
85
+ @click="showExportMenu.set(!showExportMenu())"
86
+ >
87
+ <Icon name="download" size="16" />
88
+ Export
89
+ </button>
90
+
91
+ <div
92
+ :show="showExportMenu"
93
+ class="dropdown w-32 absolute right-0 mt-2 bg-white rounded-lg shadow-lg border border-gray-200 z-10"
94
+ >
95
+ <button type="button" class="dropdown-item w-full text-left px-4 py-2 text-sm hover:bg-gray-50">
96
+ Export CSV
97
+ </button>
98
+ <button type="button" class="dropdown-item w-full text-left px-4 py-2 text-sm hover:bg-gray-50">
99
+ Export JSON
100
+ </button>
101
+ </div>
102
+ </div>
103
+ @endif
104
+ </div>