@controleonline/ui-default 1.0.263

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/.github/agents/developer.agent.md +30 -0
  2. package/.github/agents/devops.agent.md +30 -0
  3. package/.github/agents/qa.agent.md +30 -0
  4. package/.github/agents/security.agent.md +30 -0
  5. package/.scrutinizer.yml +20 -0
  6. package/AGENTS.md +47 -0
  7. package/FUNDING.yml +1 -0
  8. package/package.json +21 -0
  9. package/src/react/components/errors/DefaultErrors.js +360 -0
  10. package/src/react/components/files/DefaultFile.js +46 -0
  11. package/src/react/components/filters/CompactFilterSelector.js +262 -0
  12. package/src/react/components/filters/CompactFilterSelector.styles.js +124 -0
  13. package/src/react/components/filters/DateShortcutFilter.js +264 -0
  14. package/src/react/components/filters/DateShortcutFilter.styles.js +82 -0
  15. package/src/react/components/filters/DefaultColumnFilter.js +97 -0
  16. package/src/react/components/filters/DefaultColumnFilter.styles.js +21 -0
  17. package/src/react/components/filters/DefaultExternalFilters.js +441 -0
  18. package/src/react/components/filters/DefaultExternalFilters.styles.js +177 -0
  19. package/src/react/components/filters/DefaultSearch.js +103 -0
  20. package/src/react/components/filters/DefaultSearch.styles.js +70 -0
  21. package/src/react/components/filters/dateFilterSelection.js +29 -0
  22. package/src/react/components/form/DefaultForm.js +198 -0
  23. package/src/react/components/form/DefaultForm.styles.js +70 -0
  24. package/src/react/components/help/DefaultTooltip.js +87 -0
  25. package/src/react/components/help/DefaultTooltip.styles.js +61 -0
  26. package/src/react/components/inputs/DefaultInput.js +160 -0
  27. package/src/react/components/inputs/DefaultInput.styles.js +93 -0
  28. package/src/react/components/inputs/DefaultSelect.js +192 -0
  29. package/src/react/components/inputs/DefaultSelect.styles.js +65 -0
  30. package/src/react/components/inputs/defaultInputUtils.js +230 -0
  31. package/src/react/components/map/DefaultGoogleMap.styles.js +9 -0
  32. package/src/react/components/map/DefaultGoogleMap.web.js +698 -0
  33. package/src/react/components/map/DefaultMap.native.js +71 -0
  34. package/src/react/components/map/DefaultMap.shared.js +762 -0
  35. package/src/react/components/map/DefaultMap.styles.js +16 -0
  36. package/src/react/components/map/DefaultMap.web.js +66 -0
  37. package/src/react/components/map/DefaultNativeMap.native.js +615 -0
  38. package/src/react/components/map/DefaultNativeMap.shared.js +532 -0
  39. package/src/react/components/map/DefaultNativeMap.styles.js +122 -0
  40. package/src/react/components/table/DefaultTable.js +1897 -0
  41. package/src/react/components/table/DefaultTable.styles.js +610 -0
  42. package/src/react/index.js +10 -0
  43. package/src/react/utils/tableVisibleColumnsPreferences.js +264 -0
  44. package/src/store/default/actions.js +444 -0
  45. package/src/store/default/getters.js +28 -0
  46. package/src/store/default/mutation_types.js +26 -0
  47. package/src/store/default/mutations.js +138 -0
  48. package/src/tests/react/components/DateShortcutFilter.test.js +96 -0
  49. package/src/tests/react/components/errors/DefaultErrors.test.js +162 -0
  50. package/src/tests/react/components/files/DefaultFile.test.js +80 -0
  51. package/src/tests/react/components/map/DefaultMap.shared.test.js +162 -0
  52. package/src/tests/react/filters/dateFilterSelection.test.js +46 -0
  53. package/src/tests/react/inputs/defaultInputUtils.test.js +45 -0
  54. package/src/tests/react/store/defaultActions.test.js +112 -0
  55. package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +223 -0
  56. package/src/utils/filters.js +56 -0
