Skip to content

Commit 5575303

Browse files
committed
RD-T39 Fixing up countly and sentry. Map icon fix for web. OB update.
1 parent 916d341 commit 5575303

14 files changed

Lines changed: 740 additions & 316 deletions

File tree

src/app/(app)/_layout.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { useCallsStore } from '@/stores/calls/store';
3232
import useLockscreenStore from '@/stores/lockscreen/store';
3333
import { useRolesStore } from '@/stores/roles/store';
3434
import { securityStore } from '@/stores/security/store';
35+
import { useSignalRStore } from '@/stores/signalr/signalr-store';
3536

3637
export default function TabLayout() {
3738
const { t } = useTranslation();
@@ -110,22 +111,57 @@ export default function TabLayout() {
110111
const initTimeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Initialization timeout after 30 seconds')), 30000));
111112

112113
const initPromise = (async () => {
114+
// Step 1: Initialize core store (fetches config)
113115
await useCoreStore.getState().init();
114116

115117
logger.info({
116-
message: 'Core store initialized, initializing calls store',
118+
message: 'Core store initialized, fetching full config for SignalR',
117119
context: { platform: Platform.OS },
118120
});
119121

122+
// Step 2: Fetch the full config (needed for SignalR EventingUrl)
123+
await useCoreStore.getState().fetchConfig();
124+
125+
logger.info({
126+
message: 'Config fetched, initializing calls store',
127+
context: { platform: Platform.OS },
128+
});
129+
130+
// Step 3: Initialize calls store
120131
await useCallsStore.getState().init();
121132

122133
logger.info({
123134
message: 'Calls store initialized, getting security rights',
124135
context: { platform: Platform.OS },
125136
});
126137

138+
// Step 4: Get security rights (needed for department ID)
127139
await securityStore.getState().getRights();
128140

141+
logger.info({
142+
message: 'Security rights loaded, connecting SignalR hubs',
143+
context: { platform: Platform.OS },
144+
});
145+
146+
// Step 5: Connect SignalR hubs after all core data is loaded
147+
const signalRStore = useSignalRStore.getState();
148+
try {
149+
await Promise.all([
150+
signalRStore.connectUpdateHub(),
151+
signalRStore.connectGeolocationHub(),
152+
]);
153+
logger.info({
154+
message: 'SignalR hubs connected successfully',
155+
context: { platform: Platform.OS },
156+
});
157+
} catch (signalRError) {
158+
// Log SignalR connection errors but don't fail initialization
159+
logger.error({
160+
message: 'Failed to connect SignalR hubs during initialization',
161+
context: { error: signalRError, platform: Platform.OS },
162+
});
163+
}
164+
129165
hasInitialized.current = true;
130166

131167
logger.info({

src/app/(app)/home.web.tsx

Lines changed: 110 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useFocusEffect } from '@react-navigation/native';
22
import { type Href, router } from 'expo-router';
3-
import React, { useCallback, useEffect, useMemo, useState } from 'react';
3+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
44
import { useTranslation } from 'react-i18next';
55
import { ScrollView, StyleSheet, useWindowDimensions, View } from 'react-native';
66

@@ -21,7 +21,7 @@ import { useDispatchConsoleStore } from '@/stores/dispatch/dispatch-console-stor
2121
import { useHomeStore } from '@/stores/home/home-store';
2222
import { useNotesStore } from '@/stores/notes/store';
2323
import { usePersonnelStore } from '@/stores/personnel/store';
24-
import { useSignalRStore } from '@/stores/signalr/signalr-store';
24+
import { type SignalREventType, useSignalRStore } from '@/stores/signalr/signalr-store';
2525
import { useUnitsStore } from '@/stores/units/store';
2626

2727
export default function DispatchConsoleWeb() {
@@ -38,7 +38,15 @@ export default function DispatchConsoleWeb() {
3838
const { units, isLoading: unitsLoading, fetchUnits } = useUnitsStore();
3939
const { personnel, isLoading: personnelLoading, fetchPersonnel } = usePersonnelStore();
4040
const { notes, isLoading: notesLoading, fetchNotes } = useNotesStore();
41-
const { lastUpdateTimestamp } = useSignalRStore();
41+
42+
// SignalR store - subscribe to specific event timestamps
43+
const {
44+
lastEventType,
45+
lastPersonnelUpdateTimestamp,
46+
lastUnitsUpdateTimestamp,
47+
lastCallsUpdateTimestamp,
48+
isUpdateHubConnected,
49+
} = useSignalRStore();
4250

4351
// Dispatch console store
4452
const {
@@ -71,6 +79,11 @@ export default function DispatchConsoleWeb() {
7179
const [currentTime, setCurrentTime] = useState(new Date().toLocaleTimeString('en-US', { hour12: false }));
7280
const [isAddingNote, setIsAddingNote] = useState(false);
7381

82+
// Track previous timestamps to detect changes
83+
const prevPersonnelTimestamp = useRef(0);
84+
const prevUnitsTimestamp = useRef(0);
85+
const prevCallsTimestamp = useRef(0);
86+
7487
// Update time every second
7588
useEffect(() => {
7689
const timer = setInterval(() => {
@@ -123,21 +136,107 @@ export default function DispatchConsoleWeb() {
123136
}, [trackEvent, isLandscape, isTablet])
124137
);
125138

126-
// Listen for SignalR updates and refresh data
139+
// Helper function to get event description for activity log
140+
const getEventDescription = (eventType: SignalREventType | null): string => {
141+
switch (eventType) {
142+
case 'personnelStatusUpdated':
143+
return t('dispatch.personnel_status_updated');
144+
case 'personnelStaffingUpdated':
145+
return t('dispatch.personnel_staffing_updated');
146+
case 'unitStatusUpdated':
147+
return t('dispatch.unit_status_updated');
148+
case 'callsUpdated':
149+
return t('dispatch.calls_updated');
150+
case 'callAdded':
151+
return t('dispatch.call_added');
152+
case 'callClosed':
153+
return t('dispatch.call_closed');
154+
default:
155+
return t('dispatch.data_refreshed');
156+
}
157+
};
158+
159+
// Listen for SignalR personnel updates
127160
useEffect(() => {
128-
if (lastUpdateTimestamp > 0) {
161+
if (lastPersonnelUpdateTimestamp > 0 && lastPersonnelUpdateTimestamp !== prevPersonnelTimestamp.current) {
162+
prevPersonnelTimestamp.current = lastPersonnelUpdateTimestamp;
163+
164+
logger.info({
165+
message: 'SignalR: Personnel update received, refreshing personnel data',
166+
context: { eventType: lastEventType, timestamp: lastPersonnelUpdateTimestamp },
167+
});
168+
129169
addActivityLogEntry({
130-
type: 'system',
131-
action: t('dispatch.system_update'),
132-
description: t('dispatch.data_refreshed'),
170+
type: 'personnel',
171+
action: t('dispatch.signalr_update'),
172+
description: getEventDescription(lastEventType),
133173
});
134174

135-
fetchCalls();
136-
fetchUnits();
137175
fetchPersonnel();
138176
}
139177
// eslint-disable-next-line react-hooks/exhaustive-deps
140-
}, [lastUpdateTimestamp]);
178+
}, [lastPersonnelUpdateTimestamp]);
179+
180+
// Listen for SignalR units updates
181+
useEffect(() => {
182+
if (lastUnitsUpdateTimestamp > 0 && lastUnitsUpdateTimestamp !== prevUnitsTimestamp.current) {
183+
prevUnitsTimestamp.current = lastUnitsUpdateTimestamp;
184+
185+
logger.info({
186+
message: 'SignalR: Units update received, refreshing units data',
187+
context: { eventType: lastEventType, timestamp: lastUnitsUpdateTimestamp },
188+
});
189+
190+
addActivityLogEntry({
191+
type: 'unit',
192+
action: t('dispatch.signalr_update'),
193+
description: getEventDescription(lastEventType),
194+
});
195+
196+
fetchUnits();
197+
}
198+
// eslint-disable-next-line react-hooks/exhaustive-deps
199+
}, [lastUnitsUpdateTimestamp]);
200+
201+
// Listen for SignalR calls updates
202+
useEffect(() => {
203+
if (lastCallsUpdateTimestamp > 0 && lastCallsUpdateTimestamp !== prevCallsTimestamp.current) {
204+
prevCallsTimestamp.current = lastCallsUpdateTimestamp;
205+
206+
logger.info({
207+
message: 'SignalR: Calls update received, refreshing calls data',
208+
context: { eventType: lastEventType, timestamp: lastCallsUpdateTimestamp },
209+
});
210+
211+
addActivityLogEntry({
212+
type: 'call',
213+
action: t('dispatch.signalr_update'),
214+
description: getEventDescription(lastEventType),
215+
});
216+
217+
fetchCalls();
218+
// Also refresh notes if we have a selected call (call data may have changed)
219+
if (selectedCallId) {
220+
fetchNotes();
221+
}
222+
}
223+
// eslint-disable-next-line react-hooks/exhaustive-deps
224+
}, [lastCallsUpdateTimestamp]);
225+
226+
// Log when SignalR hub connection status changes
227+
useEffect(() => {
228+
if (isUpdateHubConnected) {
229+
logger.info({
230+
message: 'SignalR Update Hub connected - dispatch console ready for real-time updates',
231+
});
232+
addActivityLogEntry({
233+
type: 'system',
234+
action: t('dispatch.signalr_connected'),
235+
description: t('dispatch.realtime_updates_active'),
236+
});
237+
}
238+
// eslint-disable-next-line react-hooks/exhaustive-deps
239+
}, [isUpdateHubConnected]);
141240

142241
// Fetch call extra data and notes when filter is active
143242
useEffect(() => {

src/app/(app)/map.web.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Modal, Pressable, ScrollView, StyleSheet, Switch, Text, TouchableOpacit
88

99
import { getMapDataAndMarkers } from '@/api/mapping/mapping';
1010
import { FocusAwareStatusBar } from '@/components/ui/focus-aware-status-bar';
11-
import { MAP_ICONS } from '@/constants/map-icons';
11+
import { getMapIconWebUrl, MAP_ICONS } from '@/constants/map-icons';
1212
import { useAnalytics } from '@/hooks/use-analytics';
1313
import { MapLayerType, useMapLayers } from '@/hooks/use-map-layers';
1414
import { Env } from '@/lib/env';
@@ -196,18 +196,18 @@ export default function MapWeb() {
196196
const iconKey = (pin.ImagePath?.toLowerCase() || 'call') as MapIconKey;
197197
const iconData = MAP_ICONS[iconKey] || MAP_ICONS['call'];
198198

199-
if (iconData) {
200-
const img = document.createElement('img');
201-
// For web, we need to handle the require() differently
202-
// The iconData.uri is a require() result, which in web context is a module
203-
const imgSrc = typeof iconData.uri === 'object' && 'default' in iconData.uri ? (iconData.uri as { default: string }).default : typeof iconData.uri === 'string' ? iconData.uri : `/mapping/${iconData.imgName}.png`;
204-
img.src = imgSrc;
205-
img.style.width = '32px';
206-
img.style.height = '32px';
207-
img.style.objectFit = 'contain';
208-
img.alt = pin.Title;
209-
iconContainer.appendChild(img);
210-
}
199+
const img = document.createElement('img');
200+
const imgSrc = getMapIconWebUrl(iconData);
201+
img.src = imgSrc;
202+
img.style.width = '32px';
203+
img.style.height = '32px';
204+
img.style.objectFit = 'contain';
205+
img.alt = pin.Title;
206+
img.onerror = () => {
207+
// Fallback to call icon if image fails to load
208+
img.src = getMapIconWebUrl(MAP_ICONS['call']);
209+
};
210+
iconContainer.appendChild(img);
211211

212212
el.appendChild(iconContainer);
213213

src/app/_layout.tsx

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -43,39 +43,63 @@ const navigationIntegration = Sentry.reactNavigationIntegration({
4343
enableTimeToInitialDisplay: false,
4444
});
4545

46-
Sentry.init({
47-
dsn: Env.SENTRY_DSN,
48-
debug: __DEV__, // Only debug in development, not production
49-
tracesSampleRate: __DEV__ ? 1.0 : 0.2, // 100% in dev, 20% in production to reduce performance impact
50-
profilesSampleRate: __DEV__ ? 1.0 : 0.2, // 100% in dev, 20% in production to reduce performance impact
51-
sendDefaultPii: false,
52-
integrations: [
53-
// Pass integration
54-
navigationIntegration,
55-
// Disable HTTP instrumentation on web platform due to hanging issues
46+
// Only initialize Sentry if a DSN is provided
47+
if (Env.SENTRY_DSN) {
48+
Sentry.init({
49+
dsn: Env.SENTRY_DSN,
50+
debug: __DEV__, // Only debug in development, not production
51+
tracesSampleRate: __DEV__ ? 1.0 : 0.2, // 100% in dev, 20% in production to reduce performance impact
52+
profilesSampleRate: __DEV__ ? 1.0 : 0.2, // 100% in dev, 20% in production to reduce performance impact
53+
sendDefaultPii: false,
54+
// Add release and environment information
55+
release: Env.VERSION,
56+
environment: Env.APP_ENV || (__DEV__ ? 'development' : 'production'),
57+
// Add platform as a tag
58+
initialScope: {
59+
tags: {
60+
platform: Platform.OS,
61+
},
62+
},
63+
integrations: [
64+
// Pass integration
65+
navigationIntegration,
66+
// Disable HTTP instrumentation on web platform due to hanging issues
67+
...(Platform.OS === 'web'
68+
? []
69+
: [
70+
// Add other integrations here for native platforms if needed
71+
]),
72+
],
73+
enableNativeFramesTracking: Platform.OS !== 'web', // Only enable native frames tracking on native platforms
74+
// Disable auto-instrumentation on web to prevent fetch/xhr blocking
5675
...(Platform.OS === 'web'
57-
? []
58-
: [
59-
// Add other integrations here for native platforms if needed
60-
]),
61-
],
62-
enableNativeFramesTracking: true, //!isRunningInExpoGo(), // Tracks slow and frozen frames in the application
63-
// Disable auto-instrumentation on web to prevent fetch/xhr blocking
64-
...(Platform.OS === 'web'
65-
? {
66-
enableAutoPerformanceTracing: false,
67-
enableAutoSessionTracking: false,
76+
? {
77+
enableAutoPerformanceTracing: false,
78+
enableAutoSessionTracking: false,
79+
}
80+
: {}),
81+
// Add additional options to prevent timing issues
82+
beforeSendTransaction(event: any) {
83+
// Filter out problematic navigation transactions that might cause timestamp errors
84+
if (event.contexts?.trace?.op === 'navigation' && !event.contexts?.trace?.data?.route) {
85+
return null;
6886
}
69-
: {}),
70-
// Add additional options to prevent timing issues
71-
beforeSendTransaction(event: any) {
72-
// Filter out problematic navigation transactions that might cause timestamp errors
73-
if (event.contexts?.trace?.op === 'navigation' && !event.contexts?.trace?.data?.route) {
74-
return null;
75-
}
76-
return event;
77-
},
78-
});
87+
return event;
88+
},
89+
beforeSend(event, hint) {
90+
// Add additional context for web platform
91+
if (Platform.OS === 'web') {
92+
event.tags = {
93+
...event.tags,
94+
'user_agent': typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown',
95+
};
96+
}
97+
return event;
98+
},
99+
});
100+
} else if (__DEV__) {
101+
console.log('Sentry DSN not configured - error tracking disabled');
102+
}
79103

80104
// Initialize LiveKit for the current platform
81105
//initializeLiveKitForPlatform();

src/app/onboarding.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,18 @@ type OnboardingItemProps = {
2323

2424
const onboardingData: OnboardingItemProps[] = [
2525
{
26-
title: 'Resgrid Dispatch',
27-
description: "Track your unit's location and status in real-time with our advanced mapping and AVL system",
26+
title: 'Command Your Operations',
27+
description: 'Create, dispatch, and manage emergency calls with a powerful mobile command center at your fingertips',
2828
icon: <MapPin size={80} color="#FF7B1A" />,
2929
},
3030
{
31-
title: 'Instant Notifications',
32-
description: 'Receive immediate alerts for emergencies and important updates from your department',
31+
title: 'Real-Time Situational Awareness',
32+
description: 'Track all units, personnel, and resources on an interactive map with live status updates and AVL',
3333
icon: <Bell size={80} color="#FF7B1A" />,
3434
},
3535
{
36-
title: 'Interact with Calls',
37-
description: 'Seamlessly view call information and interact with your team members for efficient emergency response',
36+
title: 'Seamless Coordination',
37+
description: 'Communicate instantly with field units, update call statuses, and coordinate response efforts from anywhere',
3838
icon: <Users size={80} color="#FF7B1A" />,
3939
},
4040
];

0 commit comments

Comments
 (0)