@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.
- package/CHANGELOG.md +68 -0
- package/LICENSE.md +21 -0
- package/README.md +361 -0
- package/bin/cli.ts +169 -0
- package/dist/Analytics.d.ts +558 -0
- package/dist/api.d.ts +109 -0
- package/dist/batching.d.ts +93 -0
- package/dist/chunk-2mx7fq49.js +4 -0
- package/dist/chunk-3z29508k.js +204 -0
- package/dist/chunk-deephkz6.js +56 -0
- package/dist/chunk-j261vgyp.js +305 -0
- package/dist/chunk-xga17tz7.js +207 -0
- package/dist/config.d.ts +132 -0
- package/dist/dashboard/components/index.d.ts +80 -0
- package/dist/dashboard/composables/useAnalytics.d.ts +99 -0
- package/dist/dashboard/index.d.ts +110 -0
- package/dist/dashboard/types/index.d.ts +144 -0
- package/dist/dashboard/utils/index.d.ts +81 -0
- package/dist/dynamodb.d.ts +86 -0
- package/dist/funnels.d.ts +104 -0
- package/dist/geolocation.d.ts +96 -0
- package/dist/index.d.ts +371 -0
- package/dist/index.js +10211 -0
- package/dist/infrastructure/cdk.d.ts +60 -0
- package/dist/infrastructure/cloudformation.d.ts +44 -0
- package/dist/infrastructure/index.d.ts +45 -0
- package/dist/infrastructure/setup.d.ts +133 -0
- package/dist/integrations/cloudflare.d.ts +76 -0
- package/dist/integrations/hono.d.ts +60 -0
- package/dist/integrations/index.d.ts +24 -0
- package/dist/integrations/nuxt.d.ts +7 -0
- package/dist/integrations/nuxt.js +42 -0
- package/dist/integrations/runtime/use-ts-analytics.d.ts +18 -0
- package/dist/integrations/runtime/use-ts-analytics.js +17 -0
- package/dist/integrations/stx.d.ts +88 -0
- package/dist/integrations/stx.js +18 -0
- package/dist/lib/crypto-random.d.ts +4 -0
- package/dist/lib/salt.d.ts +16 -0
- package/dist/local.d.ts +56 -0
- package/dist/model-connector.d.ts +145 -0
- package/dist/models/AggregatedStats.d.ts +9 -0
- package/dist/models/CampaignStats.d.ts +9 -0
- package/dist/models/Conversion.d.ts +11 -0
- package/dist/models/CustomEvent.d.ts +11 -0
- package/dist/models/DeviceStats.d.ts +9 -0
- package/dist/models/EventStats.d.ts +9 -0
- package/dist/models/GeoStats.d.ts +9 -0
- package/dist/models/Goal.d.ts +9 -0
- package/dist/models/GoalStats.d.ts +9 -0
- package/dist/models/PageStats.d.ts +11 -0
- package/dist/models/PageView.d.ts +13 -0
- package/dist/models/RealtimeStats.d.ts +9 -0
- package/dist/models/ReferrerStats.d.ts +9 -0
- package/dist/models/Session.d.ts +11 -0
- package/dist/models/Site.d.ts +11 -0
- package/dist/models/index.d.ts +28 -0
- package/dist/models/types.d.ts +60 -0
- package/dist/sqs-buffering.d.ts +243 -0
- package/dist/stacks-integration.d.ts +159 -0
- package/dist/tracking-script.d.ts +71 -0
- package/dist/tracking.d.ts +17 -0
- package/dist/tracking.js +26 -0
- package/dist/types.d.ts +595 -0
- package/dist/utils/geolocation.d.ts +111 -0
- package/dist/utils/user-agent.d.ts +17 -0
- package/dist/version.d.ts +7 -0
- package/package.json +119 -0
- package/src/Analytics.ts +3349 -0
- package/src/api.ts +1286 -0
- package/src/assets/crosswind.css +2220 -0
- package/src/batching.ts +452 -0
- package/src/components/dashboard/index.ts +18 -0
- package/src/config.ts +456 -0
- package/src/dashboard/Dashboard.stx +1517 -0
- package/src/dashboard/components/AlertCard.stx +177 -0
- package/src/dashboard/components/AnalyticsDashboard.stx +354 -0
- package/src/dashboard/components/AnimatedNumber.stx +86 -0
- package/src/dashboard/components/BarChart.stx +220 -0
- package/src/dashboard/components/BrowserBreakdown.stx +98 -0
- package/src/dashboard/components/BrowsersTable.stx +125 -0
- package/src/dashboard/components/CampaignBreakdown.stx +163 -0
- package/src/dashboard/components/CampaignTable.stx +238 -0
- package/src/dashboard/components/CountryList.stx +101 -0
- package/src/dashboard/components/DataTable.stx +226 -0
- package/src/dashboard/components/DateRangePicker.stx +77 -0
- package/src/dashboard/components/DeviceBreakdown.stx +94 -0
- package/src/dashboard/components/DevicesTable.stx +163 -0
- package/src/dashboard/components/DonutChart.stories.ts +55 -0
- package/src/dashboard/components/DonutChart.stx +157 -0
- package/src/dashboard/components/EmptyState.stx +90 -0
- package/src/dashboard/components/EngagementMetrics.stx +183 -0
- package/src/dashboard/components/EventsSection.stx +192 -0
- package/src/dashboard/components/FilterBar.stx +104 -0
- package/src/dashboard/components/FullAnalyticsDashboard.stx +455 -0
- package/src/dashboard/components/FunnelChart.stx +142 -0
- package/src/dashboard/components/GeoTable.stx +337 -0
- package/src/dashboard/components/GoalsPanel.stx +109 -0
- package/src/dashboard/components/Header.stx +39 -0
- package/src/dashboard/components/HeatmapChart.stx +140 -0
- package/src/dashboard/components/LiveActivityFeed.stx +172 -0
- package/src/dashboard/components/MetricComparison.stx +106 -0
- package/src/dashboard/components/MiniStats.stx +96 -0
- package/src/dashboard/components/OSBreakdown.stx +124 -0
- package/src/dashboard/components/PageDetailCard.stx +102 -0
- package/src/dashboard/components/PagesTable.stx +127 -0
- package/src/dashboard/components/ProgressRing.stx +89 -0
- package/src/dashboard/components/RealtimeCounter.stories.ts +45 -0
- package/src/dashboard/components/RealtimeCounter.stx +46 -0
- package/src/dashboard/components/ReferrersTable.stx +180 -0
- package/src/dashboard/components/SparklineChart.stx +160 -0
- package/src/dashboard/components/StatCard.stories.ts +58 -0
- package/src/dashboard/components/StatCard.stx +90 -0
- package/src/dashboard/components/SummaryStats.stx +81 -0
- package/src/dashboard/components/TabNav.stx +66 -0
- package/src/dashboard/components/ThemeSwitcher.stx +124 -0
- package/src/dashboard/components/TimeSeriesChart.stx +106 -0
- package/src/dashboard/components/TopList.stories.ts +58 -0
- package/src/dashboard/components/TopList.stx +74 -0
- package/src/dashboard/components/TrendIndicator.stx +84 -0
- package/src/dashboard/components/heatmap/ClickHeatmap.stx +264 -0
- package/src/dashboard/components/heatmap/ElementClickList.stx +125 -0
- package/src/dashboard/components/heatmap/HeatmapControls.stx +125 -0
- package/src/dashboard/components/heatmap/HeatmapLegend.stx +69 -0
- package/src/dashboard/components/heatmap/PageHeatmap.stx +264 -0
- package/src/dashboard/components/heatmap/ScrollHeatmap.stx +127 -0
- package/src/dashboard/components/heatmap/index.ts +12 -0
- package/src/dashboard/components/index.ts +86 -0
- package/src/dashboard/composables/useAnalytics.ts +465 -0
- package/src/dashboard/demo/DemoApp.stx +370 -0
- package/src/dashboard/demo/index.ts +8 -0
- package/src/dashboard/demo/mockData.ts +234 -0
- package/src/dashboard/index.ts +117 -0
- package/src/dashboard/stx-shim.d.ts +4 -0
- package/src/dashboard/types/index.ts +203 -0
- package/src/dashboard/utils/index.ts +426 -0
- package/src/dynamodb.ts +344 -0
- package/src/funnels.ts +534 -0
- package/src/geolocation.ts +515 -0
- package/src/handlers/alerts.ts +328 -0
- package/src/handlers/annotations.ts +128 -0
- package/src/handlers/api-keys.ts +240 -0
- package/src/handlers/auth.ts +1020 -0
- package/src/handlers/authz.ts +137 -0
- package/src/handlers/collect.ts +724 -0
- package/src/handlers/data.ts +625 -0
- package/src/handlers/experiments.ts +138 -0
- package/src/handlers/funnels.ts +216 -0
- package/src/handlers/goals.ts +218 -0
- package/src/handlers/heatmaps.ts +272 -0
- package/src/handlers/index.ts +56 -0
- package/src/handlers/lib/read-cache.ts +63 -0
- package/src/handlers/misc.ts +486 -0
- package/src/handlers/oauth.ts +233 -0
- package/src/handlers/performance.ts +388 -0
- package/src/handlers/sessions.ts +413 -0
- package/src/handlers/sharing.ts +198 -0
- package/src/handlers/stats.ts +1368 -0
- package/src/handlers/team.ts +161 -0
- package/src/handlers/uptime.ts +283 -0
- package/src/handlers/views.ts +390 -0
- package/src/handlers/webhooks.ts +226 -0
- package/src/heatmap/index.ts +32 -0
- package/src/heatmap/tracking-script.ts +452 -0
- package/src/heatmap/types.ts +79 -0
- package/src/index.ts +387 -0
- package/src/infrastructure/cdk.ts +496 -0
- package/src/infrastructure/cloudformation.ts +595 -0
- package/src/infrastructure/index.ts +48 -0
- package/src/infrastructure/setup.ts +611 -0
- package/src/integrations/cloudflare.ts +732 -0
- package/src/integrations/hono.ts +589 -0
- package/src/integrations/index.ts +27 -0
- package/src/integrations/nuxt.ts +78 -0
- package/src/integrations/runtime/use-ts-analytics.ts +32 -0
- package/src/integrations/stx.ts +138 -0
- package/src/jobs/index.ts +127 -0
- package/src/lib/crypto-random.ts +41 -0
- package/src/lib/ddb-errors.ts +20 -0
- package/src/lib/dynamodb.ts +216 -0
- package/src/lib/email.ts +38 -0
- package/src/lib/ga-import.ts +471 -0
- package/src/lib/ga4-api.ts +244 -0
- package/src/lib/goals.ts +205 -0
- package/src/lib/index.ts +6 -0
- package/src/lib/ingest-counters.ts +123 -0
- package/src/lib/log.ts +36 -0
- package/src/lib/membership.ts +98 -0
- package/src/lib/plans.ts +90 -0
- package/src/lib/quota.ts +46 -0
- package/src/lib/rate-limit.ts +27 -0
- package/src/lib/rollups.ts +472 -0
- package/src/lib/salt.ts +97 -0
- package/src/lib/scheduler.ts +97 -0
- package/src/lib/significance.ts +66 -0
- package/src/lib/site-retention.ts +56 -0
- package/src/local.ts +360 -0
- package/src/model-connector.ts +616 -0
- package/src/models/AggregatedStats.ts +162 -0
- package/src/models/CampaignStats.ts +141 -0
- package/src/models/Conversion.ts +135 -0
- package/src/models/CustomEvent.ts +123 -0
- package/src/models/DeviceStats.ts +105 -0
- package/src/models/EventStats.ts +108 -0
- package/src/models/GeoStats.ts +112 -0
- package/src/models/Goal.ts +105 -0
- package/src/models/GoalStats.ts +106 -0
- package/src/models/PageStats.ts +152 -0
- package/src/models/PageView.ts +277 -0
- package/src/models/RealtimeStats.ts +96 -0
- package/src/models/ReferrerStats.ts +115 -0
- package/src/models/Session.ts +235 -0
- package/src/models/Site.ts +115 -0
- package/src/models/index.ts +50 -0
- package/src/models/orm/index.ts +2043 -0
- package/src/models/types.ts +71 -0
- package/src/router.ts +521 -0
- package/src/sqs-buffering.ts +806 -0
- package/src/stacks-integration.ts +527 -0
- package/src/tracking-script.ts +944 -0
- package/src/tracking.ts +27 -0
- package/src/types/analytics.ts +219 -0
- package/src/types/api.ts +75 -0
- package/src/types/bun-router.d.ts +15 -0
- package/src/types/dashboard.ts +62 -0
- package/src/types/index.ts +66 -0
- package/src/types/stx.d.ts +27 -0
- package/src/types/window.d.ts +101 -0
- package/src/types.ts +911 -0
- package/src/utils/cache.ts +148 -0
- package/src/utils/date.ts +118 -0
- package/src/utils/filters.ts +71 -0
- package/src/utils/geolocation.ts +323 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/response.ts +180 -0
- package/src/utils/timezone-country.ts +242 -0
- package/src/utils/user-agent.ts +110 -0
- package/src/version.ts +7 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* PageHeatmap Component
|
|
4
|
+
*
|
|
5
|
+
* Main container for page heatmap visualization with iframe/screenshot
|
|
6
|
+
* and overlay controls.
|
|
7
|
+
*/
|
|
8
|
+
export const siteId = $props.siteId
|
|
9
|
+
export const pageUrl = $props.pageUrl
|
|
10
|
+
export const apiEndpoint = $props.apiEndpoint || ''
|
|
11
|
+
export const screenshotUrl = $props.screenshotUrl
|
|
12
|
+
export const initialStartDate = $props.initialStartDate
|
|
13
|
+
export const initialEndDate = $props.initialEndDate
|
|
14
|
+
|
|
15
|
+
// Use window.apiPath if available, otherwise define a local one
|
|
16
|
+
const apiPath = window.apiPath || ((path) => {
|
|
17
|
+
const urlParams = new URLSearchParams(window.location.search)
|
|
18
|
+
const USE_STEALTH = urlParams.get('stealth') !== 'false'
|
|
19
|
+
if (!USE_STEALTH) return path
|
|
20
|
+
const STEALTH_MAP = { sites: 'projects', heatmap: 'touch' }
|
|
21
|
+
let result = path.replace('/api/sites/', '/api/p/')
|
|
22
|
+
Object.keys(STEALTH_MAP).forEach(k => {
|
|
23
|
+
result = result.replace(new RegExp(`/${k}(?=/|\\?|$)`, 'g'), `/${STEALTH_MAP[k]}`)
|
|
24
|
+
})
|
|
25
|
+
return result
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// State
|
|
29
|
+
let viewMode = 'click'
|
|
30
|
+
let deviceFilter = 'all'
|
|
31
|
+
let startDate = initialStartDate || new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10)
|
|
32
|
+
let endDate = initialEndDate || new Date().toISOString().slice(0, 10)
|
|
33
|
+
let loading = false
|
|
34
|
+
let error = null
|
|
35
|
+
|
|
36
|
+
// Heatmap data
|
|
37
|
+
let clickData = []
|
|
38
|
+
let topElements = []
|
|
39
|
+
let totalClicks = 0
|
|
40
|
+
let scrollData = []
|
|
41
|
+
let avgScrollDepth = 0
|
|
42
|
+
let totalScrollSessions = 0
|
|
43
|
+
|
|
44
|
+
// Container dimensions
|
|
45
|
+
let containerWidth = 800
|
|
46
|
+
let containerHeight = 600
|
|
47
|
+
|
|
48
|
+
const apiBaseUrl = apiPath(`${apiEndpoint}/api/sites/${siteId}/heatmap`)
|
|
49
|
+
|
|
50
|
+
// Fetch click heatmap data
|
|
51
|
+
export async function fetchClickData() {
|
|
52
|
+
loading = true
|
|
53
|
+
error = null
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const params = new URLSearchParams({
|
|
57
|
+
path: pageUrl,
|
|
58
|
+
startDate: new Date(startDate).toISOString(),
|
|
59
|
+
endDate: new Date(endDate).toISOString(),
|
|
60
|
+
device: deviceFilter,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const response = await fetch(`${apiBaseUrl}/clicks?${params}`)
|
|
64
|
+
if (!response.ok) throw new Error('Failed to fetch click data')
|
|
65
|
+
|
|
66
|
+
const data = await response.json()
|
|
67
|
+
clickData = data.clicks || []
|
|
68
|
+
topElements = data.topElements || []
|
|
69
|
+
totalClicks = data.totalClicks || 0
|
|
70
|
+
} catch (err) {
|
|
71
|
+
error = err.message || 'Unknown error'
|
|
72
|
+
console.error('Heatmap fetch error:', err)
|
|
73
|
+
} finally {
|
|
74
|
+
loading = false
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Fetch scroll heatmap data
|
|
79
|
+
export async function fetchScrollData() {
|
|
80
|
+
loading = true
|
|
81
|
+
error = null
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const params = new URLSearchParams({
|
|
85
|
+
path: pageUrl,
|
|
86
|
+
startDate: new Date(startDate).toISOString(),
|
|
87
|
+
endDate: new Date(endDate).toISOString(),
|
|
88
|
+
device: deviceFilter,
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const response = await fetch(`${apiBaseUrl}/scroll?${params}`)
|
|
92
|
+
if (!response.ok) throw new Error('Failed to fetch scroll data')
|
|
93
|
+
|
|
94
|
+
const data = await response.json()
|
|
95
|
+
scrollData = data.scrollDepths || []
|
|
96
|
+
avgScrollDepth = data.avgMaxDepth || 0
|
|
97
|
+
totalScrollSessions = data.totalSessions || 0
|
|
98
|
+
} catch (err) {
|
|
99
|
+
error = err.message || 'Unknown error'
|
|
100
|
+
console.error('Scroll data fetch error:', err)
|
|
101
|
+
} finally {
|
|
102
|
+
loading = false
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Fetch data based on view mode
|
|
107
|
+
export async function fetchData() {
|
|
108
|
+
if (viewMode === 'click') {
|
|
109
|
+
await fetchClickData()
|
|
110
|
+
} else if (viewMode === 'scroll') {
|
|
111
|
+
await fetchScrollData()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Handle view mode change
|
|
116
|
+
export function handleViewModeChange(mode) {
|
|
117
|
+
viewMode = mode
|
|
118
|
+
fetchData()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Handle device filter change
|
|
122
|
+
export function handleDeviceChange(device) {
|
|
123
|
+
deviceFilter = device
|
|
124
|
+
fetchData()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Handle date range change
|
|
128
|
+
export function handleDateRangeChange(range) {
|
|
129
|
+
startDate = range.start.toISOString().slice(0, 10)
|
|
130
|
+
endDate = range.end.toISOString().slice(0, 10)
|
|
131
|
+
fetchData()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Handle element highlight
|
|
135
|
+
export function handleHighlight(selector) {
|
|
136
|
+
console.log('Highlight:', selector)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Handle element click
|
|
140
|
+
export function handleElementClick(element) {
|
|
141
|
+
console.log('Element clicked:', element)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Handle refresh
|
|
145
|
+
export function handleRefresh() {
|
|
146
|
+
fetchData()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Initialize
|
|
150
|
+
if (typeof window !== 'undefined') {
|
|
151
|
+
setTimeout(fetchData, 0)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export { viewMode, deviceFilter, startDate, endDate, loading, error, clickData, topElements, totalClicks, scrollData, avgScrollDepth, totalScrollSessions, containerWidth, containerHeight }
|
|
155
|
+
</script>
|
|
156
|
+
|
|
157
|
+
<div class="page-heatmap flex flex-col gap-4 h-full">
|
|
158
|
+
<!-- Header -->
|
|
159
|
+
<div class="flex justify-between items-center">
|
|
160
|
+
<div class="flex flex-col gap-1">
|
|
161
|
+
<h2 class="text-xl font-semibold text-gray-900">Page Heatmap</h2>
|
|
162
|
+
<span class="text-sm text-gray-500 font-mono">{{ pageUrl }}</span>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<!-- Controls -->
|
|
167
|
+
@include('heatmap/HeatmapControls', {
|
|
168
|
+
viewMode: viewMode,
|
|
169
|
+
deviceFilter: deviceFilter,
|
|
170
|
+
startDate: startDate,
|
|
171
|
+
endDate: endDate,
|
|
172
|
+
loading: loading,
|
|
173
|
+
onViewModeChange: handleViewModeChange,
|
|
174
|
+
onDeviceChange: handleDeviceChange,
|
|
175
|
+
onDateRangeChange: handleDateRangeChange,
|
|
176
|
+
onRefresh: handleRefresh
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
<!-- Error state -->
|
|
180
|
+
@if(error)
|
|
181
|
+
<div class="flex items-center gap-3 px-4 py-3 bg-red-50 border border-red-200 rounded-lg text-red-600 text-sm">
|
|
182
|
+
<Icon name="alert-circle" size="20" class="flex-shrink-0" />
|
|
183
|
+
<span>{{ error }}</span>
|
|
184
|
+
<button type="button" class="ml-auto px-3 py-1 text-xs font-medium text-red-600 bg-white border border-red-200 rounded hover:bg-red-50" @click="fetchData">
|
|
185
|
+
Retry
|
|
186
|
+
</button>
|
|
187
|
+
</div>
|
|
188
|
+
@endif
|
|
189
|
+
|
|
190
|
+
<!-- Main content -->
|
|
191
|
+
<div class="grid grid-cols-1 lg:grid-cols-[1fr_320px] gap-6 flex-1 min-h-0">
|
|
192
|
+
<!-- Visualization area -->
|
|
193
|
+
<div class="flex flex-col gap-4 bg-white rounded-lg border border-gray-200 p-4 overflow-hidden">
|
|
194
|
+
<!-- Click heatmap view -->
|
|
195
|
+
@if(viewMode === 'click')
|
|
196
|
+
<div class="relative flex-1 min-h-[400px] border border-gray-200 rounded-md overflow-hidden bg-gray-50">
|
|
197
|
+
<!-- Page iframe or screenshot -->
|
|
198
|
+
@if(!screenshotUrl)
|
|
199
|
+
<iframe
|
|
200
|
+
src="{{ pageUrl }}"
|
|
201
|
+
class="w-full h-full border-none pointer-events-none"
|
|
202
|
+
sandbox="allow-same-origin"
|
|
203
|
+
loading="lazy"
|
|
204
|
+
/>
|
|
205
|
+
@else
|
|
206
|
+
<img
|
|
207
|
+
src="{{ screenshotUrl }}"
|
|
208
|
+
alt="Screenshot of {{ pageUrl }}"
|
|
209
|
+
class="w-full h-full object-cover object-top"
|
|
210
|
+
/>
|
|
211
|
+
@endif
|
|
212
|
+
|
|
213
|
+
<!-- Heatmap overlay -->
|
|
214
|
+
@include('heatmap/ClickHeatmap', {
|
|
215
|
+
clicks: clickData,
|
|
216
|
+
width: containerWidth,
|
|
217
|
+
height: containerHeight,
|
|
218
|
+
loading: loading
|
|
219
|
+
})
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
<!-- Legend -->
|
|
223
|
+
<div class="flex justify-between items-center pt-3 border-t border-gray-200">
|
|
224
|
+
@include('heatmap/HeatmapLegend', {
|
|
225
|
+
showValues: true,
|
|
226
|
+
maxValue: totalClicks
|
|
227
|
+
})
|
|
228
|
+
<span class="text-xs text-gray-500">{{ totalClicks.toLocaleString() }} total clicks</span>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
@elseif(viewMode === 'scroll')
|
|
232
|
+
<!-- Scroll heatmap view -->
|
|
233
|
+
@include('heatmap/ScrollHeatmap', {
|
|
234
|
+
data: scrollData,
|
|
235
|
+
avgMaxDepth: avgScrollDepth,
|
|
236
|
+
totalSessions: totalScrollSessions,
|
|
237
|
+
height: containerHeight,
|
|
238
|
+
loading: loading
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
@else
|
|
242
|
+
<!-- Movement heatmap view (placeholder) -->
|
|
243
|
+
<div class="flex flex-col items-center justify-center h-full text-gray-500 text-center">
|
|
244
|
+
<Icon name="trending-up" size="48" class="mb-4 opacity-50" />
|
|
245
|
+
<h3 class="text-base font-semibold text-gray-600 mb-2">Movement Tracking</h3>
|
|
246
|
+
<p class="text-sm">Mouse movement heatmaps coming soon</p>
|
|
247
|
+
</div>
|
|
248
|
+
@endif
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
<!-- Sidebar -->
|
|
252
|
+
@if(viewMode === 'click')
|
|
253
|
+
<div class="flex flex-col gap-4 min-h-0 overflow-y-auto lg:max-h-none max-h-[300px]">
|
|
254
|
+
@include('heatmap/ElementClickList', {
|
|
255
|
+
elements: topElements,
|
|
256
|
+
loading: loading,
|
|
257
|
+
maxItems: 15,
|
|
258
|
+
onHighlight: handleHighlight,
|
|
259
|
+
onClick: handleElementClick
|
|
260
|
+
})
|
|
261
|
+
</div>
|
|
262
|
+
@endif
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* ScrollHeatmap Component
|
|
4
|
+
*
|
|
5
|
+
* Visualizes scroll depth as horizontal bands showing reach percentage.
|
|
6
|
+
*/
|
|
7
|
+
export const data = $props.data || []
|
|
8
|
+
export const avgMaxDepth = $props.avgMaxDepth || 0
|
|
9
|
+
export const totalSessions = $props.totalSessions || 0
|
|
10
|
+
export const height = $props.height || 400
|
|
11
|
+
export const loading = $props.loading || false
|
|
12
|
+
|
|
13
|
+
// Generate color based on reach percentage
|
|
14
|
+
export function getColor(percentage) {
|
|
15
|
+
if (percentage >= 80) return '#10b981' // Green - high engagement
|
|
16
|
+
if (percentage >= 60) return '#34d399' // Light green
|
|
17
|
+
if (percentage >= 40) return '#fbbf24' // Yellow - medium engagement
|
|
18
|
+
if (percentage >= 20) return '#f97316' // Orange
|
|
19
|
+
return '#ef4444' // Red - low engagement
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function formatTime(ms) {
|
|
23
|
+
if (ms < 1000) return `${ms}ms`
|
|
24
|
+
const seconds = Math.floor(ms / 1000)
|
|
25
|
+
if (seconds < 60) return `${seconds}s`
|
|
26
|
+
const minutes = Math.floor(seconds / 60)
|
|
27
|
+
const remainingSeconds = seconds % 60
|
|
28
|
+
return `${minutes}m ${remainingSeconds}s`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Create depth buckets from 0 to 100 in increments of 10
|
|
32
|
+
const depthBuckets = []
|
|
33
|
+
for (let depth = 0; depth <= 90; depth += 10) {
|
|
34
|
+
const dataPoint = data.find(d => d.depth === depth)
|
|
35
|
+
depthBuckets.push({
|
|
36
|
+
depth,
|
|
37
|
+
label: `${depth}%`,
|
|
38
|
+
reachPercentage: dataPoint?.reachPercentage ?? (depth === 0 ? 100 : 0),
|
|
39
|
+
avgTimeMs: dataPoint?.avgTimeMs ?? 0,
|
|
40
|
+
sessions: dataPoint?.sessions ?? 0,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { depthBuckets }
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<div class="scroll-heatmap bg-white rounded-lg border border-gray-200 p-6 relative">
|
|
48
|
+
<!-- Loading state -->
|
|
49
|
+
@if(loading)
|
|
50
|
+
<div class="absolute inset-0 flex items-center justify-center bg-white/80 z-10">
|
|
51
|
+
<div class="w-8 h-8 border-2 border-gray-200 border-t-indigo-600 rounded-full animate-spin" />
|
|
52
|
+
</div>
|
|
53
|
+
@endif
|
|
54
|
+
|
|
55
|
+
<!-- Summary stats -->
|
|
56
|
+
<div class="flex gap-8 mb-6">
|
|
57
|
+
<div class="flex flex-col">
|
|
58
|
+
<span class="text-2xl font-bold text-gray-900">{{ avgMaxDepth }}%</span>
|
|
59
|
+
<span class="text-xs text-gray-500">Avg. Scroll Depth</span>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="flex flex-col">
|
|
62
|
+
<span class="text-2xl font-bold text-gray-900">{{ totalSessions.toLocaleString() }}</span>
|
|
63
|
+
<span class="text-xs text-gray-500">Total Sessions</span>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<!-- Scroll visualization -->
|
|
68
|
+
<div class="flex gap-8" style="height: {{ height }}px">
|
|
69
|
+
<!-- Page representation -->
|
|
70
|
+
<div class="w-[120px] flex-shrink-0">
|
|
71
|
+
<div class="h-full border-2 border-gray-300 rounded-md overflow-hidden bg-gray-50">
|
|
72
|
+
<div class="p-2 border-b border-gray-200 bg-white">
|
|
73
|
+
<div class="h-1.5 bg-gray-300 rounded mb-1" />
|
|
74
|
+
<div class="h-1.5 w-3/5 bg-gray-300 rounded" />
|
|
75
|
+
</div>
|
|
76
|
+
<div class="flex flex-col h-[calc(100%-2.5rem)]">
|
|
77
|
+
@each(bucket in depthBuckets)
|
|
78
|
+
<div
|
|
79
|
+
class="flex-1 flex items-center justify-center border-b border-dashed border-black/10 last:border-b-0"
|
|
80
|
+
style="background-color: {{ getColor(bucket.reachPercentage) }}; opacity: {{ bucket.reachPercentage / 100 }}"
|
|
81
|
+
>
|
|
82
|
+
<span class="text-[10px] font-semibold text-black/60">{{ bucket.label }}</span>
|
|
83
|
+
</div>
|
|
84
|
+
@endeach
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<!-- Data column -->
|
|
90
|
+
<div class="flex-1">
|
|
91
|
+
<div class="flex flex-col h-full">
|
|
92
|
+
@each(bucket in depthBuckets)
|
|
93
|
+
<div class="flex-1 flex items-center gap-3">
|
|
94
|
+
<div class="flex-1 h-6 bg-gray-100 rounded overflow-hidden">
|
|
95
|
+
<div
|
|
96
|
+
class="h-full rounded transition-all duration-300"
|
|
97
|
+
style="width: {{ bucket.reachPercentage }}%; background-color: {{ getColor(bucket.reachPercentage) }}"
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="flex items-center gap-2 w-24 flex-shrink-0">
|
|
101
|
+
<span class="text-sm font-semibold text-gray-900 w-10">{{ bucket.reachPercentage }}%</span>
|
|
102
|
+
@if(bucket.avgTimeMs > 0)
|
|
103
|
+
<span class="text-xs text-gray-500">{{ formatTime(bucket.avgTimeMs) }}</span>
|
|
104
|
+
@endif
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
@endeach
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<!-- Legend -->
|
|
113
|
+
<div class="flex justify-center gap-6 mt-6 pt-4 border-t border-gray-200">
|
|
114
|
+
<div class="flex items-center gap-1.5 text-xs text-gray-500">
|
|
115
|
+
<span class="w-3 h-3 rounded-sm" style="background: #10b981" />
|
|
116
|
+
<span>80%+ reach</span>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="flex items-center gap-1.5 text-xs text-gray-500">
|
|
119
|
+
<span class="w-3 h-3 rounded-sm" style="background: #fbbf24" />
|
|
120
|
+
<span>40-60% reach</span>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="flex items-center gap-1.5 text-xs text-gray-500">
|
|
123
|
+
<span class="w-3 h-3 rounded-sm" style="background: #ef4444" />
|
|
124
|
+
<span><20% reach</span>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heatmap Components
|
|
3
|
+
*
|
|
4
|
+
* STX components for visualizing user interaction heatmaps.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { default as PageHeatmap } from './PageHeatmap.stx'
|
|
8
|
+
export { default as ClickHeatmap } from './ClickHeatmap.stx'
|
|
9
|
+
export { default as ScrollHeatmap } from './ScrollHeatmap.stx'
|
|
10
|
+
export { default as HeatmapControls } from './HeatmapControls.stx'
|
|
11
|
+
export { default as HeatmapLegend } from './HeatmapLegend.stx'
|
|
12
|
+
export { default as ElementClickList } from './ElementClickList.stx'
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics Dashboard Components
|
|
3
|
+
*
|
|
4
|
+
* stx components for displaying analytics data.
|
|
5
|
+
* These are .stx files that need to be processed by the stx framework.
|
|
6
|
+
* Total: 31 components
|
|
7
|
+
*
|
|
8
|
+
* Components are exported as string identifiers pointing to the component files.
|
|
9
|
+
* The stx framework uses these to resolve and render the components at runtime.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Component name constants - use these when referencing components in stx templates
|
|
13
|
+
export const AlertCard = 'AlertCard'
|
|
14
|
+
export const AnalyticsDashboard = 'AnalyticsDashboard'
|
|
15
|
+
export const AnimatedNumber = 'AnimatedNumber'
|
|
16
|
+
export const BarChart = 'BarChart'
|
|
17
|
+
export const BrowserBreakdown = 'BrowserBreakdown'
|
|
18
|
+
export const CampaignBreakdown = 'CampaignBreakdown'
|
|
19
|
+
export const CountryList = 'CountryList'
|
|
20
|
+
export const DataTable = 'DataTable'
|
|
21
|
+
export const DateRangePicker = 'DateRangePicker'
|
|
22
|
+
export const DeviceBreakdown = 'DeviceBreakdown'
|
|
23
|
+
export const DonutChart = 'DonutChart'
|
|
24
|
+
export const EmptyState = 'EmptyState'
|
|
25
|
+
export const EngagementMetrics = 'EngagementMetrics'
|
|
26
|
+
export const FilterBar = 'FilterBar'
|
|
27
|
+
export const FullAnalyticsDashboard = 'FullAnalyticsDashboard'
|
|
28
|
+
export const FunnelChart = 'FunnelChart'
|
|
29
|
+
export const GoalsPanel = 'GoalsPanel'
|
|
30
|
+
export const HeatmapChart = 'HeatmapChart'
|
|
31
|
+
export const LiveActivityFeed = 'LiveActivityFeed'
|
|
32
|
+
export const MetricComparison = 'MetricComparison'
|
|
33
|
+
export const MiniStats = 'MiniStats'
|
|
34
|
+
export const OSBreakdown = 'OSBreakdown'
|
|
35
|
+
export const PageDetailCard = 'PageDetailCard'
|
|
36
|
+
export const ProgressRing = 'ProgressRing'
|
|
37
|
+
export const RealtimeCounter = 'RealtimeCounter'
|
|
38
|
+
export const SparklineChart = 'SparklineChart'
|
|
39
|
+
export const StatCard = 'StatCard'
|
|
40
|
+
export const ThemeSwitcher = 'ThemeSwitcher'
|
|
41
|
+
export const TimeSeriesChart = 'TimeSeriesChart'
|
|
42
|
+
export const TopList = 'TopList'
|
|
43
|
+
export const TrendIndicator = 'TrendIndicator'
|
|
44
|
+
|
|
45
|
+
// Component registry for dynamic component resolution
|
|
46
|
+
export const componentRegistry: Record<string, string> = {
|
|
47
|
+
AlertCard: './AlertCard.stx',
|
|
48
|
+
AnalyticsDashboard: './AnalyticsDashboard.stx',
|
|
49
|
+
AnimatedNumber: './AnimatedNumber.stx',
|
|
50
|
+
BarChart: './BarChart.stx',
|
|
51
|
+
BrowserBreakdown: './BrowserBreakdown.stx',
|
|
52
|
+
CampaignBreakdown: './CampaignBreakdown.stx',
|
|
53
|
+
CountryList: './CountryList.stx',
|
|
54
|
+
DataTable: './DataTable.stx',
|
|
55
|
+
DateRangePicker: './DateRangePicker.stx',
|
|
56
|
+
DeviceBreakdown: './DeviceBreakdown.stx',
|
|
57
|
+
DonutChart: './DonutChart.stx',
|
|
58
|
+
EmptyState: './EmptyState.stx',
|
|
59
|
+
EngagementMetrics: './EngagementMetrics.stx',
|
|
60
|
+
FilterBar: './FilterBar.stx',
|
|
61
|
+
FullAnalyticsDashboard: './FullAnalyticsDashboard.stx',
|
|
62
|
+
FunnelChart: './FunnelChart.stx',
|
|
63
|
+
GoalsPanel: './GoalsPanel.stx',
|
|
64
|
+
HeatmapChart: './HeatmapChart.stx',
|
|
65
|
+
LiveActivityFeed: './LiveActivityFeed.stx',
|
|
66
|
+
MetricComparison: './MetricComparison.stx',
|
|
67
|
+
MiniStats: './MiniStats.stx',
|
|
68
|
+
OSBreakdown: './OSBreakdown.stx',
|
|
69
|
+
PageDetailCard: './PageDetailCard.stx',
|
|
70
|
+
ProgressRing: './ProgressRing.stx',
|
|
71
|
+
RealtimeCounter: './RealtimeCounter.stx',
|
|
72
|
+
SparklineChart: './SparklineChart.stx',
|
|
73
|
+
StatCard: './StatCard.stx',
|
|
74
|
+
ThemeSwitcher: './ThemeSwitcher.stx',
|
|
75
|
+
TimeSeriesChart: './TimeSeriesChart.stx',
|
|
76
|
+
TopList: './TopList.stx',
|
|
77
|
+
TrendIndicator: './TrendIndicator.stx',
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// List of all component names
|
|
81
|
+
export const allComponents: string[] = Object.keys(componentRegistry)
|
|
82
|
+
|
|
83
|
+
// Function to get component file path
|
|
84
|
+
export function getComponentPath(name: string): string | undefined {
|
|
85
|
+
return componentRegistry[name]
|
|
86
|
+
}
|