@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.
Files changed (61) hide show
  1. package/AGENTS.md +47 -45
  2. package/package.json +20 -20
  3. package/src/react/components/errors/DefaultErrors.js +307 -325
  4. package/src/react/components/files/DefaultFile.js +46 -46
  5. package/src/react/components/filters/CompactFilterSelector.js +78 -24
  6. package/src/react/components/filters/CompactFilterSelector.styles.js +17 -5
  7. package/src/react/components/filters/DefaultColumnFilter.js +191 -57
  8. package/src/react/components/filters/DefaultExternalFilters.js +245 -182
  9. package/src/react/components/filters/DefaultSearch.js +131 -92
  10. package/src/react/components/filters/DefaultSearch.styles.js +65 -70
  11. package/src/react/components/form/DefaultForm.js +172 -151
  12. package/src/react/components/form/DefaultForm.styles.js +71 -66
  13. package/src/react/components/help/DefaultTooltip.js +87 -87
  14. package/src/react/components/help/DefaultTooltip.styles.js +61 -61
  15. package/src/react/components/inputs/DefaultDateInput.js +150 -0
  16. package/src/react/components/inputs/DefaultInput.js +162 -127
  17. package/src/react/components/inputs/DefaultInput.styles.js +6 -0
  18. package/src/react/components/inputs/DefaultSelect.js +320 -152
  19. package/src/react/components/inputs/defaultInputUtils.js +40 -19
  20. package/src/react/components/map/DefaultGoogleMap.styles.js +115 -9
  21. package/src/react/components/map/DefaultGoogleMap.web.js +560 -446
  22. package/src/react/components/map/DefaultMap.native.js +73 -71
  23. package/src/react/components/map/DefaultMap.styles.js +16 -16
  24. package/src/react/components/map/DefaultMap.web.js +68 -66
  25. package/src/react/components/map/DefaultNativeMap.native.js +307 -183
  26. package/src/react/components/map/DefaultNativeMap.shared.js +439 -340
  27. package/src/react/components/map/DefaultNativeMap.styles.js +156 -122
  28. package/src/react/components/table/DefaultColumnMenu.js +108 -0
  29. package/src/react/components/table/DefaultDebug.js +233 -0
  30. package/src/react/components/table/DefaultFiltersModal.js +142 -0
  31. package/src/react/components/table/DefaultModalButton.js +18 -0
  32. package/src/react/components/table/DefaultSearchModal.js +49 -0
  33. package/src/react/components/table/DefaultTable.js +345 -1846
  34. package/src/react/components/table/DefaultTable.styles.js +247 -136
  35. package/src/react/components/table/DefaultTable.utils.js +502 -0
  36. package/src/react/components/table/DefaultTableBody.js +26 -0
  37. package/src/react/components/table/DefaultTableCards.js +171 -0
  38. package/src/react/components/table/DefaultTableControls.js +208 -0
  39. package/src/react/components/table/DefaultTableEmptyState.js +22 -0
  40. package/src/react/components/table/DefaultTableFooter.js +172 -0
  41. package/src/react/components/table/DefaultTableInput.js +145 -0
  42. package/src/react/components/table/DefaultTableRows.js +340 -0
  43. package/src/react/components/table/DefaultTableSearch.js +100 -0
  44. package/src/react/components/table/DefaultTableToolbar.js +98 -0
  45. package/src/react/components/table/DefaultToolbarAction.js +56 -0
  46. package/src/react/components/table/useDefaultTablePagination.js +370 -0
  47. package/src/react/components/table/useDefaultTableSorting.js +126 -0
  48. package/src/react/components/table/useDefaultTableTheme.js +58 -0
  49. package/src/react/index.js +10 -10
  50. package/src/react/utils/tableVisibleColumnsPreferences.js +239 -211
  51. package/src/store/default/actions.js +51 -13
  52. package/src/store/default/getters.js +10 -8
  53. package/src/store/default/mutation_types.js +4 -2
  54. package/src/store/default/mutations.js +10 -1
  55. package/src/tests/react/components/errors/DefaultErrors.test.js +162 -162
  56. package/src/tests/react/components/files/DefaultFile.test.js +80 -80
  57. package/src/tests/react/components/form/DefaultForm.test.js +81 -0
  58. package/src/tests/react/components/inputs/DefaultSelect.test.js +185 -0
  59. package/src/tests/react/components/table/DefaultTable.test.js +1183 -0
  60. package/src/tests/react/store/defaultActions.test.js +59 -0
  61. package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +247 -168
