react-native-inapp-inspector 1.1.24 → 1.1.25
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/dist/commonjs/components/AnalyticsEventCard.js +94 -88
- package/dist/commonjs/components/ConsoleLogCard.js +49 -25
- package/dist/commonjs/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
- package/dist/commonjs/components/Inspector/AnalyticsFilterModal.js +1002 -0
- package/dist/commonjs/components/Inspector/AnalyticsTab.js +637 -10
- package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -1
- package/dist/commonjs/components/Inspector/BundleTab.js +826 -406
- package/dist/commonjs/components/Inspector/InspectorContext.js +6 -1
- package/dist/commonjs/components/Inspector/InspectorHeader.js +13 -5
- package/dist/commonjs/components/Inspector/LogDetail.js +223 -111
- package/dist/commonjs/components/Inspector/MainScreen.js +1 -1
- package/dist/commonjs/components/Inspector/NetworkDetail.js +131 -81
- package/dist/commonjs/components/Inspector/PerformanceTab.js +20 -10
- package/dist/commonjs/components/NetworkIcons.d.ts +14 -0
- package/dist/commonjs/components/NetworkIcons.js +93 -1
- package/dist/commonjs/components/SegmentedTabs.js +31 -22
- package/dist/commonjs/components/TreeNode.js +13 -12
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/customHooks/bundleAnalyzer.d.ts +5 -1
- package/dist/commonjs/customHooks/bundleAnalyzer.js +497 -126
- package/dist/commonjs/customHooks/consoleLogger.js +83 -45
- package/dist/commonjs/customHooks/networkLogger.js +10 -1
- package/dist/commonjs/customHooks/performanceTracker.d.ts +8 -0
- package/dist/commonjs/customHooks/performanceTracker.js +44 -3
- package/dist/commonjs/decorators/index.d.ts +49 -0
- package/dist/commonjs/decorators/index.js +142 -0
- package/dist/commonjs/helpers/gaAnalyticsRegistry.d.ts +18 -0
- package/dist/commonjs/helpers/gaAnalyticsRegistry.js +174 -0
- package/dist/commonjs/helpers/index.d.ts +7 -4
- package/dist/commonjs/helpers/index.js +102 -64
- package/dist/commonjs/i18n/locales/en.json +51 -4
- package/dist/commonjs/index.d.ts +4 -0
- package/dist/commonjs/index.js +216 -8
- package/dist/commonjs/styles/index.d.ts +4 -1
- package/dist/commonjs/styles/index.js +22 -19
- package/dist/commonjs/types/enums.d.ts +115 -9
- package/dist/commonjs/types/enums.js +103 -2
- package/dist/commonjs/types/interfaces.d.ts +19 -0
- package/dist/esm/components/AnalyticsEventCard.js +95 -89
- package/dist/esm/components/ConsoleLogCard.js +49 -25
- package/dist/esm/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
- package/dist/esm/components/Inspector/AnalyticsFilterModal.js +964 -0
- package/dist/esm/components/Inspector/AnalyticsTab.js +639 -12
- package/dist/esm/components/Inspector/BundleTab.d.ts +1 -1
- package/dist/esm/components/Inspector/BundleTab.js +827 -407
- package/dist/esm/components/Inspector/InspectorContext.js +1 -0
- package/dist/esm/components/Inspector/InspectorHeader.js +14 -6
- package/dist/esm/components/Inspector/LogDetail.js +223 -111
- package/dist/esm/components/Inspector/MainScreen.js +2 -2
- package/dist/esm/components/Inspector/NetworkDetail.js +132 -49
- package/dist/esm/components/Inspector/PerformanceTab.js +20 -10
- package/dist/esm/components/NetworkIcons.d.ts +14 -0
- package/dist/esm/components/NetworkIcons.js +78 -0
- package/dist/esm/components/SegmentedTabs.js +31 -22
- package/dist/esm/components/TreeNode.js +13 -12
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/customHooks/bundleAnalyzer.d.ts +5 -1
- package/dist/esm/customHooks/bundleAnalyzer.js +493 -125
- package/dist/esm/customHooks/consoleLogger.js +83 -45
- package/dist/esm/customHooks/networkLogger.js +10 -1
- package/dist/esm/customHooks/performanceTracker.d.ts +8 -0
- package/dist/esm/customHooks/performanceTracker.js +38 -1
- package/dist/esm/decorators/index.d.ts +49 -0
- package/dist/esm/decorators/index.js +137 -0
- package/dist/esm/helpers/gaAnalyticsRegistry.d.ts +18 -0
- package/dist/esm/helpers/gaAnalyticsRegistry.js +169 -0
- package/dist/esm/helpers/index.d.ts +7 -4
- package/dist/esm/helpers/index.js +100 -63
- package/dist/esm/i18n/locales/en.json +51 -4
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +190 -8
- package/dist/esm/styles/index.d.ts +4 -1
- package/dist/esm/styles/index.js +22 -19
- package/dist/esm/types/enums.d.ts +115 -9
- package/dist/esm/types/enums.js +102 -3
- package/dist/esm/types/interfaces.d.ts +19 -0
- package/package.json +1 -1
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import React, { useCallback } from 'react';
|
|
1
|
+
import React, { useCallback, useState, useMemo } from 'react';
|
|
2
2
|
import { FlatList, Pressable, Text, TextInput, TouchableOpacity, View, } from 'react-native';
|
|
3
3
|
import { animateNextLayout, useInspector } from './InspectorContext';
|
|
4
4
|
import AnalyticsEventCard from '../AnalyticsEventCard';
|
|
5
5
|
import AnalyticsDetail from '../AnalyticsDetail';
|
|
6
|
+
import AnalyticsFilterModal from './AnalyticsFilterModal';
|
|
6
7
|
import EndOfListFooter from '../EndOfListFooter';
|
|
7
8
|
import styles from '../../styles';
|
|
8
9
|
import { AppColors } from '../../styles/AppColors';
|
|
10
|
+
import { AppFonts } from '../../styles/AppFonts';
|
|
11
|
+
import TouchableScale from '../TouchableScale';
|
|
9
12
|
import { getCurrentUserId, getCurrentUserProperties, getDefaultEventParameters, getCollectionEnabled, } from '../../customHooks/analyticsLogger';
|
|
10
|
-
import {
|
|
13
|
+
import { getEventCategory, } from '../../helpers';
|
|
14
|
+
import { SearchIcon, ClearIcon, TrashIcon, EmptyRadarIcon, HeaderPauseIcon, FilterIcon, CartIcon, GlobeIcon, BoltIcon, SparkleIcon, TargetGoalIcon, PinIcon, MoneyIcon, } from '../NetworkIcons';
|
|
11
15
|
const AnalyticsHeader = React.memo(() => {
|
|
12
|
-
const { filteredAnalyticsEvents, analyticsHeaderExpanded, setAnalyticsHeaderExpanded, } = useInspector();
|
|
16
|
+
const { analyticsEvents, filteredAnalyticsEvents, analyticsFilters, setAnalyticsFilters, analyticsHeaderExpanded, setAnalyticsHeaderExpanded, } = useInspector();
|
|
13
17
|
const userId = getCurrentUserId();
|
|
14
18
|
const userProperties = getCurrentUserProperties();
|
|
15
19
|
const defaultParams = getDefaultEventParameters();
|
|
@@ -17,34 +21,628 @@ const AnalyticsHeader = React.memo(() => {
|
|
|
17
21
|
const hasUserProps = Object.keys(userProperties).length > 0;
|
|
18
22
|
const hasDefaultParams = Object.keys(defaultParams).length > 0;
|
|
19
23
|
const totalEvents = filteredAnalyticsEvents.length;
|
|
24
|
+
// Category counts & breakdown across all session events
|
|
25
|
+
const categoryStats = useMemo(() => {
|
|
26
|
+
let ecommerce = 0;
|
|
27
|
+
let page_view = 0;
|
|
28
|
+
let system = 0;
|
|
29
|
+
let custom = 0;
|
|
30
|
+
let totalRevenue = 0;
|
|
31
|
+
for (const e of analyticsEvents) {
|
|
32
|
+
const cat = getEventCategory(e.name);
|
|
33
|
+
const c = e.count || 1;
|
|
34
|
+
if (cat === 'ecommerce') {
|
|
35
|
+
ecommerce += c;
|
|
36
|
+
const val = e.params?.value ?? e.params?.price;
|
|
37
|
+
if (val && !isNaN(Number(val))) {
|
|
38
|
+
totalRevenue += Number(val);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else if (cat === 'page_view') {
|
|
42
|
+
page_view += c;
|
|
43
|
+
}
|
|
44
|
+
else if (cat === 'system') {
|
|
45
|
+
system += c;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
custom += c;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const total = ecommerce + page_view + system + custom || 1;
|
|
52
|
+
return {
|
|
53
|
+
ecommerce,
|
|
54
|
+
page_view,
|
|
55
|
+
system,
|
|
56
|
+
custom,
|
|
57
|
+
totalRevenue,
|
|
58
|
+
ecommercePct: Math.round((ecommerce / total) * 100),
|
|
59
|
+
page_viewPct: Math.round((page_view / total) * 100),
|
|
60
|
+
systemPct: Math.round((system / total) * 100),
|
|
61
|
+
customPct: Math.round((custom / total) * 100),
|
|
62
|
+
totalCount: total,
|
|
63
|
+
};
|
|
64
|
+
}, [analyticsEvents]);
|
|
65
|
+
// Top Screen Views Leaderboard (GA4 Realtime Card)
|
|
66
|
+
const topScreens = useMemo(() => {
|
|
67
|
+
const screenMap = new Map();
|
|
68
|
+
for (const e of filteredAnalyticsEvents) {
|
|
69
|
+
const scr = e.screenName ||
|
|
70
|
+
e.params?.firebase_screen ||
|
|
71
|
+
e.params?.screen_name ||
|
|
72
|
+
e.params?.firebase_screen_class ||
|
|
73
|
+
e.screenClass;
|
|
74
|
+
if (scr && typeof scr === 'string' && scr.trim() !== '') {
|
|
75
|
+
screenMap.set(scr, (screenMap.get(scr) || 0) + (e.count || 1));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const arr = Array.from(screenMap.entries()).sort((a, b) => b[1] - a[1]);
|
|
79
|
+
const maxVal = arr.length > 0 ? arr[0][1] : 1;
|
|
80
|
+
return arr.slice(0, 3).map(([name, count]) => ({
|
|
81
|
+
name,
|
|
82
|
+
count,
|
|
83
|
+
pct: Math.round((count / (filteredAnalyticsEvents.length || 1)) * 100),
|
|
84
|
+
barWidthPct: Math.round((count / maxVal) * 100),
|
|
85
|
+
}));
|
|
86
|
+
}, [filteredAnalyticsEvents]);
|
|
87
|
+
// Toggle category filter from top button group
|
|
88
|
+
const toggleCategoryFilter = (cat) => {
|
|
89
|
+
animateNextLayout();
|
|
90
|
+
setAnalyticsFilters(prev => {
|
|
91
|
+
const isSelected = !prev.categories.has('all') && prev.categories.has(cat);
|
|
92
|
+
if (isSelected) {
|
|
93
|
+
return { ...prev, categories: new Set(['all']) };
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
return { ...prev, categories: new Set([cat]) };
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
// 12-Bucket GA4 Realtime Stacked Histogram Graph (-30m / 30s to NOW)
|
|
101
|
+
const BUCKET_COUNT = 12;
|
|
102
|
+
const chartHeight = 56;
|
|
103
|
+
const histogramBars = useMemo(() => {
|
|
104
|
+
const buckets = Array.from({ length: BUCKET_COUNT }, () => ({
|
|
105
|
+
ecommerce: 0,
|
|
106
|
+
page_view: 0,
|
|
107
|
+
system: 0,
|
|
108
|
+
custom: 0,
|
|
109
|
+
total: 0,
|
|
110
|
+
}));
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
const windowMs = 60000; // 60s window
|
|
113
|
+
const bucketDuration = windowMs / BUCKET_COUNT;
|
|
114
|
+
for (const e of filteredAnalyticsEvents) {
|
|
115
|
+
const age = now - e.timestamp;
|
|
116
|
+
if (age >= 0 && age < windowMs) {
|
|
117
|
+
const bucketIdx = Math.floor((windowMs - 1 - age) / bucketDuration);
|
|
118
|
+
if (bucketIdx >= 0 && bucketIdx < BUCKET_COUNT) {
|
|
119
|
+
const cat = getEventCategory(e.name);
|
|
120
|
+
const c = e.count || 1;
|
|
121
|
+
buckets[bucketIdx].total += c;
|
|
122
|
+
if (cat === 'ecommerce')
|
|
123
|
+
buckets[bucketIdx].ecommerce += c;
|
|
124
|
+
else if (cat === 'page_view')
|
|
125
|
+
buckets[bucketIdx].page_view += c;
|
|
126
|
+
else if (cat === 'system')
|
|
127
|
+
buckets[bucketIdx].system += c;
|
|
128
|
+
else
|
|
129
|
+
buckets[bucketIdx].custom += c;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const maxVal = Math.max(...buckets.map(b => b.total), 4);
|
|
134
|
+
return buckets.map((b, idx) => {
|
|
135
|
+
const totalH = Math.max((b.total / maxVal) * (chartHeight - 14), b.total > 0 ? 8 : 2.5);
|
|
136
|
+
const ecomH = b.total > 0 ? (b.ecommerce / b.total) * totalH : 0;
|
|
137
|
+
const screenH = b.total > 0 ? (b.page_view / b.total) * totalH : 0;
|
|
138
|
+
const sysH = b.total > 0 ? (b.system / b.total) * totalH : 0;
|
|
139
|
+
const custH = b.total > 0 ? (b.custom / b.total) * totalH : totalH;
|
|
140
|
+
return {
|
|
141
|
+
index: idx,
|
|
142
|
+
total: b.total,
|
|
143
|
+
totalHeight: totalH,
|
|
144
|
+
ecomHeight: ecomH,
|
|
145
|
+
screenHeight: screenH,
|
|
146
|
+
sysHeight: sysH,
|
|
147
|
+
custHeight: custH,
|
|
148
|
+
isLatest: idx === BUCKET_COUNT - 1,
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
}, [filteredAnalyticsEvents, chartHeight]);
|
|
20
152
|
return (<View style={styles.analyticsHeaderCard}>
|
|
21
|
-
|
|
153
|
+
{/* ── Top Header Row (GA4 Style) ─────────────────────────────────────── */}
|
|
154
|
+
<View style={styles.analyticsHeaderTop}>
|
|
22
155
|
<View style={{ flex: 1 }}>
|
|
23
156
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
24
|
-
<Text style={styles.analyticsHeaderTitle}>
|
|
157
|
+
<Text style={styles.analyticsHeaderTitle}>Users in last 30 minutes</Text>
|
|
25
158
|
<View style={[
|
|
26
159
|
styles.statusDot,
|
|
27
|
-
{
|
|
160
|
+
{
|
|
161
|
+
backgroundColor: isTrackingEnabled
|
|
162
|
+
? AppColors.greenColor
|
|
163
|
+
: AppColors.errorColor,
|
|
164
|
+
},
|
|
28
165
|
]}/>
|
|
29
|
-
<Text style={
|
|
30
|
-
|
|
166
|
+
<Text style={[
|
|
167
|
+
styles.statusText,
|
|
168
|
+
{
|
|
169
|
+
color: isTrackingEnabled
|
|
170
|
+
? AppColors.greenColor
|
|
171
|
+
: AppColors.errorColor,
|
|
172
|
+
fontFamily: AppFonts.interBold,
|
|
173
|
+
},
|
|
174
|
+
]}>
|
|
175
|
+
{isTrackingEnabled ? 'Realtime' : 'Paused'}
|
|
31
176
|
</Text>
|
|
32
177
|
</View>
|
|
33
178
|
<Text style={styles.analyticsHeaderSubtitle} numberOfLines={1}>
|
|
34
|
-
{userId ? `User
|
|
179
|
+
{userId ? `User: ${userId}` : 'Anonymous Session'}
|
|
35
180
|
</Text>
|
|
36
181
|
</View>
|
|
37
|
-
|
|
182
|
+
|
|
38
183
|
<TouchableOpacity style={styles.analyticsHeaderToggle} activeOpacity={0.7} onPress={() => {
|
|
39
184
|
animateNextLayout();
|
|
40
185
|
setAnalyticsHeaderExpanded(!analyticsHeaderExpanded);
|
|
41
186
|
}}>
|
|
42
187
|
<Text style={styles.analyticsHeaderToggleText}>
|
|
43
|
-
{analyticsHeaderExpanded ? '
|
|
188
|
+
{analyticsHeaderExpanded ? 'Hide Info' : 'Session Info'}
|
|
44
189
|
</Text>
|
|
45
190
|
</TouchableOpacity>
|
|
46
191
|
</View>
|
|
47
192
|
|
|
193
|
+
{/* ── GA4 Realtime Metric Hero & Activity Chart ─────────────────────── */}
|
|
194
|
+
<View style={{
|
|
195
|
+
marginTop: 10,
|
|
196
|
+
marginBottom: 8,
|
|
197
|
+
backgroundColor: AppColors.grayBackground,
|
|
198
|
+
borderRadius: 10,
|
|
199
|
+
borderWidth: 1,
|
|
200
|
+
borderColor: AppColors.dividerColor,
|
|
201
|
+
padding: 10,
|
|
202
|
+
}}>
|
|
203
|
+
{/* Hero Metric Row */}
|
|
204
|
+
<View style={{
|
|
205
|
+
flexDirection: 'row',
|
|
206
|
+
justifyContent: 'space-between',
|
|
207
|
+
alignItems: 'flex-start',
|
|
208
|
+
marginBottom: 8,
|
|
209
|
+
}}>
|
|
210
|
+
<View>
|
|
211
|
+
<View style={{ flexDirection: 'row', alignItems: 'baseline', gap: 6 }}>
|
|
212
|
+
<Text style={{
|
|
213
|
+
fontFamily: AppFonts.interBold,
|
|
214
|
+
fontSize: 22,
|
|
215
|
+
color: AppColors.primaryBlack,
|
|
216
|
+
lineHeight: 26,
|
|
217
|
+
}}>
|
|
218
|
+
{totalEvents}
|
|
219
|
+
</Text>
|
|
220
|
+
<Text style={{
|
|
221
|
+
fontFamily: AppFonts.interMedium,
|
|
222
|
+
fontSize: 10.5,
|
|
223
|
+
color: AppColors.grayTextWeak,
|
|
224
|
+
}}>
|
|
225
|
+
events in window
|
|
226
|
+
</Text>
|
|
227
|
+
</View>
|
|
228
|
+
</View>
|
|
229
|
+
|
|
230
|
+
{/* Realtime Badges */}
|
|
231
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
|
|
232
|
+
{categoryStats.totalRevenue > 0 && (<View style={{
|
|
233
|
+
flexDirection: 'row',
|
|
234
|
+
alignItems: 'center',
|
|
235
|
+
gap: 3,
|
|
236
|
+
backgroundColor: `${AppColors.emerald600}15`,
|
|
237
|
+
paddingHorizontal: 6,
|
|
238
|
+
paddingVertical: 2.5,
|
|
239
|
+
borderRadius: 4,
|
|
240
|
+
borderWidth: 1,
|
|
241
|
+
borderColor: `${AppColors.emerald600}30`,
|
|
242
|
+
}}>
|
|
243
|
+
<MoneyIcon color={AppColors.emerald600} size={10}/>
|
|
244
|
+
<Text style={{
|
|
245
|
+
fontFamily: AppFonts.interBold,
|
|
246
|
+
fontSize: 9.5,
|
|
247
|
+
color: AppColors.emerald600,
|
|
248
|
+
}}>
|
|
249
|
+
${categoryStats.totalRevenue.toFixed(2)}
|
|
250
|
+
</Text>
|
|
251
|
+
</View>)}
|
|
252
|
+
<View style={{
|
|
253
|
+
flexDirection: 'row',
|
|
254
|
+
alignItems: 'center',
|
|
255
|
+
gap: 4,
|
|
256
|
+
backgroundColor: `${AppColors.brandPurple}14`,
|
|
257
|
+
paddingHorizontal: 6,
|
|
258
|
+
paddingVertical: 2.5,
|
|
259
|
+
borderRadius: 4,
|
|
260
|
+
borderWidth: 1,
|
|
261
|
+
borderColor: `${AppColors.brandPurple}2E`,
|
|
262
|
+
}}>
|
|
263
|
+
<TargetGoalIcon color={AppColors.brandPurple} size={10}/>
|
|
264
|
+
<Text style={{
|
|
265
|
+
fontFamily: AppFonts.interBold,
|
|
266
|
+
fontSize: 9.5,
|
|
267
|
+
color: AppColors.brandPurple,
|
|
268
|
+
}}>
|
|
269
|
+
Live Telemetry
|
|
270
|
+
</Text>
|
|
271
|
+
</View>
|
|
272
|
+
</View>
|
|
273
|
+
</View>
|
|
274
|
+
|
|
275
|
+
{/* GA4 Stacked Realtime Histogram Bar Graph */}
|
|
276
|
+
<View style={{
|
|
277
|
+
height: chartHeight,
|
|
278
|
+
flexDirection: 'row',
|
|
279
|
+
alignItems: 'flex-end',
|
|
280
|
+
justifyContent: 'space-between',
|
|
281
|
+
paddingHorizontal: 2,
|
|
282
|
+
paddingBottom: 2,
|
|
283
|
+
borderBottomWidth: 1,
|
|
284
|
+
borderBottomColor: AppColors.dividerColor,
|
|
285
|
+
}}>
|
|
286
|
+
{histogramBars.map(bar => (<View key={bar.index} style={{
|
|
287
|
+
flex: 1,
|
|
288
|
+
alignItems: 'center',
|
|
289
|
+
justifyContent: 'flex-end',
|
|
290
|
+
height: '100%',
|
|
291
|
+
marginHorizontal: 1.5,
|
|
292
|
+
}}>
|
|
293
|
+
{bar.total > 0 && (<Text style={{
|
|
294
|
+
fontFamily: AppFonts.interBold,
|
|
295
|
+
fontSize: 7.5,
|
|
296
|
+
color: bar.isLatest
|
|
297
|
+
? AppColors.brandPurple
|
|
298
|
+
: AppColors.grayTextWeak,
|
|
299
|
+
marginBottom: 2,
|
|
300
|
+
}}>
|
|
301
|
+
{bar.total}
|
|
302
|
+
</Text>)}
|
|
303
|
+
{/* Stacked Category Segments Column */}
|
|
304
|
+
<View style={{
|
|
305
|
+
width: '100%',
|
|
306
|
+
maxWidth: 15,
|
|
307
|
+
height: bar.totalHeight,
|
|
308
|
+
borderRadius: 3,
|
|
309
|
+
overflow: 'hidden',
|
|
310
|
+
backgroundColor: bar.total > 0
|
|
311
|
+
? `${AppColors.grayBorderSecondary}40`
|
|
312
|
+
: `${AppColors.grayBorderSecondary}40`,
|
|
313
|
+
}}>
|
|
314
|
+
{bar.total > 0 ? (<>
|
|
315
|
+
{bar.ecomHeight > 0 && (<View style={{
|
|
316
|
+
height: bar.ecomHeight,
|
|
317
|
+
backgroundColor: AppColors.amber500,
|
|
318
|
+
}}/>)}
|
|
319
|
+
{bar.screenHeight > 0 && (<View style={{
|
|
320
|
+
height: bar.screenHeight,
|
|
321
|
+
backgroundColor: AppColors.sky500,
|
|
322
|
+
}}/>)}
|
|
323
|
+
{bar.sysHeight > 0 && (<View style={{
|
|
324
|
+
height: bar.sysHeight,
|
|
325
|
+
backgroundColor: AppColors.purple,
|
|
326
|
+
}}/>)}
|
|
327
|
+
{bar.custHeight > 0 && (<View style={{
|
|
328
|
+
height: bar.custHeight,
|
|
329
|
+
backgroundColor: AppColors.brandPurple,
|
|
330
|
+
}}/>)}
|
|
331
|
+
</>) : (<View style={{
|
|
332
|
+
height: 2.5,
|
|
333
|
+
backgroundColor: `${AppColors.grayBorderSecondary}80`,
|
|
334
|
+
}}/>)}
|
|
335
|
+
</View>
|
|
336
|
+
</View>))}
|
|
337
|
+
</View>
|
|
338
|
+
|
|
339
|
+
{/* Timeline Axis Labels */}
|
|
340
|
+
<View style={{
|
|
341
|
+
flexDirection: 'row',
|
|
342
|
+
justifyContent: 'space-between',
|
|
343
|
+
marginTop: 4,
|
|
344
|
+
paddingHorizontal: 2,
|
|
345
|
+
}}>
|
|
346
|
+
<Text style={{
|
|
347
|
+
fontFamily: AppFonts.interMedium,
|
|
348
|
+
fontSize: 8,
|
|
349
|
+
color: AppColors.grayTextWeak,
|
|
350
|
+
}}>
|
|
351
|
+
-30 min
|
|
352
|
+
</Text>
|
|
353
|
+
<Text style={{
|
|
354
|
+
fontFamily: AppFonts.interMedium,
|
|
355
|
+
fontSize: 8,
|
|
356
|
+
color: AppColors.grayTextWeak,
|
|
357
|
+
}}>
|
|
358
|
+
-20 min
|
|
359
|
+
</Text>
|
|
360
|
+
<Text style={{
|
|
361
|
+
fontFamily: AppFonts.interMedium,
|
|
362
|
+
fontSize: 8,
|
|
363
|
+
color: AppColors.grayTextWeak,
|
|
364
|
+
}}>
|
|
365
|
+
-10 min
|
|
366
|
+
</Text>
|
|
367
|
+
<Text style={{
|
|
368
|
+
fontFamily: AppFonts.interBold,
|
|
369
|
+
fontSize: 8,
|
|
370
|
+
color: AppColors.brandPurple,
|
|
371
|
+
}}>
|
|
372
|
+
NOW
|
|
373
|
+
</Text>
|
|
374
|
+
</View>
|
|
375
|
+
</View>
|
|
376
|
+
|
|
377
|
+
{/* ── Category Breakdown Distribution Bar & Interactive Filter Buttons ─ */}
|
|
378
|
+
<View style={{ marginTop: 2, marginBottom: 4 }}>
|
|
379
|
+
{/* Multi-color Proportional Segmented Bar */}
|
|
380
|
+
<View style={{
|
|
381
|
+
height: 5,
|
|
382
|
+
borderRadius: 2.5,
|
|
383
|
+
backgroundColor: `${AppColors.grayBorderSecondary}40`,
|
|
384
|
+
flexDirection: 'row',
|
|
385
|
+
overflow: 'hidden',
|
|
386
|
+
marginBottom: 8,
|
|
387
|
+
}}>
|
|
388
|
+
{categoryStats.ecommerce > 0 && (<View style={{
|
|
389
|
+
flex: categoryStats.ecommerce,
|
|
390
|
+
backgroundColor: AppColors.amber500,
|
|
391
|
+
}}/>)}
|
|
392
|
+
{categoryStats.page_view > 0 && (<View style={{
|
|
393
|
+
flex: categoryStats.page_view,
|
|
394
|
+
backgroundColor: AppColors.sky500,
|
|
395
|
+
}}/>)}
|
|
396
|
+
{categoryStats.system > 0 && (<View style={{
|
|
397
|
+
flex: categoryStats.system,
|
|
398
|
+
backgroundColor: AppColors.purple,
|
|
399
|
+
}}/>)}
|
|
400
|
+
{categoryStats.custom > 0 && (<View style={{
|
|
401
|
+
flex: categoryStats.custom,
|
|
402
|
+
backgroundColor: AppColors.brandPurple,
|
|
403
|
+
}}/>)}
|
|
404
|
+
</View>
|
|
405
|
+
|
|
406
|
+
{/* 4 Interactive Category Filter Buttons */}
|
|
407
|
+
<View style={{
|
|
408
|
+
flexDirection: 'row',
|
|
409
|
+
flexWrap: 'wrap',
|
|
410
|
+
gap: 6,
|
|
411
|
+
}}>
|
|
412
|
+
{/* Ecommerce Button */}
|
|
413
|
+
{(() => {
|
|
414
|
+
const isSelected = !analyticsFilters.categories.has('all') &&
|
|
415
|
+
analyticsFilters.categories.has('ecommerce');
|
|
416
|
+
return (<TouchableScale onPress={() => toggleCategoryFilter('ecommerce')} style={[
|
|
417
|
+
{
|
|
418
|
+
flexDirection: 'row',
|
|
419
|
+
alignItems: 'center',
|
|
420
|
+
gap: 4.5,
|
|
421
|
+
backgroundColor: isSelected
|
|
422
|
+
? AppColors.amber500
|
|
423
|
+
: `${AppColors.amber500}14`,
|
|
424
|
+
paddingHorizontal: 7,
|
|
425
|
+
paddingVertical: 4.5,
|
|
426
|
+
borderRadius: 6,
|
|
427
|
+
borderWidth: 1,
|
|
428
|
+
borderColor: isSelected
|
|
429
|
+
? AppColors.amber500
|
|
430
|
+
: `${AppColors.amber500}35`,
|
|
431
|
+
},
|
|
432
|
+
isSelected && {
|
|
433
|
+
shadowColor: AppColors.amber500,
|
|
434
|
+
shadowOffset: { width: 0, height: 1 },
|
|
435
|
+
shadowOpacity: 0.25,
|
|
436
|
+
shadowRadius: 2,
|
|
437
|
+
elevation: 2,
|
|
438
|
+
},
|
|
439
|
+
]}>
|
|
440
|
+
<CartIcon color={isSelected ? AppColors.white : AppColors.amber700} size={11}/>
|
|
441
|
+
<Text style={{
|
|
442
|
+
fontFamily: AppFonts.interBold,
|
|
443
|
+
fontSize: 10,
|
|
444
|
+
color: isSelected ? AppColors.white : AppColors.amber700,
|
|
445
|
+
}}>
|
|
446
|
+
Ecommerce: {categoryStats.ecommerce} ({categoryStats.ecommercePct}%)
|
|
447
|
+
</Text>
|
|
448
|
+
</TouchableScale>);
|
|
449
|
+
})()}
|
|
450
|
+
|
|
451
|
+
{/* Screens Button */}
|
|
452
|
+
{(() => {
|
|
453
|
+
const isSelected = !analyticsFilters.categories.has('all') &&
|
|
454
|
+
analyticsFilters.categories.has('page_view');
|
|
455
|
+
return (<TouchableScale onPress={() => toggleCategoryFilter('page_view')} style={[
|
|
456
|
+
{
|
|
457
|
+
flexDirection: 'row',
|
|
458
|
+
alignItems: 'center',
|
|
459
|
+
gap: 4.5,
|
|
460
|
+
backgroundColor: isSelected
|
|
461
|
+
? AppColors.sky600
|
|
462
|
+
: `${AppColors.sky500}14`,
|
|
463
|
+
paddingHorizontal: 7,
|
|
464
|
+
paddingVertical: 4.5,
|
|
465
|
+
borderRadius: 6,
|
|
466
|
+
borderWidth: 1,
|
|
467
|
+
borderColor: isSelected
|
|
468
|
+
? AppColors.sky600
|
|
469
|
+
: `${AppColors.sky500}35`,
|
|
470
|
+
},
|
|
471
|
+
isSelected && {
|
|
472
|
+
shadowColor: AppColors.sky600,
|
|
473
|
+
shadowOffset: { width: 0, height: 1 },
|
|
474
|
+
shadowOpacity: 0.25,
|
|
475
|
+
shadowRadius: 2,
|
|
476
|
+
elevation: 2,
|
|
477
|
+
},
|
|
478
|
+
]}>
|
|
479
|
+
<GlobeIcon color={isSelected ? AppColors.white : AppColors.sky600} size={11}/>
|
|
480
|
+
<Text style={{
|
|
481
|
+
fontFamily: AppFonts.interBold,
|
|
482
|
+
fontSize: 10,
|
|
483
|
+
color: isSelected ? AppColors.white : AppColors.sky600,
|
|
484
|
+
}}>
|
|
485
|
+
Screens: {categoryStats.page_view} ({categoryStats.page_viewPct}%)
|
|
486
|
+
</Text>
|
|
487
|
+
</TouchableScale>);
|
|
488
|
+
})()}
|
|
489
|
+
|
|
490
|
+
{/* System Button */}
|
|
491
|
+
{(() => {
|
|
492
|
+
const isSelected = !analyticsFilters.categories.has('all') &&
|
|
493
|
+
analyticsFilters.categories.has('system');
|
|
494
|
+
return (<TouchableScale onPress={() => toggleCategoryFilter('system')} style={[
|
|
495
|
+
{
|
|
496
|
+
flexDirection: 'row',
|
|
497
|
+
alignItems: 'center',
|
|
498
|
+
gap: 4.5,
|
|
499
|
+
backgroundColor: isSelected
|
|
500
|
+
? AppColors.purple
|
|
501
|
+
: `${AppColors.purple}14`,
|
|
502
|
+
paddingHorizontal: 7,
|
|
503
|
+
paddingVertical: 4.5,
|
|
504
|
+
borderRadius: 6,
|
|
505
|
+
borderWidth: 1,
|
|
506
|
+
borderColor: isSelected
|
|
507
|
+
? AppColors.purple
|
|
508
|
+
: `${AppColors.purple}35`,
|
|
509
|
+
},
|
|
510
|
+
isSelected && {
|
|
511
|
+
shadowColor: AppColors.purple,
|
|
512
|
+
shadowOffset: { width: 0, height: 1 },
|
|
513
|
+
shadowOpacity: 0.25,
|
|
514
|
+
shadowRadius: 2,
|
|
515
|
+
elevation: 2,
|
|
516
|
+
},
|
|
517
|
+
]}>
|
|
518
|
+
<BoltIcon color={isSelected ? AppColors.white : AppColors.purple} size={11}/>
|
|
519
|
+
<Text style={{
|
|
520
|
+
fontFamily: AppFonts.interBold,
|
|
521
|
+
fontSize: 10,
|
|
522
|
+
color: isSelected ? AppColors.white : AppColors.purple,
|
|
523
|
+
}}>
|
|
524
|
+
System: {categoryStats.system} ({categoryStats.systemPct}%)
|
|
525
|
+
</Text>
|
|
526
|
+
</TouchableScale>);
|
|
527
|
+
})()}
|
|
528
|
+
|
|
529
|
+
{/* Custom Button */}
|
|
530
|
+
{(() => {
|
|
531
|
+
const isSelected = !analyticsFilters.categories.has('all') &&
|
|
532
|
+
analyticsFilters.categories.has('custom');
|
|
533
|
+
return (<TouchableScale onPress={() => toggleCategoryFilter('custom')} style={[
|
|
534
|
+
{
|
|
535
|
+
flexDirection: 'row',
|
|
536
|
+
alignItems: 'center',
|
|
537
|
+
gap: 4.5,
|
|
538
|
+
backgroundColor: isSelected
|
|
539
|
+
? AppColors.brandPurple
|
|
540
|
+
: `${AppColors.brandPurple}14`,
|
|
541
|
+
paddingHorizontal: 7,
|
|
542
|
+
paddingVertical: 4.5,
|
|
543
|
+
borderRadius: 6,
|
|
544
|
+
borderWidth: 1,
|
|
545
|
+
borderColor: isSelected
|
|
546
|
+
? AppColors.brandPurple
|
|
547
|
+
: `${AppColors.brandPurple}35`,
|
|
548
|
+
},
|
|
549
|
+
isSelected && {
|
|
550
|
+
shadowColor: AppColors.brandPurple,
|
|
551
|
+
shadowOffset: { width: 0, height: 1 },
|
|
552
|
+
shadowOpacity: 0.25,
|
|
553
|
+
shadowRadius: 2,
|
|
554
|
+
elevation: 2,
|
|
555
|
+
},
|
|
556
|
+
]}>
|
|
557
|
+
<SparkleIcon color={isSelected ? AppColors.white : AppColors.brandPurple} size={11}/>
|
|
558
|
+
<Text style={{
|
|
559
|
+
fontFamily: AppFonts.interBold,
|
|
560
|
+
fontSize: 10,
|
|
561
|
+
color: isSelected ? AppColors.white : AppColors.brandPurple,
|
|
562
|
+
}}>
|
|
563
|
+
Custom: {categoryStats.custom} ({categoryStats.customPct}%)
|
|
564
|
+
</Text>
|
|
565
|
+
</TouchableScale>);
|
|
566
|
+
})()}
|
|
567
|
+
</View>
|
|
568
|
+
</View>
|
|
569
|
+
|
|
570
|
+
{/* ── Top Screens / Views Mini Leaderboard (GA4 Realtime Overview) ──── */}
|
|
571
|
+
{topScreens.length > 0 && (<View style={{
|
|
572
|
+
marginTop: 8,
|
|
573
|
+
backgroundColor: `${AppColors.sky500}0A`,
|
|
574
|
+
borderRadius: 8,
|
|
575
|
+
borderWidth: 1,
|
|
576
|
+
borderColor: `${AppColors.sky500}22`,
|
|
577
|
+
padding: 8,
|
|
578
|
+
}}>
|
|
579
|
+
<View style={{
|
|
580
|
+
flexDirection: 'row',
|
|
581
|
+
justifyContent: 'space-between',
|
|
582
|
+
alignItems: 'center',
|
|
583
|
+
marginBottom: 6,
|
|
584
|
+
}}>
|
|
585
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
586
|
+
<PinIcon color={AppColors.sky600} size={11}/>
|
|
587
|
+
<Text style={{
|
|
588
|
+
fontFamily: AppFonts.interBold,
|
|
589
|
+
fontSize: 9.5,
|
|
590
|
+
color: AppColors.sky600,
|
|
591
|
+
letterSpacing: 0.3,
|
|
592
|
+
}}>
|
|
593
|
+
TOP SCREENS (REALTIME)
|
|
594
|
+
</Text>
|
|
595
|
+
</View>
|
|
596
|
+
<Text style={{
|
|
597
|
+
fontFamily: AppFonts.interMedium,
|
|
598
|
+
fontSize: 9,
|
|
599
|
+
color: AppColors.grayTextWeak,
|
|
600
|
+
}}>
|
|
601
|
+
Views
|
|
602
|
+
</Text>
|
|
603
|
+
</View>
|
|
604
|
+
|
|
605
|
+
<View style={{ gap: 4 }}>
|
|
606
|
+
{topScreens.map(scr => (<View key={scr.name}>
|
|
607
|
+
<View style={{
|
|
608
|
+
flexDirection: 'row',
|
|
609
|
+
justifyContent: 'space-between',
|
|
610
|
+
alignItems: 'center',
|
|
611
|
+
marginBottom: 2,
|
|
612
|
+
}}>
|
|
613
|
+
<Text style={{
|
|
614
|
+
fontFamily: AppFonts.interMedium,
|
|
615
|
+
fontSize: 10,
|
|
616
|
+
color: AppColors.primaryBlack,
|
|
617
|
+
}} numberOfLines={1}>
|
|
618
|
+
{scr.name}
|
|
619
|
+
</Text>
|
|
620
|
+
<Text style={{
|
|
621
|
+
fontFamily: AppFonts.interBold,
|
|
622
|
+
fontSize: 9.5,
|
|
623
|
+
color: AppColors.sky600,
|
|
624
|
+
}}>
|
|
625
|
+
{scr.count} ({scr.pct}%)
|
|
626
|
+
</Text>
|
|
627
|
+
</View>
|
|
628
|
+
{/* Mini Horizontal Progress Meter */}
|
|
629
|
+
<View style={{
|
|
630
|
+
height: 3,
|
|
631
|
+
borderRadius: 1.5,
|
|
632
|
+
backgroundColor: `${AppColors.sky500}22`,
|
|
633
|
+
overflow: 'hidden',
|
|
634
|
+
}}>
|
|
635
|
+
<View style={{
|
|
636
|
+
height: '100%',
|
|
637
|
+
width: `${scr.barWidthPct}%`,
|
|
638
|
+
backgroundColor: AppColors.sky500,
|
|
639
|
+
}}/>
|
|
640
|
+
</View>
|
|
641
|
+
</View>))}
|
|
642
|
+
</View>
|
|
643
|
+
</View>)}
|
|
644
|
+
|
|
645
|
+
{/* ── Expandable User Properties & Default Parameters ───────────────── */}
|
|
48
646
|
{analyticsHeaderExpanded && (<>
|
|
49
647
|
{/* Stats Row */}
|
|
50
648
|
<View style={styles.analyticsStatsRow}>
|
|
@@ -88,7 +686,8 @@ const AnalyticsHeader = React.memo(() => {
|
|
|
88
686
|
</View>);
|
|
89
687
|
});
|
|
90
688
|
const AnalyticsTab = React.memo(() => {
|
|
91
|
-
const { filteredAnalyticsEvents, analyticsSearch, setAnalyticsSearch, handleDelete, selectedEvent, setSelectedEvent, newEventIds, logRouteMapRef, isAnalyticsLayoutReady, setIsAnalyticsLayoutReady, isAnalyticsPaused, setIsAnalyticsPaused, } = useInspector();
|
|
689
|
+
const { filteredAnalyticsEvents, analyticsSearch, setAnalyticsSearch, handleDelete, selectedEvent, setSelectedEvent, newEventIds, logRouteMapRef, isAnalyticsLayoutReady, setIsAnalyticsLayoutReady, isAnalyticsPaused, setIsAnalyticsPaused, isAnalyticsFilterApplied, } = useInspector();
|
|
690
|
+
const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);
|
|
92
691
|
const keyExtractor = useCallback((item, index) => item?.id?.toString() ?? index.toString(), []);
|
|
93
692
|
const renderItem = useCallback(({ item, index }) => {
|
|
94
693
|
const prev = filteredAnalyticsEvents[index + 1];
|
|
@@ -154,6 +753,31 @@ const AnalyticsTab = React.memo(() => {
|
|
|
154
753
|
? AppColors.darkOrange
|
|
155
754
|
: AppColors.grayTextStrong} size={16}/>
|
|
156
755
|
</TouchableOpacity>
|
|
756
|
+
|
|
757
|
+
{/* GA Dashboard-style Filter Button */}
|
|
758
|
+
<TouchableOpacity style={[
|
|
759
|
+
styles.toolbarBtn,
|
|
760
|
+
isAnalyticsFilterApplied && {
|
|
761
|
+
borderColor: `${AppColors.brandPurple}60`,
|
|
762
|
+
backgroundColor: `${AppColors.brandPurple}15`,
|
|
763
|
+
},
|
|
764
|
+
]} onPress={() => setIsFilterModalOpen(true)} hitSlop={6}>
|
|
765
|
+
<FilterIcon color={isAnalyticsFilterApplied
|
|
766
|
+
? AppColors.brandPurple
|
|
767
|
+
: AppColors.grayTextStrong} size={15}/>
|
|
768
|
+
{isAnalyticsFilterApplied && (<View style={{
|
|
769
|
+
position: 'absolute',
|
|
770
|
+
top: 2,
|
|
771
|
+
right: 2,
|
|
772
|
+
width: 7,
|
|
773
|
+
height: 7,
|
|
774
|
+
borderRadius: 3.5,
|
|
775
|
+
backgroundColor: AppColors.darkOrange,
|
|
776
|
+
borderWidth: 1,
|
|
777
|
+
borderColor: AppColors.white,
|
|
778
|
+
}}/>)}
|
|
779
|
+
</TouchableOpacity>
|
|
780
|
+
|
|
157
781
|
<TouchableOpacity style={[
|
|
158
782
|
styles.toolbarBtn,
|
|
159
783
|
{
|
|
@@ -166,6 +790,9 @@ const AnalyticsTab = React.memo(() => {
|
|
|
166
790
|
</View>
|
|
167
791
|
</View>)}
|
|
168
792
|
|
|
793
|
+
{/* Filter Modal */}
|
|
794
|
+
<AnalyticsFilterModal visible={isFilterModalOpen} onClose={() => setIsFilterModalOpen(false)}/>
|
|
795
|
+
|
|
169
796
|
<View style={{ flex: 1 }} onLayout={() => setIsAnalyticsLayoutReady(true)}>
|
|
170
797
|
{selectedEvent != null ? (<AnalyticsDetail event={selectedEvent}/>) : isAnalyticsLayoutReady ? (<FlatList data={filteredAnalyticsEvents} keyExtractor={keyExtractor} ListHeaderComponent={AnalyticsHeader} renderItem={renderItem} initialNumToRender={20} maxToRenderPerBatch={20} windowSize={5} removeClippedSubviews={false} ListEmptyComponent={<View style={styles.emptyContainer}>
|
|
171
798
|
<View style={styles.emptyIconWrap}>
|