@@ -0,0 +1,16 @@
1
+ import {StyleSheet} from 'react-native';
2
+
3
+ export default StyleSheet.create({
4
+ mapContainer: {
5
+ width: '100%',
6
+ height: '100%',
7
+ position: 'relative',
8
+ backgroundColor: '#E5EEF5',
9
+ },
10
+ mapViewport: {
11
+ width: '100%',
12
+ height: '100%',
13
+ borderWidth: 0,
14
+ backgroundColor: '#E5EEF5',
15
+ },
16
+ });
@@ -0,0 +1,66 @@
1
+ import React, {useMemo} from 'react';
2
+ import {View} from 'react-native';
3
+
4
+ import DefaultGoogleMap from './DefaultGoogleMap.web';
5
+
6
+ import styles from './DefaultMap.styles';
7
+ import {
8
+ buildOpenStreetMapHtml,
9
+ resolveDefaultMapPayload,
10
+ } from './DefaultMap.shared';
11
+
12
+ export default function DefaultMap({
13
+ config = null,
14
+ addresses = null,
15
+ markerPayloads = [],
16
+ paths = [],
17
+ userCoordinates = null,
18
+ apiKey = '',
19
+ }) {
20
+ const resolvedPayload = useMemo(
21
+ () =>
22
+ resolveDefaultMapPayload({
23
+ config,
24
+ addresses,
25
+ markerPayloads,
26
+ paths,
27
+ userCoordinates,
28
+ apiKey,
29
+ }),
30
+ [apiKey, addresses, config, markerPayloads, paths, userCoordinates],
31
+ );
32
+
33
+ if (
34
+ resolvedPayload.markerPayloads.length === 0 &&
35
+ !resolvedPayload.userCoordinates &&
36
+ resolvedPayload.paths.length === 0
37
+ ) {
38
+ return null;
39
+ }
40
+
41
+ if (resolvedPayload.apiKey) {
42
+ return (
43
+ <DefaultGoogleMap
44
+ apiKey={resolvedPayload.apiKey}
45
+ markerPayloads={resolvedPayload.markerPayloads}
46
+ userCoordinates={resolvedPayload.userCoordinates}
47
+ paths={resolvedPayload.paths}
48
+ />
49
+ );
50
+ }
51
+
52
+ return (
53
+ <View style={styles.mapContainer}>
54
+ <iframe
55
+ title="Mapa"
56
+ srcDoc={buildOpenStreetMapHtml({
57
+ markerPayloads: resolvedPayload.markerPayloads,
58
+ paths: resolvedPayload.paths,
59
+ userCoordinates: resolvedPayload.userCoordinates,
60
+ routeColor: '#0EA5E9',
61
+ })}
62
+ style={{width: '100%', height: '100%', border: 0}}
63
+ />
64
+ </View>
65
+ );
66
+ }
@@ -0,0 +1,615 @@
1
+ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
2
+ import {ActivityIndicator, Image, Linking, Platform, Text, View} from 'react-native';
3
+ import {env} from '@env';
4
+
5
+ import styles from './DefaultNativeMap.styles';
6
+ import {
7
+ buildGoogleMapsNavigationUrl,
8
+ buildWazeNavigationUrl,
9
+ buildAndroidWebMapHtml,
10
+ resolveWebViewBaseUrlForDomain,
11
+ } from './DefaultNativeMap.shared';
12
+ import {extractMapCoordinates} from './DefaultMap.shared';
13
+
14
+ const getNativeMapComponents = () => {
15
+ if (Platform.OS === 'android') {
16
+ return null;
17
+ }
18
+
19
+ try {
20
+ return require('react-native-maps');
21
+ } catch {
22
+ return null;
23
+ }
24
+ };
25
+
26
+ const getNativeWebView = () => {
27
+ if (Platform.OS !== 'android') {
28
+ return null;
29
+ }
30
+
31
+ try {
32
+ return require('react-native-webview').WebView;
33
+ } catch {
34
+ return null;
35
+ }
36
+ };
37
+
38
+ const nativeMapComponents = getNativeMapComponents();
39
+ const NativeWebView = getNativeWebView();
40
+ const NativeMapView = nativeMapComponents?.default || null;
41
+ const Marker = nativeMapComponents?.Marker || null;
42
+ const Callout = nativeMapComponents?.Callout || null;
43
+ const CalloutSubview = nativeMapComponents?.CalloutSubview || null;
44
+ const Polyline = nativeMapComponents?.Polyline || null;
45
+ const PROVIDER_GOOGLE = nativeMapComponents?.PROVIDER_GOOGLE || null;
46
+ export const HAS_NATIVE_MAP_SUPPORT = Boolean(
47
+ (Platform.OS === 'android' && NativeWebView) ||
48
+ (NativeMapView && Marker && Callout),
49
+ );
50
+
51
+ const DEFAULT_ANDROID_ERROR_MESSAGE = 'Nao foi possivel carregar o mapa no Android.';
52
+ const GOOGLE_MAPS_WEBVIEW_ERROR_PATTERN =
53
+ /google maps|maps api|referernotallowedmaperror|billingnotenabledmaperror|apikey|invalidkeymaperror|apinotactivatedmaperror/i;
54
+
55
+ const DEFAULT_REGION = {
56
+ latitude: -23.55052,
57
+ longitude: -46.633308,
58
+ latitudeDelta: 0.24,
59
+ longitudeDelta: 0.24,
60
+ };
61
+
62
+ const decodePolyline = encoded => {
63
+ if (!encoded) {
64
+ return [];
65
+ }
66
+
67
+ const coordinates = [];
68
+ let index = 0;
69
+ let latitude = 0;
70
+ let longitude = 0;
71
+
72
+ while (index < encoded.length) {
73
+ let shift = 0;
74
+ let result = 0;
75
+ let byte = null;
76
+
77
+ do {
78
+ byte = encoded.charCodeAt(index++) - 63;
79
+ result |= (byte & 0x1f) << shift;
80
+ shift += 5;
81
+ } while (byte >= 0x20);
82
+
83
+ latitude += result & 1 ? ~(result >> 1) : result >> 1;
84
+
85
+ shift = 0;
86
+ result = 0;
87
+
88
+ do {
89
+ byte = encoded.charCodeAt(index++) - 63;
90
+ result |= (byte & 0x1f) << shift;
91
+ shift += 5;
92
+ } while (byte >= 0x20);
93
+
94
+ longitude += result & 1 ? ~(result >> 1) : result >> 1;
95
+
96
+ coordinates.push({
97
+ latitude: latitude / 1e5,
98
+ longitude: longitude / 1e5,
99
+ });
100
+ }
101
+
102
+ return coordinates;
103
+ };
104
+
105
+ const fetchRouteCoordinates = async ({apiKey, origin, destination}) => {
106
+ if (
107
+ !apiKey ||
108
+ !Number.isFinite(origin?.latitude) ||
109
+ !Number.isFinite(origin?.longitude) ||
110
+ !Number.isFinite(destination?.latitude) ||
111
+ !Number.isFinite(destination?.longitude)
112
+ ) {
113
+ return [];
114
+ }
115
+
116
+ const response = await fetch(
117
+ `https://maps.googleapis.com/maps/api/directions/json?origin=${encodeURIComponent(
118
+ `${origin.latitude},${origin.longitude}`,
119
+ )}&destination=${encodeURIComponent(
120
+ `${destination.latitude},${destination.longitude}`,
121
+ )}&mode=driving&key=${encodeURIComponent(apiKey)}`,
122
+ );
123
+ const payload = await response.json();
124
+ const encodedPoints = payload?.routes?.[0]?.overview_polyline?.points;
125
+
126
+ if (payload?.status !== 'OK' || !encodedPoints) {
127
+ return [];
128
+ }
129
+
130
+ return decodePolyline(encodedPoints);
131
+ };
132
+
133
+ const buildRegion = coordinates => {
134
+ if (!Array.isArray(coordinates) || coordinates.length === 0) {
135
+ return DEFAULT_REGION;
136
+ }
137
+
138
+ if (coordinates.length === 1) {
139
+ return {
140
+ latitude: coordinates[0].latitude,
141
+ longitude: coordinates[0].longitude,
142
+ latitudeDelta: 0.04,
143
+ longitudeDelta: 0.04,
144
+ };
145
+ }
146
+
147
+ const latitudes = coordinates.map(item => item.latitude);
148
+ const longitudes = coordinates.map(item => item.longitude);
149
+ const minLatitude = Math.min(...latitudes);
150
+ const maxLatitude = Math.max(...latitudes);
151
+ const minLongitude = Math.min(...longitudes);
152
+ const maxLongitude = Math.max(...longitudes);
153
+
154
+ return {
155
+ latitude: (minLatitude + maxLatitude) / 2,
156
+ longitude: (minLongitude + maxLongitude) / 2,
157
+ latitudeDelta: Math.max((maxLatitude - minLatitude) * 1.45, 0.04),
158
+ longitudeDelta: Math.max((maxLongitude - minLongitude) * 1.45, 0.04),
159
+ };
160
+ };
161
+
162
+ const MetaRow = ({label, value}) => {
163
+ if (!value) {
164
+ return null;
165
+ }
166
+
167
+ return (
168
+ <View style={styles.metaRow}>
169
+ <Text style={styles.metaLabel}>{label}</Text>
170
+ <Text style={styles.metaValue}>{value}</Text>
171
+ </View>
172
+ );
173
+ };
174
+
175
+ const CalloutAction = ({label, onPress, primary = false}) => {
176
+ if (!CalloutSubview || !label || typeof onPress !== 'function') {
177
+ return null;
178
+ }
179
+
180
+ return (
181
+ <CalloutSubview
182
+ onPress={onPress}
183
+ style={[styles.actionButton, primary && styles.actionButtonPrimary]}>
184
+ <Text style={[styles.actionText, primary && styles.actionTextPrimary]}>
185
+ {label}
186
+ </Text>
187
+ </CalloutSubview>
188
+ );
189
+ };
190
+
191
+ const resolveMarkerNavigationUrls = item => {
192
+ const position = {
193
+ latitude: Number(item?.latitude),
194
+ longitude: Number(item?.longitude),
195
+ };
196
+
197
+ return {
198
+ googleMapsUrl:
199
+ item?.googleMapsUrl ||
200
+ buildGoogleMapsNavigationUrl(position),
201
+ wazeUrl:
202
+ item?.wazeUrl ||
203
+ buildWazeNavigationUrl(position),
204
+ };
205
+ };
206
+
207
+ export default function DefaultNativeMap({
208
+ apiKey = '',
209
+ markerPayloads = [],
210
+ paths = [],
211
+ routeColor = '#0EA5E9',
212
+ userCoordinates = null,
213
+ }) {
214
+ const mapRef = useRef(null);
215
+ const [mapReady, setMapReady] = useState(false);
216
+ const [selectedMarkerId, setSelectedMarkerId] = useState(null);
217
+ const [routeCoordinates, setRouteCoordinates] = useState([]);
218
+ const [androidMapState, setAndroidMapState] = useState('loading');
219
+ const [androidMapErrorMessage, setAndroidMapErrorMessage] = useState('');
220
+ const hasUserCoordinates =
221
+ Number.isFinite(userCoordinates?.latitude) &&
222
+ Number.isFinite(userCoordinates?.longitude);
223
+ const baseMapCoordinates = useMemo(() => {
224
+ const coordinates = markerPayloads
225
+ .map(item => ({
226
+ latitude: Number(item?.latitude),
227
+ longitude: Number(item?.longitude),
228
+ }))
229
+ .filter(
230
+ item => Number.isFinite(item.latitude) && Number.isFinite(item.longitude),
231
+ );
232
+
233
+ if (hasUserCoordinates) {
234
+ coordinates.push({
235
+ latitude: Number(userCoordinates.latitude),
236
+ longitude: Number(userCoordinates.longitude),
237
+ });
238
+ }
239
+
240
+ return coordinates;
241
+ }, [hasUserCoordinates, markerPayloads, userCoordinates]);
242
+ const pathCoordinates = useMemo(
243
+ () =>
244
+ (Array.isArray(paths) ? paths : []).flatMap(path => {
245
+ const from = extractMapCoordinates(path?.from || path?.origin || path?.start);
246
+ const to = extractMapCoordinates(path?.to || path?.destination || path?.end);
247
+
248
+ return [from, to].filter(Boolean);
249
+ }),
250
+ [paths],
251
+ );
252
+ const selectedMarker = useMemo(
253
+ () => markerPayloads.find(item => item.id === selectedMarkerId) || null,
254
+ [markerPayloads, selectedMarkerId],
255
+ );
256
+ const focusCoordinates = useMemo(() => {
257
+ const combinedCoordinates = [...baseMapCoordinates, ...pathCoordinates];
258
+
259
+ if (routeCoordinates.length > 1) {
260
+ return routeCoordinates;
261
+ }
262
+
263
+ if (
264
+ hasUserCoordinates &&
265
+ Number.isFinite(selectedMarker?.latitude) &&
266
+ Number.isFinite(selectedMarker?.longitude)
267
+ ) {
268
+ return [
269
+ {
270
+ latitude: Number(userCoordinates.latitude),
271
+ longitude: Number(userCoordinates.longitude),
272
+ },
273
+ {
274
+ latitude: Number(selectedMarker.latitude),
275
+ longitude: Number(selectedMarker.longitude),
276
+ },
277
+ ];
278
+ }
279
+
280
+ return combinedCoordinates;
281
+ }, [
282
+ baseMapCoordinates,
283
+ hasUserCoordinates,
284
+ routeCoordinates,
285
+ pathCoordinates,
286
+ selectedMarker?.latitude,
287
+ selectedMarker?.longitude,
288
+ userCoordinates,
289
+ ]);
290
+
291
+ const initialRegion = useMemo(() => buildRegion(focusCoordinates), [focusCoordinates]);
292
+ const androidMapBaseUrl = useMemo(
293
+ () => resolveWebViewBaseUrlForDomain(env?.DOMAIN),
294
+ [],
295
+ );
296
+ const androidMapHtml = useMemo(() => {
297
+ if (Platform.OS !== 'android') {
298
+ return '';
299
+ }
300
+
301
+ return buildAndroidWebMapHtml({
302
+ apiKey,
303
+ markerPayloads,
304
+ paths,
305
+ routeColor,
306
+ userCoordinates,
307
+ });
308
+ }, [apiKey, markerPayloads, paths, routeColor, userCoordinates]);
309
+
310
+ useEffect(() => {
311
+ if (!selectedMarkerId) {
312
+ return;
313
+ }
314
+
315
+ if (!selectedMarker) {
316
+ setSelectedMarkerId(null);
317
+ }
318
+ }, [selectedMarker, selectedMarkerId]);
319
+
320
+ useEffect(() => {
321
+ if (Platform.OS !== 'android') {
322
+ return undefined;
323
+ }
324
+
325
+ setAndroidMapState('loading');
326
+ setAndroidMapErrorMessage('');
327
+ return undefined;
328
+ }, [androidMapHtml]);
329
+
330
+ useEffect(() => {
331
+ if (!hasUserCoordinates || !selectedMarker || !apiKey || !Polyline) {
332
+ setRouteCoordinates([]);
333
+ return undefined;
334
+ }
335
+
336
+ let cancelled = false;
337
+
338
+ fetchRouteCoordinates({
339
+ apiKey,
340
+ origin: {
341
+ latitude: Number(userCoordinates.latitude),
342
+ longitude: Number(userCoordinates.longitude),
343
+ },
344
+ destination: {
345
+ latitude: Number(selectedMarker.latitude),
346
+ longitude: Number(selectedMarker.longitude),
347
+ },
348
+ })
349
+ .then(coordinates => {
350
+ if (!cancelled) {
351
+ setRouteCoordinates(Array.isArray(coordinates) ? coordinates : []);
352
+ }
353
+ })
354
+ .catch(() => {
355
+ if (!cancelled) {
356
+ setRouteCoordinates([]);
357
+ }
358
+ });
359
+
360
+ return () => {
361
+ cancelled = true;
362
+ };
363
+ }, [Polyline, apiKey, hasUserCoordinates, selectedMarker, userCoordinates]);
364
+
365
+ useEffect(() => {
366
+ if (!mapReady || !mapRef.current || focusCoordinates.length === 0) {
367
+ return undefined;
368
+ }
369
+
370
+ const timeoutId = setTimeout(() => {
371
+ if (!mapRef.current) {
372
+ return;
373
+ }
374
+
375
+ if (focusCoordinates.length === 1) {
376
+ mapRef.current.animateToRegion(buildRegion(focusCoordinates), 250);
377
+ return;
378
+ }
379
+
380
+ mapRef.current.fitToCoordinates(focusCoordinates, {
381
+ animated: true,
382
+ edgePadding: {
383
+ top: 56,
384
+ right: 32,
385
+ bottom: 56,
386
+ left: 32,
387
+ },
388
+ });
389
+ }, 60);
390
+
391
+ return () => clearTimeout(timeoutId);
392
+ }, [focusCoordinates, mapReady]);
393
+
394
+ const openExternalUrl = useCallback(url => {
395
+ const normalizedUrl = String(url || '').trim();
396
+ if (!normalizedUrl) {
397
+ return;
398
+ }
399
+
400
+ Linking.openURL(normalizedUrl).catch(() => {});
401
+ }, []);
402
+
403
+ const handleAndroidNavigation = useCallback(
404
+ request => {
405
+ const url = String(request?.url || '');
406
+
407
+ if (!url || url === 'about:blank') {
408
+ return true;
409
+ }
410
+
411
+ if (
412
+ url.includes('google.com/maps') ||
413
+ url.includes('maps.google.com') ||
414
+ url.includes('waze.com/ul')
415
+ ) {
416
+ openExternalUrl(url);
417
+ return false;
418
+ }
419
+
420
+ return true;
421
+ },
422
+ [openExternalUrl],
423
+ );
424
+
425
+ if (!apiKey || (markerPayloads.length === 0 && !hasUserCoordinates && pathCoordinates.length === 0)) {
426
+ return null;
427
+ }
428
+
429
+ if (Platform.OS === 'android') {
430
+ return (
431
+ <View style={styles.mapContainer}>
432
+ <NativeWebView
433
+ source={{html: androidMapHtml, baseUrl: androidMapBaseUrl}}
434
+ style={styles.mapViewport}
435
+ originWhitelist={['*']}
436
+ javaScriptEnabled
437
+ domStorageEnabled
438
+ scrollEnabled={false}
439
+ bounces={false}
440
+ overScrollMode="never"
441
+ setSupportMultipleWindows={false}
442
+ onShouldStartLoadWithRequest={handleAndroidNavigation}
443
+ onMessage={event => {
444
+ try {
445
+ const message = JSON.parse(event.nativeEvent.data);
446
+
447
+ if (message?.type === 'ready') {
448
+ setAndroidMapState('ready');
449
+ setAndroidMapErrorMessage('');
450
+ return;
451
+ }
452
+
453
+ if (message?.type === 'open-url') {
454
+ openExternalUrl(message?.url);
455
+ return;
456
+ }
457
+
458
+ if (message?.type === 'error') {
459
+ setAndroidMapState('error');
460
+ setAndroidMapErrorMessage(
461
+ String(message?.message || '').trim() || DEFAULT_ANDROID_ERROR_MESSAGE,
462
+ );
463
+ return;
464
+ }
465
+
466
+ if (message?.type === 'window-error') {
467
+ setAndroidMapState('error');
468
+ setAndroidMapErrorMessage(
469
+ String(message?.message || '').trim() || DEFAULT_ANDROID_ERROR_MESSAGE,
470
+ );
471
+ return;
472
+ }
473
+
474
+ if (message?.type === 'console') {
475
+ const consoleMessage = String(message?.message || '').trim();
476
+
477
+ if (GOOGLE_MAPS_WEBVIEW_ERROR_PATTERN.test(consoleMessage)) {
478
+ setAndroidMapErrorMessage(consoleMessage);
479
+ }
480
+ }
481
+ } catch {
482
+ setAndroidMapState('error');
483
+ setAndroidMapErrorMessage(DEFAULT_ANDROID_ERROR_MESSAGE);
484
+ }
485
+ }}
486
+ onError={() => {
487
+ setAndroidMapState('error');
488
+ setAndroidMapErrorMessage(current =>
489
+ current || DEFAULT_ANDROID_ERROR_MESSAGE,
490
+ );
491
+ }}
492
+ />
493
+ {androidMapState !== 'ready' ? (
494
+ <View style={styles.mapOverlay}>
495
+ {androidMapState !== 'error' ? (
496
+ <ActivityIndicator color={routeColor} />
497
+ ) : null}
498
+ <Text style={styles.mapOverlayText}>
499
+ {androidMapState === 'error'
500
+ ? androidMapErrorMessage || DEFAULT_ANDROID_ERROR_MESSAGE
501
+ : 'Carregando mapa das unidades...'}
502
+ </Text>
503
+ </View>
504
+ ) : null}
505
+ </View>
506
+ );
507
+ }
508
+
509
+ return (
510
+ <NativeMapView
511
+ ref={mapRef}
512
+ style={styles.mapViewport}
513
+ initialRegion={initialRegion}
514
+ provider={Platform.OS === 'android' && PROVIDER_GOOGLE ? PROVIDER_GOOGLE : undefined}
515
+ showsUserLocation={false}
516
+ showsMyLocationButton={false}
517
+ showsCompass
518
+ rotateEnabled
519
+ toolbarEnabled={false}
520
+ onMapReady={() => setMapReady(true)}>
521
+ {hasUserCoordinates ? (
522
+ <Marker
523
+ key="shop-user-location"
524
+ coordinate={{
525
+ latitude: Number(userCoordinates.latitude),
526
+ longitude: Number(userCoordinates.longitude),
527
+ }}
528
+ title="Sua localizacao"
529
+ description="Posicao atual do cliente"
530
+ onPress={() => {
531
+ setSelectedMarkerId(null);
532
+ setRouteCoordinates([]);
533
+ }}
534
+ />
535
+ ) : null}
536
+ {Polyline && routeCoordinates.length > 1 ? (
537
+ <Polyline
538
+ coordinates={routeCoordinates}
539
+ strokeColor={routeColor}
540
+ strokeWidth={5}
541
+ />
542
+ ) : null}
543
+ {Polyline && Array.isArray(paths)
544
+ ? paths.map(path => {
545
+ const from = extractMapCoordinates(path?.from || path?.origin || path?.start);
546
+ const to = extractMapCoordinates(path?.to || path?.destination || path?.end);
547
+
548
+ if (!from || !to) {
549
+ return null;
550
+ }
551
+
552
+ return (
553
+ <Polyline
554
+ key={path?.id || `${from.latitude}:${from.longitude}-${to.latitude}:${to.longitude}`}
555
+ coordinates={[from, to]}
556
+ strokeColor={path?.color || routeColor}
557
+ strokeWidth={4}
558
+ />
559
+ );
560
+ })
561
+ : null}
562
+ {markerPayloads.map(item => {
563
+ const navigationUrls = resolveMarkerNavigationUrls(item);
564
+
565
+ return (
566
+ <Marker
567
+ key={item.id}
568
+ coordinate={{
569
+ latitude: Number(item.latitude),
570
+ longitude: Number(item.longitude),
571
+ }}
572
+ title={item.title}
573
+ description={item.addressLine}
574
+ onPress={() => setSelectedMarkerId(item.id)}>
575
+ {item.markerIconUrl ? (
576
+ <View style={styles.markerWrap}>
577
+ <Image
578
+ source={{uri: item.markerIconUrl}}
579
+ style={styles.markerIcon}
580
+ resizeMode="contain"
581
+ />
582
+ </View>
583
+ ) : null}
584
+ <Callout tooltip>
585
+ <View style={styles.calloutCard}>
586
+ <Text style={styles.companyName}>{item.companyName}</Text>
587
+ <Text style={styles.title}>{item.title}</Text>
588
+ {item.addressLine ? <Text style={styles.line}>{item.addressLine}</Text> : null}
589
+ {item.addressExtra ? <Text style={styles.line}>{item.addressExtra}</Text> : null}
590
+
591
+ <View style={styles.metaList}>
592
+ <MetaRow label="Telefone" value={item.phoneLabel} />
593
+ <MetaRow label="Distancia" value={item.distanceLabel} />
594
+ <MetaRow label="Horario" value={item.openingHours} />
595
+ </View>
596
+
597
+ <View style={styles.actionsRow}>
598
+ <CalloutAction
599
+ label="Abrir no Maps"
600
+ onPress={() => openExternalUrl(navigationUrls.googleMapsUrl)}
601
+ primary
602
+ />
603
+ <CalloutAction
604
+ label="Waze"
605
+ onPress={() => openExternalUrl(navigationUrls.wazeUrl)}
606
+ />
607
+ </View>
608
+ </View>
609
+ </Callout>
610
+ </Marker>
611
+ );
612
+ })}
613
+ </NativeMapView>
614
+ );
615
+ }