@@ -1,15 +1,16 @@
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';
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
+ resolveMapPopupTheme,
12
+ } from './DefaultNativeMap.shared';
13
+ import {extractMapCoordinates} from './DefaultMap.shared';
13
14
 
14
15
  const getNativeMapComponents = () => {
15
16
  if (Platform.OS === 'android') {
@@ -102,7 +103,7 @@ const decodePolyline = encoded => {
102
103
  return coordinates;
103
104
  };
104
105
 
105
- const fetchRouteCoordinates = async ({apiKey, origin, destination}) => {
106
+ const fetchRouteData = async ({apiKey, origin, destination}) => {
106
107
  if (
107
108
  !apiKey ||
108
109
  !Number.isFinite(origin?.latitude) ||
@@ -110,7 +111,11 @@ const fetchRouteCoordinates = async ({apiKey, origin, destination}) => {
110
111
  !Number.isFinite(destination?.latitude) ||
111
112
  !Number.isFinite(destination?.longitude)
112
113
  ) {
113
- return [];
114
+ return {
115
+ coordinates: [],
116
+ distanceLabel: '',
117
+ durationLabel: '',
118
+ };
114
119
  }
115
120
 
116
121
  const response = await fetch(
@@ -122,12 +127,21 @@ const fetchRouteCoordinates = async ({apiKey, origin, destination}) => {
122
127
  );
123
128
  const payload = await response.json();
124
129
  const encodedPoints = payload?.routes?.[0]?.overview_polyline?.points;
130
+ const activeLeg = payload?.routes?.[0]?.legs?.[0] || null;
125
131
 
126
132
  if (payload?.status !== 'OK' || !encodedPoints) {
127
- return [];
133
+ return {
134
+ coordinates: [],
135
+ distanceLabel: '',
136
+ durationLabel: '',
137
+ };
128
138
  }
129
139
 
130
- return decodePolyline(encodedPoints);
140
+ return {
141
+ coordinates: decodePolyline(encodedPoints),
142
+ distanceLabel: String(activeLeg?.distance?.text || '').trim(),
143
+ durationLabel: String(activeLeg?.duration?.text || '').trim(),
144
+ };
131
145
  };
132
146
 
133
147
  const buildRegion = coordinates => {
@@ -159,20 +173,24 @@ const buildRegion = coordinates => {
159
173
  };
160
174
  };
161
175
 
162
- const MetaRow = ({label, value}) => {
176
+ const MetaRow = ({label, value, popupTheme}) => {
163
177
  if (!value) {
164
178
  return null;
165
179
  }
166
180
 
167
181
  return (
168
182
  <View style={styles.metaRow}>
169
- <Text style={styles.metaLabel}>{label}</Text>
170
- <Text style={styles.metaValue}>{value}</Text>
183
+ <Text style={[styles.metaLabel, popupTheme && {color: popupTheme.textMuted}]}>
184
+ {label}
185
+ </Text>
186
+ <Text style={[styles.metaValue, popupTheme && {color: popupTheme.modalText}]}>
187
+ {value}
188
+ </Text>
171
189
  </View>
172
190
  );
173
191
  };
174
192
 
175
- const CalloutAction = ({label, onPress, primary = false}) => {
193
+ const CalloutAction = ({label, onPress, popupTheme, primary = false}) => {
176
194
  if (!CalloutSubview || !label || typeof onPress !== 'function') {
177
195
  return null;
178
196
  }
@@ -180,47 +198,73 @@ const CalloutAction = ({label, onPress, primary = false}) => {
180
198
  return (
181
199
  <CalloutSubview
182
200
  onPress={onPress}
183
- style={[styles.actionButton, primary && styles.actionButtonPrimary]}>
184
- <Text style={[styles.actionText, primary && styles.actionTextPrimary]}>
201
+ style={[
202
+ styles.actionButton,
203
+ primary && styles.actionButtonPrimary,
204
+ popupTheme && {
205
+ borderColor: popupTheme.buttonBackground,
206
+ backgroundColor: popupTheme.buttonBackground,
207
+ },
208
+ ]}>
209
+ <Text
210
+ style={[
211
+ styles.actionText,
212
+ primary && styles.actionTextPrimary,
213
+ popupTheme && {color: popupTheme.buttonText},
214
+ ]}>
185
215
  {label}
186
216
  </Text>
187
217
  </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
- }) {
218
+ );
219
+ };
220
+
221
+ const buildTravelSummary = (item, routeSummary = null) => {
222
+ const distanceLabel =
223
+ routeSummary?.distanceLabel || item?.distanceLabel || '';
224
+ const durationLabel =
225
+ routeSummary?.durationLabel ||
226
+ item?.durationLabel ||
227
+ item?.travelDurationLabel ||
228
+ '';
229
+
230
+ return [distanceLabel, durationLabel].filter(Boolean).join(' • ');
231
+ };
232
+
233
+ const resolveMarkerNavigationUrls = item => {
234
+ const position = {
235
+ latitude: Number(item?.latitude),
236
+ longitude: Number(item?.longitude),
237
+ };
238
+
239
+ return {
240
+ googleMapsUrl:
241
+ item?.googleMapsUrl ||
242
+ buildGoogleMapsNavigationUrl(position),
243
+ wazeUrl:
244
+ item?.wazeUrl ||
245
+ buildWazeNavigationUrl(position),
246
+ };
247
+ };
248
+
249
+ export default function DefaultNativeMap({
250
+ apiKey = '',
251
+ markerPayloads = [],
252
+ paths = [],
253
+ routeColor = '#0EA5E9',
254
+ userCoordinates = null,
255
+ popupTheme = null,
256
+ }) {
214
257
  const mapRef = useRef(null);
215
258
  const [mapReady, setMapReady] = useState(false);
216
259
  const [selectedMarkerId, setSelectedMarkerId] = useState(null);
217
260
  const [routeCoordinates, setRouteCoordinates] = useState([]);
261
+ const [selectedRouteSummary, setSelectedRouteSummary] = useState(null);
218
262
  const [androidMapState, setAndroidMapState] = useState('loading');
219
263
  const [androidMapErrorMessage, setAndroidMapErrorMessage] = useState('');
220
264
  const hasUserCoordinates =
221
265
  Number.isFinite(userCoordinates?.latitude) &&
222
266
  Number.isFinite(userCoordinates?.longitude);
223
- const baseMapCoordinates = useMemo(() => {
267
+ const baseMapCoordinates = useMemo(() => {
224
268
  const coordinates = markerPayloads
225
269
  .map(item => ({
226
270
  latitude: Number(item?.latitude),
@@ -237,28 +281,28 @@ export default function DefaultNativeMap({
237
281
  });
238
282
  }
239
283
 
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
- }
284
+ return coordinates;
285
+ }, [hasUserCoordinates, markerPayloads, userCoordinates]);
286
+ const pathCoordinates = useMemo(
287
+ () =>
288
+ (Array.isArray(paths) ? paths : []).flatMap(path => {
289
+ const from = extractMapCoordinates(path?.from || path?.origin || path?.start);
290
+ const to = extractMapCoordinates(path?.to || path?.destination || path?.end);
291
+
292
+ return [from, to].filter(Boolean);
293
+ }),
294
+ [paths],
295
+ );
296
+ const selectedMarker = useMemo(
297
+ () => markerPayloads.find(item => item.id === selectedMarkerId) || null,
298
+ [markerPayloads, selectedMarkerId],
299
+ );
300
+ const focusCoordinates = useMemo(() => {
301
+ const combinedCoordinates = [...baseMapCoordinates, ...pathCoordinates];
302
+
303
+ if (routeCoordinates.length > 1) {
304
+ return routeCoordinates;
305
+ }
262
306
 
263
307
  if (
264
308
  hasUserCoordinates &&
@@ -276,44 +320,51 @@ export default function DefaultNativeMap({
276
320
  },
277
321
  ];
278
322
  }
279
-
280
- return combinedCoordinates;
281
- }, [
282
- baseMapCoordinates,
283
- hasUserCoordinates,
284
- routeCoordinates,
285
- pathCoordinates,
286
- selectedMarker?.latitude,
287
- selectedMarker?.longitude,
288
- userCoordinates,
289
- ]);
323
+
324
+ return combinedCoordinates;
325
+ }, [
326
+ baseMapCoordinates,
327
+ hasUserCoordinates,
328
+ routeCoordinates,
329
+ pathCoordinates,
330
+ selectedMarker?.latitude,
331
+ selectedMarker?.longitude,
332
+ userCoordinates,
333
+ ]);
290
334
 
291
335
  const initialRegion = useMemo(() => buildRegion(focusCoordinates), [focusCoordinates]);
292
336
  const androidMapBaseUrl = useMemo(
293
337
  () => resolveWebViewBaseUrlForDomain(env?.DOMAIN),
294
338
  [],
295
339
  );
340
+ const resolvedPopupTheme = useMemo(
341
+ () => resolveMapPopupTheme(popupTheme),
342
+ [popupTheme],
343
+ );
296
344
  const androidMapHtml = useMemo(() => {
297
345
  if (Platform.OS !== 'android') {
298
346
  return '';
299
347
  }
300
348
 
301
- return buildAndroidWebMapHtml({
302
- apiKey,
303
- markerPayloads,
304
- paths,
305
- routeColor,
306
- userCoordinates,
307
- });
308
- }, [apiKey, markerPayloads, paths, routeColor, userCoordinates]);
349
+ return buildAndroidWebMapHtml({
350
+ apiKey,
351
+ markerPayloads,
352
+ paths,
353
+ routeColor,
354
+ userCoordinates,
355
+ popupTheme: resolvedPopupTheme,
356
+ });
357
+ }, [apiKey, markerPayloads, paths, resolvedPopupTheme, routeColor, userCoordinates]);
309
358
 
310
359
  useEffect(() => {
311
360
  if (!selectedMarkerId) {
361
+ setSelectedRouteSummary(null);
312
362
  return;
313
363
  }
314
364
 
315
365
  if (!selectedMarker) {
316
366
  setSelectedMarkerId(null);
367
+ setSelectedRouteSummary(null);
317
368
  }
318
369
  }, [selectedMarker, selectedMarkerId]);
319
370
 
@@ -330,12 +381,13 @@ export default function DefaultNativeMap({
330
381
  useEffect(() => {
331
382
  if (!hasUserCoordinates || !selectedMarker || !apiKey || !Polyline) {
332
383
  setRouteCoordinates([]);
384
+ setSelectedRouteSummary(null);
333
385
  return undefined;
334
386
  }
335
387
 
336
388
  let cancelled = false;
337
389
 
338
- fetchRouteCoordinates({
390
+ fetchRouteData({
339
391
  apiKey,
340
392
  origin: {
341
393
  latitude: Number(userCoordinates.latitude),
@@ -346,14 +398,21 @@ export default function DefaultNativeMap({
346
398
  longitude: Number(selectedMarker.longitude),
347
399
  },
348
400
  })
349
- .then(coordinates => {
401
+ .then(routeData => {
350
402
  if (!cancelled) {
351
- setRouteCoordinates(Array.isArray(coordinates) ? coordinates : []);
403
+ setRouteCoordinates(
404
+ Array.isArray(routeData?.coordinates) ? routeData.coordinates : [],
405
+ );
406
+ setSelectedRouteSummary({
407
+ distanceLabel: String(routeData?.distanceLabel || '').trim(),
408
+ durationLabel: String(routeData?.durationLabel || '').trim(),
409
+ });
352
410
  }
353
411
  })
354
412
  .catch(() => {
355
413
  if (!cancelled) {
356
414
  setRouteCoordinates([]);
415
+ setSelectedRouteSummary(null);
357
416
  }
358
417
  });
359
418
 
@@ -391,11 +450,11 @@ export default function DefaultNativeMap({
391
450
  return () => clearTimeout(timeoutId);
392
451
  }, [focusCoordinates, mapReady]);
393
452
 
394
- const openExternalUrl = useCallback(url => {
395
- const normalizedUrl = String(url || '').trim();
396
- if (!normalizedUrl) {
397
- return;
398
- }
453
+ const openExternalUrl = useCallback(url => {
454
+ const normalizedUrl = String(url || '').trim();
455
+ if (!normalizedUrl) {
456
+ return;
457
+ }
399
458
 
400
459
  Linking.openURL(normalizedUrl).catch(() => {});
401
460
  }, []);
@@ -419,14 +478,14 @@ export default function DefaultNativeMap({
419
478
 
420
479
  return true;
421
480
  },
422
- [openExternalUrl],
423
- );
424
-
425
- if (!apiKey || (markerPayloads.length === 0 && !hasUserCoordinates && pathCoordinates.length === 0)) {
426
- return null;
427
- }
428
-
429
- if (Platform.OS === 'android') {
481
+ [openExternalUrl],
482
+ );
483
+
484
+ if (!apiKey || (markerPayloads.length === 0 && !hasUserCoordinates && pathCoordinates.length === 0)) {
485
+ return null;
486
+ }
487
+
488
+ if (Platform.OS === 'android') {
430
489
  return (
431
490
  <View style={styles.mapContainer}>
432
491
  <NativeWebView
@@ -530,86 +589,151 @@ export default function DefaultNativeMap({
530
589
  onPress={() => {
531
590
  setSelectedMarkerId(null);
532
591
  setRouteCoordinates([]);
592
+ setSelectedRouteSummary(null);
533
593
  }}
534
594
  />
535
595
  ) : 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
- })}
596
+ {Polyline && routeCoordinates.length > 1 ? (
597
+ <Polyline
598
+ coordinates={routeCoordinates}
599
+ strokeColor={routeColor}
600
+ strokeWidth={5}
601
+ />
602
+ ) : null}
603
+ {Polyline && Array.isArray(paths)
604
+ ? paths.map(path => {
605
+ const from = extractMapCoordinates(path?.from || path?.origin || path?.start);
606
+ const to = extractMapCoordinates(path?.to || path?.destination || path?.end);
607
+
608
+ if (!from || !to) {
609
+ return null;
610
+ }
611
+
612
+ return (
613
+ <Polyline
614
+ key={path?.id || `${from.latitude}:${from.longitude}-${to.latitude}:${to.longitude}`}
615
+ coordinates={[from, to]}
616
+ strokeColor={path?.color || routeColor}
617
+ strokeWidth={4}
618
+ />
619
+ );
620
+ })
621
+ : null}
622
+ {markerPayloads.map(item => {
623
+ const navigationUrls = resolveMarkerNavigationUrls(item);
624
+ const title = item?.unitAlias || item?.alias || item?.companyName || item?.title;
625
+ const travelSummary =
626
+ selectedMarkerId === item.id
627
+ ? buildTravelSummary(item, selectedRouteSummary)
628
+ : buildTravelSummary(item, null);
629
+ const logoFallback = String(title || 'CO')
630
+ .trim()
631
+ .split(/\s+/)
632
+ .slice(0, 2)
633
+ .map(chunk => chunk[0] || '')
634
+ .join('')
635
+ .toUpperCase();
636
+
637
+ return (
638
+ <Marker
639
+ key={item.id}
640
+ coordinate={{
641
+ latitude: Number(item.latitude),
642
+ longitude: Number(item.longitude),
643
+ }}
644
+ title={item.title}
645
+ description={item.addressLine}
646
+ onPress={() => setSelectedMarkerId(item.id)}>
647
+ {item.markerIconUrl ? (
648
+ <View style={styles.markerWrap}>
649
+ <Image
650
+ source={{uri: item.markerIconUrl}}
651
+ style={styles.markerIcon}
652
+ resizeMode="contain"
653
+ />
654
+ </View>
655
+ ) : null}
656
+ <Callout tooltip>
657
+ <View
658
+ style={[
659
+ styles.calloutCard,
660
+ {
661
+ backgroundColor: resolvedPopupTheme.modalBackground,
662
+ shadowColor: resolvedPopupTheme.modalShadow,
663
+ },
664
+ ]}>
665
+ <View style={styles.headerRow}>
666
+ <View
667
+ style={[
668
+ styles.logoWrap,
669
+ {borderColor: resolvedPopupTheme.dividerBorder},
670
+ ]}>
671
+ {item?.companyLogoUrl ? (
672
+ <Image
673
+ source={{uri: item.companyLogoUrl}}
674
+ style={styles.logoImage}
675
+ resizeMode="cover"
676
+ />
677
+ ) : (
678
+ <Text
679
+ style={[
680
+ styles.logoFallback,
681
+ {color: resolvedPopupTheme.buttonBackground},
682
+ ]}>
683
+ {logoFallback}
684
+ </Text>
685
+ )}
686
+ </View>
687
+ <View style={styles.headerContent}>
688
+ <Text
689
+ style={[
690
+ styles.title,
691
+ {color: resolvedPopupTheme.modalHeaderText},
692
+ ]}>
693
+ {title}
694
+ </Text>
695
+ </View>
696
+ </View>
697
+ {item.addressLine ? (
698
+ <Text style={[styles.line, {color: resolvedPopupTheme.modalText}]}>
699
+ {item.addressLine}
700
+ </Text>
701
+ ) : null}
702
+ {item.addressExtra ? (
703
+ <Text style={[styles.line, {color: resolvedPopupTheme.modalText}]}>
704
+ {item.addressExtra}
705
+ </Text>
706
+ ) : null}
707
+
708
+ {travelSummary ? (
709
+ <Text
710
+ style={[
711
+ styles.summaryText,
712
+ {color: resolvedPopupTheme.textMuted},
713
+ ]}>
714
+ {travelSummary}
715
+ </Text>
716
+ ) : null}
717
+
718
+ <View style={styles.actionsRow}>
719
+ <CalloutAction
720
+ label="Abrir no Maps"
721
+ onPress={() => openExternalUrl(navigationUrls.googleMapsUrl)}
722
+ popupTheme={resolvedPopupTheme}
723
+ primary
724
+ />
725
+ <CalloutAction
726
+ label="Abrir no Waze"
727
+ onPress={() => openExternalUrl(navigationUrls.wazeUrl)}
728
+ popupTheme={resolvedPopupTheme}
729
+ primary
730
+ />
731
+ </View>
732
+ </View>
733
+ </Callout>
734
+ </Marker>
735
+ );
736
+ })}
613
737
  </NativeMapView>
614
738
  );
615
739
  }