@controleonline/ui-default 1.0.263 → 1.0.265
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/AGENTS.md +47 -45
- package/package.json +20 -20
- package/src/react/components/errors/DefaultErrors.js +307 -325
- package/src/react/components/files/DefaultFile.js +46 -46
- package/src/react/components/filters/CompactFilterSelector.js +78 -24
- package/src/react/components/filters/CompactFilterSelector.styles.js +17 -5
- package/src/react/components/filters/DefaultColumnFilter.js +191 -57
- package/src/react/components/filters/DefaultExternalFilters.js +245 -182
- package/src/react/components/filters/DefaultSearch.js +131 -92
- package/src/react/components/filters/DefaultSearch.styles.js +65 -70
- package/src/react/components/form/DefaultForm.js +172 -151
- package/src/react/components/form/DefaultForm.styles.js +71 -66
- package/src/react/components/help/DefaultTooltip.js +87 -87
- package/src/react/components/help/DefaultTooltip.styles.js +61 -61
- package/src/react/components/inputs/DefaultDateInput.js +150 -0
- package/src/react/components/inputs/DefaultInput.js +162 -127
- package/src/react/components/inputs/DefaultInput.styles.js +6 -0
- package/src/react/components/inputs/DefaultSelect.js +320 -152
- package/src/react/components/inputs/defaultInputUtils.js +40 -19
- package/src/react/components/map/DefaultGoogleMap.styles.js +115 -9
- package/src/react/components/map/DefaultGoogleMap.web.js +560 -446
- package/src/react/components/map/DefaultMap.native.js +73 -71
- package/src/react/components/map/DefaultMap.styles.js +16 -16
- package/src/react/components/map/DefaultMap.web.js +68 -66
- package/src/react/components/map/DefaultNativeMap.native.js +307 -183
- package/src/react/components/map/DefaultNativeMap.shared.js +439 -340
- package/src/react/components/map/DefaultNativeMap.styles.js +156 -122
- package/src/react/components/table/DefaultColumnMenu.js +108 -0
- package/src/react/components/table/DefaultDebug.js +233 -0
- package/src/react/components/table/DefaultFiltersModal.js +142 -0
- package/src/react/components/table/DefaultModalButton.js +18 -0
- package/src/react/components/table/DefaultSearchModal.js +49 -0
- package/src/react/components/table/DefaultTable.js +345 -1846
- package/src/react/components/table/DefaultTable.styles.js +247 -136
- package/src/react/components/table/DefaultTable.utils.js +502 -0
- package/src/react/components/table/DefaultTableBody.js +26 -0
- package/src/react/components/table/DefaultTableCards.js +171 -0
- package/src/react/components/table/DefaultTableControls.js +208 -0
- package/src/react/components/table/DefaultTableEmptyState.js +22 -0
- package/src/react/components/table/DefaultTableFooter.js +172 -0
- package/src/react/components/table/DefaultTableInput.js +145 -0
- package/src/react/components/table/DefaultTableRows.js +340 -0
- package/src/react/components/table/DefaultTableSearch.js +100 -0
- package/src/react/components/table/DefaultTableToolbar.js +98 -0
- package/src/react/components/table/DefaultToolbarAction.js +56 -0
- package/src/react/components/table/useDefaultTablePagination.js +370 -0
- package/src/react/components/table/useDefaultTableSorting.js +126 -0
- package/src/react/components/table/useDefaultTableTheme.js +58 -0
- package/src/react/index.js +10 -10
- package/src/react/utils/tableVisibleColumnsPreferences.js +239 -211
- package/src/store/default/actions.js +51 -13
- package/src/store/default/getters.js +10 -8
- package/src/store/default/mutation_types.js +4 -2
- package/src/store/default/mutations.js +10 -1
- package/src/tests/react/components/errors/DefaultErrors.test.js +162 -162
- package/src/tests/react/components/files/DefaultFile.test.js +80 -80
- package/src/tests/react/components/form/DefaultForm.test.js +81 -0
- package/src/tests/react/components/inputs/DefaultSelect.test.js +185 -0
- package/src/tests/react/components/table/DefaultTable.test.js +1183 -0
- package/src/tests/react/store/defaultActions.test.js +59 -0
- package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +247 -168
|
@@ -1,199 +1,119 @@
|
|
|
1
|
-
import React, {useEffect, useRef} from 'react';
|
|
2
|
-
import {Platform, View} from 'react-native';
|
|
3
|
-
|
|
4
|
-
import styles from './DefaultGoogleMap.styles';
|
|
5
|
-
import {
|
|
6
|
-
buildGoogleMapsNavigationUrl,
|
|
7
|
-
buildWazeNavigationUrl,
|
|
8
|
-
} from './DefaultMap.shared';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return `<div class="shop-map-popup-line">${escapeHtml(value)}</div>`;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const buildPopupMeta = (label, value) => {
|
|
34
|
-
if (!value) {
|
|
1
|
+
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
|
2
|
+
import {Image, Platform, Text, TouchableOpacity, View} from 'react-native';
|
|
3
|
+
|
|
4
|
+
import styles from './DefaultGoogleMap.styles';
|
|
5
|
+
import {
|
|
6
|
+
buildGoogleMapsNavigationUrl,
|
|
7
|
+
buildWazeNavigationUrl,
|
|
8
|
+
} from './DefaultMap.shared';
|
|
9
|
+
import {resolveMapPopupTheme} from './DefaultNativeMap.shared';
|
|
10
|
+
import {resolveAddressDisplayParts} from '@controleonline/ui-common/src/react/utils/entityDisplay';
|
|
11
|
+
|
|
12
|
+
const GOOGLE_MAPS_SCRIPT_ID = 'shop-google-maps-api-script';
|
|
13
|
+
const GOOGLE_MAPS_CALLBACK_NAME = '__shopGoogleMapsApiReady__';
|
|
14
|
+
const GOOGLE_MAPS_LOAD_TIMEOUT_MS = 15000;
|
|
15
|
+
const GOOGLE_MAPS_POLL_INTERVAL_MS = 100;
|
|
16
|
+
const ROUTE_COLOR = '#0EA5E9';
|
|
17
|
+
const markerIconUrlValidationCache = new Map();
|
|
18
|
+
|
|
19
|
+
const buildPopupTravelSummary = (item, routeSummary = null) => {
|
|
20
|
+
const distanceLabel =
|
|
21
|
+
routeSummary?.distanceLabel || item?.distanceLabel || '';
|
|
22
|
+
const durationLabel =
|
|
23
|
+
routeSummary?.durationLabel ||
|
|
24
|
+
item?.durationLabel ||
|
|
25
|
+
item?.travelDurationLabel ||
|
|
26
|
+
'';
|
|
27
|
+
|
|
28
|
+
if (!distanceLabel && !durationLabel) {
|
|
35
29
|
return '';
|
|
36
30
|
}
|
|
37
31
|
|
|
38
32
|
return `
|
|
39
|
-
<div class="shop-map-popup-
|
|
40
|
-
|
|
41
|
-
<span>${escapeHtml(value)}</span>
|
|
33
|
+
<div class="shop-map-popup-summary">
|
|
34
|
+
${[distanceLabel, durationLabel].filter(Boolean).join(' • ')}
|
|
42
35
|
</div>
|
|
43
36
|
`;
|
|
44
37
|
};
|
|
45
38
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
item?.googleMapsUrl ||
|
|
53
|
-
buildGoogleMapsNavigationUrl(navigationPosition);
|
|
54
|
-
const wazeUrl = item?.wazeUrl || buildWazeNavigationUrl(navigationPosition);
|
|
55
|
-
|
|
56
|
-
return `
|
|
57
|
-
<div class="shop-map-popup">
|
|
58
|
-
<div class="shop-map-popup-company">${escapeHtml(item.companyName)}</div>
|
|
59
|
-
<div class="shop-map-popup-title">${escapeHtml(item.title)}</div>
|
|
60
|
-
${buildPopupLine(item.addressLine)}
|
|
61
|
-
${buildPopupLine(item.addressExtra)}
|
|
62
|
-
<div class="shop-map-popup-meta-list">
|
|
63
|
-
${buildPopupMeta('Telefone', item.phoneLabel)}
|
|
64
|
-
${buildPopupMeta('Distancia', item.distanceLabel)}
|
|
65
|
-
${buildPopupMeta('Horario', item.openingHours)}
|
|
66
|
-
</div>
|
|
67
|
-
<div class="shop-map-popup-actions">
|
|
68
|
-
<a
|
|
69
|
-
class="shop-map-popup-action shop-map-popup-action-primary"
|
|
70
|
-
href="${escapeHtml(googleMapsUrl)}"
|
|
71
|
-
target="_blank"
|
|
72
|
-
rel="noopener noreferrer"
|
|
73
|
-
>
|
|
74
|
-
Abrir no Maps
|
|
75
|
-
</a>
|
|
76
|
-
<a
|
|
77
|
-
class="shop-map-popup-action"
|
|
78
|
-
href="${escapeHtml(wazeUrl)}"
|
|
79
|
-
target="_blank"
|
|
80
|
-
rel="noopener noreferrer"
|
|
81
|
-
>
|
|
82
|
-
Waze
|
|
83
|
-
</a>
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
86
|
-
`;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const normalizeCoordinate = value => {
|
|
90
|
-
const numeric = Number(value);
|
|
91
|
-
return Number.isFinite(numeric) ? numeric : null;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const extractCoordinates = address => {
|
|
95
|
-
if (!address || typeof address !== 'object') {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const latitude = normalizeCoordinate(address.latitude);
|
|
100
|
-
const longitude = normalizeCoordinate(address.longitude);
|
|
101
|
-
|
|
102
|
-
if (latitude === null || longitude === null) {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return {latitude, longitude};
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const injectPopupStyles = () => {
|
|
110
|
-
if (typeof document === 'undefined') {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
39
|
+
const buildPopupTravelSummaryText = (item, routeSummary = null) =>
|
|
40
|
+
buildPopupTravelSummary(item, routeSummary)
|
|
41
|
+
.replace(/<\/?div[^>]*>/g, '')
|
|
42
|
+
.trim();
|
|
43
|
+
|
|
44
|
+
const normalizeText = value => String(value || '').trim();
|
|
113
45
|
|
|
114
|
-
|
|
115
|
-
|
|
46
|
+
const formatPostalCode = value => {
|
|
47
|
+
const digits = String(value || '').replace(/\D+/g, '');
|
|
48
|
+
|
|
49
|
+
if (digits.length !== 8) {
|
|
50
|
+
return normalizeText(value);
|
|
116
51
|
}
|
|
117
52
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
styleElement.textContent = `
|
|
121
|
-
.shop-map-popup {
|
|
122
|
-
min-width: 220px;
|
|
123
|
-
max-width: 280px;
|
|
124
|
-
color: #0f172a;
|
|
125
|
-
font-family: Arial, sans-serif;
|
|
126
|
-
}
|
|
53
|
+
return `${digits.slice(0, 5)}-${digits.slice(5)}`;
|
|
54
|
+
};
|
|
127
55
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
color: #0369a1;
|
|
134
|
-
margin-bottom: 6px;
|
|
135
|
-
}
|
|
56
|
+
const extractAddressExtraFallback = value => {
|
|
57
|
+
const parts = String(value || '')
|
|
58
|
+
.split('•')
|
|
59
|
+
.map(item => normalizeText(item))
|
|
60
|
+
.filter(Boolean);
|
|
136
61
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
62
|
+
const postalCodeLine = formatPostalCode(parts.find(item => /\d{5,8}/.test(item)));
|
|
63
|
+
const cityStateLine = normalizeText(
|
|
64
|
+
parts.find(item => /\b[A-Z]{2}\b/.test(item) || item.includes('/')),
|
|
65
|
+
);
|
|
142
66
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
67
|
+
return {
|
|
68
|
+
cityStateLine,
|
|
69
|
+
postalCodeLine,
|
|
70
|
+
};
|
|
71
|
+
};
|
|
149
72
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
73
|
+
const extractAddressDisplayLines = item => {
|
|
74
|
+
const addressParts = resolveAddressDisplayParts(item);
|
|
75
|
+
const addressExtraFallback = extractAddressExtraFallback(item?.addressExtra);
|
|
76
|
+
const primaryLine = normalizeText(
|
|
77
|
+
addressParts.streetLine || addressParts.primary || item?.addressLine,
|
|
78
|
+
);
|
|
79
|
+
const cityStateLine = normalizeText(
|
|
80
|
+
addressParts.cityStateLine || addressExtraFallback.cityStateLine,
|
|
81
|
+
);
|
|
82
|
+
const postalCodeLine = formatPostalCode(
|
|
83
|
+
addressParts.postalCode || addressExtraFallback.postalCodeLine,
|
|
84
|
+
);
|
|
155
85
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
86
|
+
return {
|
|
87
|
+
primaryLine,
|
|
88
|
+
cityStateLine,
|
|
89
|
+
postalCodeLine,
|
|
90
|
+
};
|
|
91
|
+
};
|
|
163
92
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
93
|
+
const normalizeCoordinate = value => {
|
|
94
|
+
const numeric = Number(value);
|
|
95
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
96
|
+
};
|
|
167
97
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
98
|
+
const extractCoordinates = address => {
|
|
99
|
+
if (!address || typeof address !== 'object') {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
173
102
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
border-radius: 999px;
|
|
177
|
-
border: 1px solid #cbd5e1;
|
|
178
|
-
padding: 10px 12px;
|
|
179
|
-
text-align: center;
|
|
180
|
-
text-decoration: none;
|
|
181
|
-
color: #0f172a;
|
|
182
|
-
font-size: 12px;
|
|
183
|
-
font-weight: 700;
|
|
184
|
-
background: #ffffff;
|
|
185
|
-
}
|
|
103
|
+
const latitude = normalizeCoordinate(address.latitude);
|
|
104
|
+
const longitude = normalizeCoordinate(address.longitude);
|
|
186
105
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
`;
|
|
193
|
-
document.head.appendChild(styleElement);
|
|
106
|
+
if (latitude === null || longitude === null) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {latitude, longitude};
|
|
194
111
|
};
|
|
195
112
|
|
|
196
|
-
const
|
|
113
|
+
const buildPopupTitle = item =>
|
|
114
|
+
item?.unitAlias || item?.alias || item?.companyName || item?.title || 'Unidade';
|
|
115
|
+
|
|
116
|
+
const loadGoogleMapsApi = apiKey => {
|
|
197
117
|
if (Platform.OS !== 'web' || typeof window === 'undefined') {
|
|
198
118
|
return Promise.resolve(null);
|
|
199
119
|
}
|
|
@@ -309,42 +229,42 @@ const loadGoogleMapsApi = apiKey => {
|
|
|
309
229
|
});
|
|
310
230
|
|
|
311
231
|
return window.__shopGoogleMapsPromise;
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
const resolveMarkerIconUrl = url => {
|
|
315
|
-
const normalizedUrl = String(url || '').trim();
|
|
316
|
-
|
|
317
|
-
if (
|
|
318
|
-
!normalizedUrl ||
|
|
319
|
-
typeof window === 'undefined' ||
|
|
320
|
-
typeof window.Image !== 'function'
|
|
321
|
-
) {
|
|
322
|
-
return Promise.resolve('');
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (markerIconUrlValidationCache.has(normalizedUrl)) {
|
|
326
|
-
return markerIconUrlValidationCache.get(normalizedUrl);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const validationPromise = new Promise(resolve => {
|
|
330
|
-
const image = new window.Image();
|
|
331
|
-
const finalize = value => {
|
|
332
|
-
const resolvedValue = String(value || '').trim();
|
|
333
|
-
markerIconUrlValidationCache.set(
|
|
334
|
-
normalizedUrl,
|
|
335
|
-
Promise.resolve(resolvedValue),
|
|
336
|
-
);
|
|
337
|
-
resolve(resolvedValue);
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
image.onload = () => finalize(normalizedUrl);
|
|
341
|
-
image.onerror = () => finalize('');
|
|
342
|
-
image.src = normalizedUrl;
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
markerIconUrlValidationCache.set(normalizedUrl, validationPromise);
|
|
346
|
-
return validationPromise;
|
|
347
|
-
};
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const resolveMarkerIconUrl = url => {
|
|
235
|
+
const normalizedUrl = String(url || '').trim();
|
|
236
|
+
|
|
237
|
+
if (
|
|
238
|
+
!normalizedUrl ||
|
|
239
|
+
typeof window === 'undefined' ||
|
|
240
|
+
typeof window.Image !== 'function'
|
|
241
|
+
) {
|
|
242
|
+
return Promise.resolve('');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (markerIconUrlValidationCache.has(normalizedUrl)) {
|
|
246
|
+
return markerIconUrlValidationCache.get(normalizedUrl);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const validationPromise = new Promise(resolve => {
|
|
250
|
+
const image = new window.Image();
|
|
251
|
+
const finalize = value => {
|
|
252
|
+
const resolvedValue = String(value || '').trim();
|
|
253
|
+
markerIconUrlValidationCache.set(
|
|
254
|
+
normalizedUrl,
|
|
255
|
+
Promise.resolve(resolvedValue),
|
|
256
|
+
);
|
|
257
|
+
resolve(resolvedValue);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
image.onload = () => finalize(normalizedUrl);
|
|
261
|
+
image.onerror = () => finalize('');
|
|
262
|
+
image.src = normalizedUrl;
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
markerIconUrlValidationCache.set(normalizedUrl, validationPromise);
|
|
266
|
+
return validationPromise;
|
|
267
|
+
};
|
|
348
268
|
|
|
349
269
|
const fitMapToBounds = ({google, map, markerCount, userCoordinates}) => {
|
|
350
270
|
const bounds = new google.maps.LatLngBounds();
|
|
@@ -374,43 +294,82 @@ const fitMapToBounds = ({google, map, markerCount, userCoordinates}) => {
|
|
|
374
294
|
left: 32,
|
|
375
295
|
});
|
|
376
296
|
|
|
377
|
-
google.maps.event.addListenerOnce(map, 'idle', () => {
|
|
378
|
-
if (markerCount === 1 && map.getZoom() > 15) {
|
|
379
|
-
map.setZoom(15);
|
|
380
|
-
}
|
|
381
|
-
});
|
|
297
|
+
google.maps.event.addListenerOnce(map, 'idle', () => {
|
|
298
|
+
if (markerCount === 1 && map.getZoom() > 15) {
|
|
299
|
+
map.setZoom(15);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
382
302
|
},
|
|
383
303
|
};
|
|
384
304
|
};
|
|
385
305
|
|
|
386
|
-
export default function DefaultGoogleMap({
|
|
387
|
-
apiKey,
|
|
388
|
-
markerPayloads = [],
|
|
389
|
-
paths = [],
|
|
390
|
-
userCoordinates = null,
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
306
|
+
export default function DefaultGoogleMap({
|
|
307
|
+
apiKey,
|
|
308
|
+
markerPayloads = [],
|
|
309
|
+
paths = [],
|
|
310
|
+
userCoordinates = null,
|
|
311
|
+
popupTheme = null,
|
|
312
|
+
}) {
|
|
313
|
+
const containerRef = useRef(null);
|
|
314
|
+
const directionsRendererRef = useRef(null);
|
|
315
|
+
const activeRouteRequestIdRef = useRef(0);
|
|
316
|
+
const [selectedMarkerId, setSelectedMarkerId] = useState(null);
|
|
317
|
+
const [selectedRouteSummary, setSelectedRouteSummary] = useState(null);
|
|
318
|
+
const resolvedPopupTheme = useMemo(
|
|
319
|
+
() => resolveMapPopupTheme(popupTheme),
|
|
320
|
+
[popupTheme],
|
|
321
|
+
);
|
|
322
|
+
const selectedMarker = useMemo(
|
|
323
|
+
() => markerPayloads.find(item => item.id === selectedMarkerId) || null,
|
|
324
|
+
[markerPayloads, selectedMarkerId],
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
useEffect(() => {
|
|
328
|
+
if (!selectedMarkerId) {
|
|
329
|
+
setSelectedRouteSummary(null);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (!selectedMarker) {
|
|
334
|
+
setSelectedMarkerId(null);
|
|
335
|
+
setSelectedRouteSummary(null);
|
|
336
|
+
}
|
|
337
|
+
}, [selectedMarker, selectedMarkerId]);
|
|
338
|
+
|
|
339
|
+
const openExternalUrl = useCallback(url => {
|
|
340
|
+
const normalizedUrl = String(url || '').trim();
|
|
341
|
+
if (!normalizedUrl || typeof window === 'undefined') {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
window.open(normalizedUrl, '_blank', 'noopener,noreferrer');
|
|
346
|
+
}, []);
|
|
347
|
+
|
|
348
|
+
const closePopup = useCallback(() => {
|
|
349
|
+
activeRouteRequestIdRef.current += 1;
|
|
350
|
+
setSelectedMarkerId(null);
|
|
351
|
+
setSelectedRouteSummary(null);
|
|
352
|
+
directionsRendererRef.current?.set('directions', null);
|
|
353
|
+
}, []);
|
|
354
|
+
|
|
355
|
+
useEffect(() => {
|
|
356
|
+
if (Platform.OS !== 'web') {
|
|
357
|
+
return undefined;
|
|
397
358
|
}
|
|
398
359
|
|
|
399
360
|
const container = containerRef.current;
|
|
400
|
-
if (
|
|
401
|
-
!container ||
|
|
402
|
-
!apiKey ||
|
|
403
|
-
(markerPayloads.length === 0 && !(Array.isArray(paths) && paths.length > 0) && !userCoordinates)
|
|
404
|
-
) {
|
|
405
|
-
return undefined;
|
|
406
|
-
}
|
|
361
|
+
if (
|
|
362
|
+
!container ||
|
|
363
|
+
!apiKey ||
|
|
364
|
+
(markerPayloads.length === 0 && !(Array.isArray(paths) && paths.length > 0) && !userCoordinates)
|
|
365
|
+
) {
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
407
368
|
|
|
408
369
|
let cancelled = false;
|
|
409
370
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
loadGoogleMapsApi(apiKey)
|
|
413
|
-
.then(async () => {
|
|
371
|
+
loadGoogleMapsApi(apiKey)
|
|
372
|
+
.then(async () => {
|
|
414
373
|
if (cancelled || !container || !window.google?.maps) {
|
|
415
374
|
return;
|
|
416
375
|
}
|
|
@@ -425,97 +384,95 @@ export default function DefaultGoogleMap({
|
|
|
425
384
|
zoomControl: false,
|
|
426
385
|
disableDefaultUI: true,
|
|
427
386
|
});
|
|
428
|
-
|
|
429
|
-
const infoWindow = new google.maps.InfoWindow({maxWidth: 320});
|
|
430
387
|
const hasUserCoordinates =
|
|
431
388
|
Number.isFinite(userCoordinates?.latitude) &&
|
|
432
389
|
Number.isFinite(userCoordinates?.longitude);
|
|
433
|
-
const directionsService = new google.maps.DirectionsService();
|
|
434
|
-
const directionsRenderer = directionsService
|
|
435
|
-
&& hasUserCoordinates
|
|
436
|
-
? new google.maps.DirectionsRenderer({
|
|
437
|
-
map,
|
|
438
|
-
suppressMarkers: true,
|
|
439
|
-
preserveViewport: false,
|
|
440
|
-
polylineOptions: {
|
|
441
|
-
strokeColor: ROUTE_COLOR,
|
|
442
|
-
strokeOpacity: 0.92,
|
|
443
|
-
strokeWeight: 5,
|
|
444
|
-
},
|
|
445
|
-
})
|
|
446
|
-
: null;
|
|
447
|
-
|
|
448
|
-
const resolveMarkerPayload = async item => {
|
|
449
|
-
const latitude = Number(item?.latitude);
|
|
450
|
-
const longitude = Number(item?.longitude);
|
|
451
|
-
const markerIconUrl = await resolveMarkerIconUrl(item?.markerIconUrl);
|
|
452
|
-
const resolvedItem = markerIconUrl
|
|
453
|
-
? {
|
|
454
|
-
...item,
|
|
455
|
-
markerIconUrl,
|
|
456
|
-
}
|
|
457
|
-
: item?.markerIconUrl
|
|
458
|
-
? {
|
|
459
|
-
...item,
|
|
460
|
-
markerIconUrl: '',
|
|
461
|
-
}
|
|
462
|
-
: item;
|
|
463
|
-
|
|
464
|
-
if (Number.isFinite(latitude) && Number.isFinite(longitude)) {
|
|
465
|
-
return {
|
|
466
|
-
item: resolvedItem,
|
|
467
|
-
position: {
|
|
468
|
-
lat: latitude,
|
|
469
|
-
lng: longitude,
|
|
470
|
-
},
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
if (!resolvedItem?.geocodeQuery) {
|
|
475
|
-
return null;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
const geocoder = new google.maps.Geocoder();
|
|
479
|
-
|
|
480
|
-
return new Promise(resolve => {
|
|
481
|
-
geocoder.geocode({address: resolvedItem.geocodeQuery}, (results, status) => {
|
|
482
|
-
const location = results?.[0]?.geometry?.location;
|
|
483
|
-
|
|
484
|
-
if (status !== 'OK' || !location) {
|
|
485
|
-
resolve(null);
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
const resolvedLatitude = Number(location.lat());
|
|
490
|
-
const resolvedLongitude = Number(location.lng());
|
|
491
|
-
|
|
492
|
-
if (
|
|
493
|
-
!Number.isFinite(resolvedLatitude) ||
|
|
494
|
-
!Number.isFinite(resolvedLongitude)
|
|
495
|
-
) {
|
|
496
|
-
resolve(null);
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
resolve({
|
|
501
|
-
item: resolvedItem,
|
|
502
|
-
position: {
|
|
503
|
-
lat: resolvedLatitude,
|
|
504
|
-
lng: resolvedLongitude,
|
|
505
|
-
},
|
|
506
|
-
});
|
|
507
|
-
});
|
|
508
|
-
});
|
|
509
|
-
};
|
|
510
|
-
const resolvedMarkers = (await Promise.all(
|
|
511
|
-
markerPayloads.map(resolveMarkerPayload),
|
|
512
|
-
)).filter(Boolean);
|
|
513
|
-
const {bounds, finalize} = fitMapToBounds({
|
|
514
|
-
google,
|
|
515
|
-
map,
|
|
516
|
-
markerCount: resolvedMarkers.length,
|
|
517
|
-
userCoordinates,
|
|
518
|
-
});
|
|
390
|
+
const directionsService = new google.maps.DirectionsService();
|
|
391
|
+
const directionsRenderer = directionsService
|
|
392
|
+
&& hasUserCoordinates
|
|
393
|
+
? new google.maps.DirectionsRenderer({
|
|
394
|
+
map,
|
|
395
|
+
suppressMarkers: true,
|
|
396
|
+
preserveViewport: false,
|
|
397
|
+
polylineOptions: {
|
|
398
|
+
strokeColor: ROUTE_COLOR,
|
|
399
|
+
strokeOpacity: 0.92,
|
|
400
|
+
strokeWeight: 5,
|
|
401
|
+
},
|
|
402
|
+
})
|
|
403
|
+
: null;
|
|
404
|
+
directionsRendererRef.current = directionsRenderer;
|
|
405
|
+
const resolveMarkerPayload = async item => {
|
|
406
|
+
const latitude = Number(item?.latitude);
|
|
407
|
+
const longitude = Number(item?.longitude);
|
|
408
|
+
const markerIconUrl = await resolveMarkerIconUrl(item?.markerIconUrl);
|
|
409
|
+
const resolvedItem = markerIconUrl
|
|
410
|
+
? {
|
|
411
|
+
...item,
|
|
412
|
+
markerIconUrl,
|
|
413
|
+
}
|
|
414
|
+
: item?.markerIconUrl
|
|
415
|
+
? {
|
|
416
|
+
...item,
|
|
417
|
+
markerIconUrl: '',
|
|
418
|
+
}
|
|
419
|
+
: item;
|
|
420
|
+
|
|
421
|
+
if (Number.isFinite(latitude) && Number.isFinite(longitude)) {
|
|
422
|
+
return {
|
|
423
|
+
item: resolvedItem,
|
|
424
|
+
position: {
|
|
425
|
+
lat: latitude,
|
|
426
|
+
lng: longitude,
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (!resolvedItem?.geocodeQuery) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const geocoder = new google.maps.Geocoder();
|
|
436
|
+
|
|
437
|
+
return new Promise(resolve => {
|
|
438
|
+
geocoder.geocode({address: resolvedItem.geocodeQuery}, (results, status) => {
|
|
439
|
+
const location = results?.[0]?.geometry?.location;
|
|
440
|
+
|
|
441
|
+
if (status !== 'OK' || !location) {
|
|
442
|
+
resolve(null);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const resolvedLatitude = Number(location.lat());
|
|
447
|
+
const resolvedLongitude = Number(location.lng());
|
|
448
|
+
|
|
449
|
+
if (
|
|
450
|
+
!Number.isFinite(resolvedLatitude) ||
|
|
451
|
+
!Number.isFinite(resolvedLongitude)
|
|
452
|
+
) {
|
|
453
|
+
resolve(null);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
resolve({
|
|
458
|
+
item: resolvedItem,
|
|
459
|
+
position: {
|
|
460
|
+
lat: resolvedLatitude,
|
|
461
|
+
lng: resolvedLongitude,
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
};
|
|
467
|
+
const resolvedMarkers = (await Promise.all(
|
|
468
|
+
markerPayloads.map(resolveMarkerPayload),
|
|
469
|
+
)).filter(Boolean);
|
|
470
|
+
const {bounds, finalize} = fitMapToBounds({
|
|
471
|
+
google,
|
|
472
|
+
map,
|
|
473
|
+
markerCount: resolvedMarkers.length,
|
|
474
|
+
userCoordinates,
|
|
475
|
+
});
|
|
519
476
|
|
|
520
477
|
if (
|
|
521
478
|
userCoordinates &&
|
|
@@ -533,12 +490,12 @@ export default function DefaultGoogleMap({
|
|
|
533
490
|
});
|
|
534
491
|
}
|
|
535
492
|
|
|
536
|
-
resolvedMarkers.forEach(entry => {
|
|
537
|
-
const item = entry.item;
|
|
538
|
-
const position = entry.position;
|
|
539
|
-
const markerOptions = {
|
|
540
|
-
position,
|
|
541
|
-
map,
|
|
493
|
+
resolvedMarkers.forEach(entry => {
|
|
494
|
+
const item = entry.item;
|
|
495
|
+
const position = entry.position;
|
|
496
|
+
const markerOptions = {
|
|
497
|
+
position,
|
|
498
|
+
map,
|
|
542
499
|
title: item.title,
|
|
543
500
|
animation: google.maps.Animation.DROP,
|
|
544
501
|
};
|
|
@@ -550,24 +507,20 @@ export default function DefaultGoogleMap({
|
|
|
550
507
|
};
|
|
551
508
|
}
|
|
552
509
|
|
|
553
|
-
const marker = new google.maps.Marker(markerOptions);
|
|
554
|
-
|
|
555
|
-
bounds.extend(position);
|
|
556
|
-
|
|
557
|
-
marker.addListener('click', () => {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
anchor: marker,
|
|
561
|
-
map,
|
|
562
|
-
shouldFocus: false,
|
|
563
|
-
});
|
|
510
|
+
const marker = new google.maps.Marker(markerOptions);
|
|
511
|
+
|
|
512
|
+
bounds.extend(position);
|
|
513
|
+
|
|
514
|
+
marker.addListener('click', () => {
|
|
515
|
+
setSelectedMarkerId(item.id);
|
|
516
|
+
setSelectedRouteSummary(null);
|
|
564
517
|
|
|
565
518
|
if (!directionsService || !directionsRenderer) {
|
|
566
519
|
return;
|
|
567
520
|
}
|
|
568
521
|
|
|
569
|
-
const routeRequestId =
|
|
570
|
-
|
|
522
|
+
const routeRequestId = activeRouteRequestIdRef.current + 1;
|
|
523
|
+
activeRouteRequestIdRef.current = routeRequestId;
|
|
571
524
|
|
|
572
525
|
directionsService.route(
|
|
573
526
|
{
|
|
@@ -582,117 +535,278 @@ export default function DefaultGoogleMap({
|
|
|
582
535
|
if (
|
|
583
536
|
cancelled ||
|
|
584
537
|
!directionsRenderer ||
|
|
585
|
-
routeRequestId !==
|
|
538
|
+
routeRequestId !== activeRouteRequestIdRef.current
|
|
586
539
|
) {
|
|
587
540
|
return;
|
|
588
541
|
}
|
|
589
542
|
|
|
590
543
|
if (status === 'OK' && response) {
|
|
544
|
+
const activeLeg = response?.routes?.[0]?.legs?.[0] || null;
|
|
591
545
|
directionsRenderer.setDirections(response);
|
|
546
|
+
setSelectedRouteSummary({
|
|
547
|
+
distanceLabel: String(activeLeg?.distance?.text || '').trim(),
|
|
548
|
+
durationLabel: String(activeLeg?.duration?.text || '').trim(),
|
|
549
|
+
});
|
|
592
550
|
return;
|
|
593
551
|
}
|
|
594
552
|
|
|
595
553
|
directionsRenderer.set('directions', null);
|
|
554
|
+
setSelectedRouteSummary(null);
|
|
596
555
|
},
|
|
597
556
|
);
|
|
598
|
-
});
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
const resolveRouteCoordinates = path => {
|
|
602
|
-
const from = extractCoordinates(path?.from || path?.origin || path?.start);
|
|
603
|
-
const to = extractCoordinates(path?.to || path?.destination || path?.end);
|
|
604
|
-
|
|
605
|
-
if (!from || !to) {
|
|
606
|
-
return Promise.resolve(null);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
return new Promise(resolve => {
|
|
610
|
-
directionsService.route(
|
|
611
|
-
{
|
|
612
|
-
origin: from,
|
|
613
|
-
destination: to,
|
|
614
|
-
travelMode: google.maps.TravelMode.DRIVING,
|
|
615
|
-
},
|
|
616
|
-
(response, status) => {
|
|
617
|
-
if (cancelled) {
|
|
618
|
-
resolve(null);
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
const overviewPath = response?.routes?.[0]?.overview_path;
|
|
623
|
-
const coordinates =
|
|
624
|
-
Array.isArray(overviewPath) && overviewPath.length > 1
|
|
625
|
-
? overviewPath
|
|
626
|
-
.map(point => ({
|
|
627
|
-
lat: Number(typeof point.lat === 'function' ? point.lat() : point.lat),
|
|
628
|
-
lng: Number(typeof point.lng === 'function' ? point.lng() : point.lng),
|
|
629
|
-
}))
|
|
630
|
-
.filter(
|
|
631
|
-
item => Number.isFinite(item.lat) && Number.isFinite(item.lng),
|
|
632
|
-
)
|
|
633
|
-
: [from, to];
|
|
634
|
-
|
|
635
|
-
if (status !== 'OK' && coordinates.length < 2) {
|
|
636
|
-
resolve(null);
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
resolve({
|
|
641
|
-
path,
|
|
642
|
-
coordinates,
|
|
643
|
-
});
|
|
644
|
-
},
|
|
645
|
-
);
|
|
646
|
-
});
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
const resolvedRoutes = await Promise.all(
|
|
650
|
-
(Array.isArray(paths) ? paths : []).map(resolveRouteCoordinates),
|
|
651
|
-
);
|
|
652
|
-
|
|
653
|
-
if (cancelled) {
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
resolvedRoutes.filter(Boolean).forEach(entry => {
|
|
658
|
-
const coordinates = Array.isArray(entry?.coordinates) ? entry.coordinates : [];
|
|
659
|
-
|
|
660
|
-
if (coordinates.length < 2) {
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
coordinates.forEach(point => {
|
|
665
|
-
bounds.extend(point);
|
|
666
|
-
});
|
|
667
|
-
|
|
668
|
-
new google.maps.Polyline({
|
|
669
|
-
path: coordinates,
|
|
670
|
-
geodesic: true,
|
|
671
|
-
strokeColor: normalizeText(entry?.path?.color || '') || ROUTE_COLOR,
|
|
672
|
-
strokeOpacity: 0.72,
|
|
673
|
-
strokeWeight: 4,
|
|
674
|
-
map,
|
|
675
|
-
});
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
finalize();
|
|
679
|
-
})
|
|
680
|
-
.catch(() => {});
|
|
681
|
-
|
|
682
|
-
return () => {
|
|
683
|
-
cancelled = true;
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
const resolveRouteCoordinates = path => {
|
|
561
|
+
const from = extractCoordinates(path?.from || path?.origin || path?.start);
|
|
562
|
+
const to = extractCoordinates(path?.to || path?.destination || path?.end);
|
|
563
|
+
|
|
564
|
+
if (!from || !to) {
|
|
565
|
+
return Promise.resolve(null);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return new Promise(resolve => {
|
|
569
|
+
directionsService.route(
|
|
570
|
+
{
|
|
571
|
+
origin: from,
|
|
572
|
+
destination: to,
|
|
573
|
+
travelMode: google.maps.TravelMode.DRIVING,
|
|
574
|
+
},
|
|
575
|
+
(response, status) => {
|
|
576
|
+
if (cancelled) {
|
|
577
|
+
resolve(null);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const overviewPath = response?.routes?.[0]?.overview_path;
|
|
582
|
+
const coordinates =
|
|
583
|
+
Array.isArray(overviewPath) && overviewPath.length > 1
|
|
584
|
+
? overviewPath
|
|
585
|
+
.map(point => ({
|
|
586
|
+
lat: Number(typeof point.lat === 'function' ? point.lat() : point.lat),
|
|
587
|
+
lng: Number(typeof point.lng === 'function' ? point.lng() : point.lng),
|
|
588
|
+
}))
|
|
589
|
+
.filter(
|
|
590
|
+
item => Number.isFinite(item.lat) && Number.isFinite(item.lng),
|
|
591
|
+
)
|
|
592
|
+
: [from, to];
|
|
593
|
+
|
|
594
|
+
if (status !== 'OK' && coordinates.length < 2) {
|
|
595
|
+
resolve(null);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
resolve({
|
|
600
|
+
path,
|
|
601
|
+
coordinates,
|
|
602
|
+
});
|
|
603
|
+
},
|
|
604
|
+
);
|
|
605
|
+
});
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
const resolvedRoutes = await Promise.all(
|
|
609
|
+
(Array.isArray(paths) ? paths : []).map(resolveRouteCoordinates),
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
if (cancelled) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
resolvedRoutes.filter(Boolean).forEach(entry => {
|
|
617
|
+
const coordinates = Array.isArray(entry?.coordinates) ? entry.coordinates : [];
|
|
618
|
+
|
|
619
|
+
if (coordinates.length < 2) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
coordinates.forEach(point => {
|
|
624
|
+
bounds.extend(point);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
new google.maps.Polyline({
|
|
628
|
+
path: coordinates,
|
|
629
|
+
geodesic: true,
|
|
630
|
+
strokeColor: normalizeText(entry?.path?.color || '') || ROUTE_COLOR,
|
|
631
|
+
strokeOpacity: 0.72,
|
|
632
|
+
strokeWeight: 4,
|
|
633
|
+
map,
|
|
634
|
+
});
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
finalize();
|
|
638
|
+
})
|
|
639
|
+
.catch(() => {});
|
|
640
|
+
|
|
641
|
+
return () => {
|
|
642
|
+
cancelled = true;
|
|
643
|
+
directionsRendererRef.current = null;
|
|
644
|
+
};
|
|
645
|
+
}, [apiKey, markerPayloads, paths, userCoordinates]);
|
|
646
|
+
|
|
647
|
+
if (
|
|
648
|
+
Platform.OS !== 'web' ||
|
|
649
|
+
!apiKey ||
|
|
650
|
+
(markerPayloads.length === 0 &&
|
|
651
|
+
!Boolean(userCoordinates) &&
|
|
652
|
+
(!Array.isArray(paths) || paths.length === 0))
|
|
653
|
+
) {
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const selectedTitle = buildPopupTitle(selectedMarker);
|
|
658
|
+
const selectedTravelSummary = buildPopupTravelSummaryText(
|
|
659
|
+
selectedMarker,
|
|
660
|
+
selectedRouteSummary,
|
|
661
|
+
);
|
|
662
|
+
const selectedNavigationPosition = selectedMarker
|
|
663
|
+
? {
|
|
664
|
+
latitude: Number(selectedMarker.latitude),
|
|
665
|
+
longitude: Number(selectedMarker.longitude),
|
|
666
|
+
}
|
|
667
|
+
: null;
|
|
668
|
+
const selectedAddressLines = extractAddressDisplayLines(selectedMarker);
|
|
669
|
+
const selectedGoogleMapsUrl = selectedMarker
|
|
670
|
+
? selectedMarker.googleMapsUrl ||
|
|
671
|
+
buildGoogleMapsNavigationUrl(selectedNavigationPosition)
|
|
672
|
+
: '';
|
|
673
|
+
const selectedWazeUrl = selectedMarker
|
|
674
|
+
? selectedMarker.wazeUrl || buildWazeNavigationUrl(selectedNavigationPosition)
|
|
675
|
+
: '';
|
|
676
|
+
|
|
677
|
+
return (
|
|
678
|
+
<View style={styles.mapRoot}>
|
|
679
|
+
<View ref={containerRef} style={styles.mapViewport} />
|
|
680
|
+
{selectedMarker ? (
|
|
681
|
+
<View pointerEvents="box-none" style={styles.popupOverlay}>
|
|
682
|
+
<View
|
|
683
|
+
style={[
|
|
684
|
+
styles.popupCard,
|
|
685
|
+
{
|
|
686
|
+
backgroundColor: resolvedPopupTheme.modalBackground,
|
|
687
|
+
shadowColor: resolvedPopupTheme.modalShadow,
|
|
688
|
+
},
|
|
689
|
+
]}>
|
|
690
|
+
<View style={styles.popupHeaderRow}>
|
|
691
|
+
<View
|
|
692
|
+
style={[
|
|
693
|
+
styles.popupLogoWrap,
|
|
694
|
+
{borderColor: resolvedPopupTheme.dividerBorder},
|
|
695
|
+
]}>
|
|
696
|
+
{selectedMarker.companyLogoUrl ? (
|
|
697
|
+
<Image
|
|
698
|
+
source={{uri: selectedMarker.companyLogoUrl}}
|
|
699
|
+
style={styles.popupLogoImage}
|
|
700
|
+
resizeMode="cover"
|
|
701
|
+
/>
|
|
702
|
+
) : null}
|
|
703
|
+
</View>
|
|
704
|
+
<View style={styles.popupTitleWrap}>
|
|
705
|
+
<Text
|
|
706
|
+
numberOfLines={1}
|
|
707
|
+
style={[
|
|
708
|
+
styles.popupTitle,
|
|
709
|
+
{color: resolvedPopupTheme.modalHeaderText},
|
|
710
|
+
]}>
|
|
711
|
+
{selectedTitle}
|
|
712
|
+
</Text>
|
|
713
|
+
</View>
|
|
714
|
+
<TouchableOpacity
|
|
715
|
+
activeOpacity={0.8}
|
|
716
|
+
accessibilityLabel="Fechar detalhes da unidade"
|
|
717
|
+
onPress={closePopup}
|
|
718
|
+
style={styles.popupCloseButton}>
|
|
719
|
+
<Text
|
|
720
|
+
style={[
|
|
721
|
+
styles.popupCloseText,
|
|
722
|
+
{color: resolvedPopupTheme.modalHeaderText},
|
|
723
|
+
]}>
|
|
724
|
+
×
|
|
725
|
+
</Text>
|
|
726
|
+
</TouchableOpacity>
|
|
727
|
+
</View>
|
|
728
|
+
|
|
729
|
+
<View
|
|
730
|
+
style={[
|
|
731
|
+
styles.popupInfoCard,
|
|
732
|
+
{
|
|
733
|
+
backgroundColor: resolvedPopupTheme.pageBackground,
|
|
734
|
+
borderColor: resolvedPopupTheme.dividerBorder,
|
|
735
|
+
},
|
|
736
|
+
]}>
|
|
737
|
+
{selectedAddressLines.primaryLine ? (
|
|
738
|
+
<Text
|
|
739
|
+
style={[
|
|
740
|
+
styles.popupLine,
|
|
741
|
+
{color: resolvedPopupTheme.modalText},
|
|
742
|
+
]}>
|
|
743
|
+
{selectedAddressLines.primaryLine}
|
|
744
|
+
</Text>
|
|
745
|
+
) : null}
|
|
746
|
+
{selectedAddressLines.cityStateLine ? (
|
|
747
|
+
<Text
|
|
748
|
+
style={[
|
|
749
|
+
styles.popupLine,
|
|
750
|
+
{color: resolvedPopupTheme.modalText},
|
|
751
|
+
]}>
|
|
752
|
+
{selectedAddressLines.cityStateLine}
|
|
753
|
+
</Text>
|
|
754
|
+
) : null}
|
|
755
|
+
{selectedAddressLines.postalCodeLine ? (
|
|
756
|
+
<Text
|
|
757
|
+
style={[
|
|
758
|
+
styles.popupLine,
|
|
759
|
+
{color: resolvedPopupTheme.modalText},
|
|
760
|
+
]}>
|
|
761
|
+
{selectedAddressLines.postalCodeLine}
|
|
762
|
+
</Text>
|
|
763
|
+
) : null}
|
|
764
|
+
{selectedTravelSummary ? (
|
|
765
|
+
<Text
|
|
766
|
+
style={[
|
|
767
|
+
styles.popupSummary,
|
|
768
|
+
{color: resolvedPopupTheme.textMuted},
|
|
769
|
+
]}>
|
|
770
|
+
{selectedTravelSummary}
|
|
771
|
+
</Text>
|
|
772
|
+
) : null}
|
|
773
|
+
</View>
|
|
774
|
+
|
|
775
|
+
<View style={styles.popupActionsRow}>
|
|
776
|
+
<TouchableOpacity
|
|
777
|
+
activeOpacity={0.85}
|
|
778
|
+
onPress={() => openExternalUrl(selectedGoogleMapsUrl)}
|
|
779
|
+
style={[
|
|
780
|
+
styles.popupActionButton,
|
|
781
|
+
{backgroundColor: resolvedPopupTheme.buttonBackground},
|
|
782
|
+
]}>
|
|
783
|
+
<Text
|
|
784
|
+
style={[
|
|
785
|
+
styles.popupActionText,
|
|
786
|
+
{color: resolvedPopupTheme.buttonText},
|
|
787
|
+
]}>
|
|
788
|
+
Abrir no Maps
|
|
789
|
+
</Text>
|
|
790
|
+
</TouchableOpacity>
|
|
791
|
+
<TouchableOpacity
|
|
792
|
+
activeOpacity={0.85}
|
|
793
|
+
onPress={() => openExternalUrl(selectedWazeUrl)}
|
|
794
|
+
style={[
|
|
795
|
+
styles.popupActionButton,
|
|
796
|
+
{backgroundColor: resolvedPopupTheme.buttonBackground},
|
|
797
|
+
]}>
|
|
798
|
+
<Text
|
|
799
|
+
style={[
|
|
800
|
+
styles.popupActionText,
|
|
801
|
+
{color: resolvedPopupTheme.buttonText},
|
|
802
|
+
]}>
|
|
803
|
+
Abrir no Waze
|
|
804
|
+
</Text>
|
|
805
|
+
</TouchableOpacity>
|
|
806
|
+
</View>
|
|
807
|
+
</View>
|
|
808
|
+
</View>
|
|
809
|
+
) : null}
|
|
810
|
+
</View>
|
|
811
|
+
);
|
|
812
|
+
}
|