@controleonline/ui-default 1.0.269 → 1.0.272

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 (91) hide show
  1. package/.github/workflows/integration-source-gate.yml +68 -0
  2. package/.github/workflows/manager-worker.yml +19 -0
  3. package/AGENTS.md +46 -50
  4. package/docs/technical/Contrato-de-colunas.md +67 -0
  5. package/docs/technical/Default-Components.md +76 -0
  6. package/docs/technical/DefaultForm.md +80 -0
  7. package/docs/technical/DefaultInput-e-DefaultSelect.md +73 -0
  8. package/docs/technical/DefaultTable.md +203 -0
  9. package/docs/wiki.md +1 -0
  10. package/package.json +22 -21
  11. package/scripts/validate-wiki-pointer.cjs +13 -0
  12. package/src/react/components/address/DefaultAddress.js +435 -0
  13. package/src/react/components/address/DefaultAddress.styles.js +133 -0
  14. package/src/react/components/address/DefaultAddressMapPane.js +55 -0
  15. package/src/react/components/address/LatLonReadonlyFields.js +51 -0
  16. package/src/react/components/address/defaultAddressHelpers.js +163 -0
  17. package/src/react/components/address/defaultAddressStyles.js +136 -0
  18. package/src/react/components/address/defaultAddressUtils.js +129 -0
  19. package/src/react/components/extra-data/DefaultExtraData.js +152 -0
  20. package/src/react/components/extra-data/DefaultExtraData.styles.js +61 -0
  21. package/src/react/components/files/DefaultFile.js +231 -46
  22. package/src/react/components/files/DefaultFileColumn.js +123 -0
  23. package/src/react/components/files/DefaultFileColumn.styles.js +52 -0
  24. package/src/react/components/files/DefaultFileColumn.utils.js +104 -0
  25. package/src/react/components/filters/CompactFilterSelector.js +51 -35
  26. package/src/react/components/filters/DefaultColumnFilter.js +58 -41
  27. package/src/react/components/filters/DefaultExternalFilters.js +331 -265
  28. package/src/react/components/form/DefaultForm.js +99 -79
  29. package/src/react/components/help/DefaultTooltip.js +59 -33
  30. package/src/react/components/help/DefaultTooltip.styles.js +23 -3
  31. package/src/react/components/inputs/DefaultColorInput.js +153 -0
  32. package/src/react/components/inputs/DefaultFeatherIconPicker.js +146 -0
  33. package/src/react/components/inputs/DefaultFeatherIconPicker.styles.js +113 -0
  34. package/src/react/components/inputs/DefaultInput.js +69 -5
  35. package/src/react/components/inputs/DefaultInput.styles.js +26 -0
  36. package/src/react/components/inputs/DefaultSelect.js +134 -123
  37. package/src/react/components/inputs/defaultInputUtils.js +69 -43
  38. package/src/react/components/map/DefaultGoogleMap.styles.js +9 -2
  39. package/src/react/components/map/DefaultGoogleMap.web.js +49 -28
  40. package/src/react/components/map/DefaultMap.native.js +73 -73
  41. package/src/react/components/map/DefaultMap.shared.js +911 -762
  42. package/src/react/components/map/DefaultMap.web.js +124 -68
  43. package/src/react/components/table/DefaultColumnMenu.js +109 -108
  44. package/src/react/components/table/DefaultDebug.js +45 -38
  45. package/src/react/components/table/DefaultSearchModal.js +9 -1
  46. package/src/react/components/table/DefaultTable.grouping.js +98 -0
  47. package/src/react/components/table/DefaultTable.js +215 -109
  48. package/src/react/components/table/DefaultTable.styles.js +367 -314
  49. package/src/react/components/table/DefaultTableBody.js +13 -8
  50. package/src/react/components/table/DefaultTableCards.js +20 -1
  51. package/src/react/components/table/DefaultTableControls.js +180 -205
  52. package/src/react/components/table/DefaultTableImportModal.js +108 -0
  53. package/src/react/components/table/DefaultTableInput.js +1 -0
  54. package/src/react/components/table/DefaultTableRowActions.js +1 -1
  55. package/src/react/components/table/DefaultTableRows.js +224 -363
  56. package/src/react/components/table/DefaultTableSearch.js +28 -5
  57. package/src/react/components/table/DefaultTableToolbar.js +92 -3
  58. package/src/react/components/table/useDefaultTablePagination.js +56 -7
  59. package/src/react/components/table/useDefaultTableStoreSync.js +164 -0
  60. package/src/react/components/upload/DefaultUpload.js +436 -0
  61. package/src/react/components/upload/DefaultUpload.styles.js +153 -0
  62. package/src/react/components/upload/DefaultUploadAttachmentsList.js +138 -0
  63. package/src/react/components/upload/DefaultUploadManagerModal.js +247 -0
  64. package/src/react/components/upload/defaultUploadHelpers.js +108 -0
  65. package/src/react/components/upload/defaultUploadLibrary.js +248 -0
  66. package/src/react/components/upload/fileUpload.js +93 -0
  67. package/src/react/hooks/usePostalCodeLookup.js +145 -0
  68. package/src/react/index.js +6 -0
  69. package/src/react/services/addressFormUtils.js +196 -0
  70. package/src/react/services/addressGeo.js +227 -0
  71. package/src/react/utils/tableVisibleColumnsPreferences.js +29 -12
  72. package/src/tests/react/components/address/defaultAddressHelpers.test.js +104 -0
  73. package/src/tests/react/components/files/DefaultFile.test.js +6 -6
  74. package/src/tests/react/components/form/DefaultForm.test.js +123 -0
  75. package/src/tests/react/components/inputs/DefaultSelect.test.js +108 -79
  76. package/src/tests/react/components/table/DefaultTable.grouping.test.js +49 -0
  77. package/src/tests/react/components/table/DefaultTable.test.js +54 -2
  78. package/src/tests/react/components/table/DefaultTableControls.filters.test.mjs +21 -0
  79. package/src/tests/react/components/table/resolveRowBackgroundColor.test.mjs +45 -0
  80. package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +161 -0
  81. package/src/tests/react/components/upload/DefaultUpload.test.js +353 -0
  82. package/src/tests/react/components/upload/defaultUploadLibrary.test.js +82 -0
  83. package/src/tests/react/components/upload/peopleMediaLibraryPreview.test.mjs +57 -0
  84. package/src/tests/react/hooks/usePostalCodeLookup.test.js +40 -0
  85. package/src/tests/react/services/SMOKE-637-cep-hydrate.md +45 -0
  86. package/src/tests/react/services/addressFormUtils.test.js +176 -0
  87. package/src/tests/react/services/addressGeoLookup.async.test.js +104 -0
  88. package/src/tests/react/services/addressGeoLookup.test.js +105 -0
  89. package/src/tests/react/services/smoke-637/form-cep-12941040.png +0 -0
  90. package/src/tests/react/services/smoke-637/index.html +132 -0
  91. package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +84 -48
@@ -1,13 +1,13 @@
1
- import React, { useCallback, useMemo, useRef, useState } from 'react';
2
- import {
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import {
3
3
  Modal,
4
4
  Image,
5
5
  ScrollView,
6
- Text,
7
- TextInput,
8
- TouchableOpacity,
9
- View,
10
- } from 'react-native';
6
+ Text,
7
+ TextInput,
8
+ TouchableOpacity,
9
+ View,
10
+ } from 'react-native';
11
11
  import Icon from 'react-native-vector-icons/Feather';
12
12
  import { getAllStores, useStore } from '@store';
13
13
  import {
@@ -71,11 +71,13 @@ const resolveColumnListLoadParams = ({
71
71
  column,
72
72
  currentCompanyId,
73
73
  requestParams = {},
74
+ row = {},
74
75
  searchValue = '',
76
+ variant = 'cell',
75
77
  }) => {
76
78
  const listStoreName = resolveStoreNameFromList(column?.list);
77
79
  const resolvedCustomParams = typeof column?.listRequestParams === 'function'
78
- ? column.listRequestParams({ currentCompanyId, requestParams })
80
+ ? column.listRequestParams({ currentCompanyId, requestParams, row, variant })
79
81
  : column?.listRequestParams;
80
82
  const customParams =
81
83
  resolvedCustomParams &&
@@ -92,31 +94,31 @@ const resolveColumnListLoadParams = ({
92
94
  : {}),
93
95
  };
94
96
  };
95
-
96
- const DefaultSelect = ({
97
- accentColor = '#2563EB',
98
- autoSave = true,
99
- column,
100
- columns = [],
101
- containerStyle = null,
102
- displayValue,
103
- editing = false,
104
- getOptionsForColumn = null,
105
- label = '',
106
- numberOfLines = 1,
107
- onBeforeOpen = null,
97
+
98
+ const DefaultSelect = ({
99
+ accentColor = '#2563EB',
100
+ autoSave = true,
101
+ column,
102
+ columns = [],
103
+ containerStyle = null,
104
+ displayValue,
105
+ editing = false,
106
+ getOptionsForColumn = null,
107
+ label = '',
108
+ numberOfLines = 1,
109
+ onBeforeOpen = null,
108
110
  onCancelEditing = null,
109
111
  onChangeValue = null,
110
112
  onSave = null,
111
113
  onSearchChange = null,
112
114
  onStartEditing = null,
113
- readTextStyle = null,
114
- row = {},
115
- saving = false,
116
- showLabel = false,
117
- storeName = '',
118
- value,
119
- variant = 'cell',
115
+ readTextStyle = null,
116
+ row = {},
117
+ saving = false,
118
+ showLabel = false,
119
+ storeName = '',
120
+ value,
121
+ variant = 'cell',
120
122
  }) => {
121
123
  const peopleStore = useStore('people');
122
124
  const ownerStore = useStore(storeName);
@@ -126,9 +128,9 @@ const DefaultSelect = ({
126
128
  const [searchText, setSearchText] = useState('');
127
129
  const currentCompanyId = peopleStore?.getters?.currentCompany?.id;
128
130
  const requestParams = ownerStore?.getters?.configs?.requestParams || {};
129
-
130
- const fieldName = getColumnKey(column);
131
- const selectedKey = normalizeOptionKey(value ?? resolveEditValue(column, row));
131
+
132
+ const fieldName = getColumnKey(column);
133
+ const selectedKey = normalizeOptionKey(value ?? resolveEditValue(column, row));
132
134
  const options = useMemo(() => {
133
135
  const localOptions = buildOptionsFromColumn(column, getOptionsForColumn, storeName);
134
136
  const remoteOptions = mapOptions(column, loadedItems, storeName);
@@ -143,7 +145,8 @@ const DefaultSelect = ({
143
145
  ...localOptions.filter(option => !seenKeys.has(option.key)),
144
146
  ];
145
147
  }, [column, getOptionsForColumn, loadedItems, storeName]);
146
- const selected = options.find(option => option.key === selectedKey);
148
+ const selected = options.find(option => option.key === selectedKey);
149
+ const resolvedFieldLabel = label || column?.formLabel || column?.label || fieldName;
147
150
  const resolvedLabel =
148
151
  displayValue ??
149
152
  selected?.label ??
@@ -175,9 +178,9 @@ const DefaultSelect = ({
175
178
  const query = normalizeText(searchText).toLowerCase();
176
179
  if (!query || onSearchChange) return options;
177
180
  return options.filter(option =>
178
- normalizeText(option.label).toLowerCase().includes(query) ||
179
- normalizeText(option.key).toLowerCase().includes(query),
180
- );
181
+ normalizeText(option.label).toLowerCase().includes(query) ||
182
+ normalizeText(option.key).toLowerCase().includes(query),
183
+ );
181
184
  }, [options, searchText]);
182
185
 
183
186
  const applyLoadedItems = useCallback(value => {
@@ -187,6 +190,12 @@ const DefaultSelect = ({
187
190
  }
188
191
  }, []);
189
192
 
193
+ useEffect(() => {
194
+ loadedListKeysRef.current.clear();
195
+ setLoadedItems([]);
196
+ setSearchText('');
197
+ }, [currentCompanyId, fieldName, row]);
198
+
190
199
  const loadRemoteOptions = useCallback((searchValue = '') => {
191
200
  const listStoreName = resolveStoreNameFromList(column?.list);
192
201
  const actionName = resolveListActionName(column?.list);
@@ -202,7 +211,9 @@ const DefaultSelect = ({
202
211
  column,
203
212
  currentCompanyId,
204
213
  requestParams,
214
+ row,
205
215
  searchValue,
216
+ variant,
206
217
  });
207
218
  const loadKey = `${getColumnKey(column)}:${listStoreName}:${actionName}:${stableSerialize(listLoadParams)}`;
208
219
 
@@ -235,7 +246,7 @@ const DefaultSelect = ({
235
246
  loadedListKeysRef.current.delete(loadKey);
236
247
  throw error;
237
248
  });
238
- }, [applyLoadedItems, column, currentCompanyId, loadedItems, requestParams]);
249
+ }, [applyLoadedItems, column, currentCompanyId, loadedItems, requestParams, row, variant]);
239
250
 
240
251
  const handleSearchChange = useCallback(value => {
241
252
  setSearchText(value);
@@ -247,43 +258,43 @@ const DefaultSelect = ({
247
258
 
248
259
  applyLoadedItems(maybeLoad);
249
260
  }, [applyLoadedItems, loadRemoteOptions, onSearchChange]);
250
-
251
- const close = () => {
252
- setIsOpen(false);
253
- setSearchText('');
254
- if (autoSave) onCancelEditing?.();
255
- };
256
-
257
- const selectOption = option => {
258
- setIsOpen(false);
259
- setSearchText('');
260
-
261
- if (autoSave) {
262
- onSave?.({
263
- value: option.key,
264
- label: option.label,
265
- object: option.raw,
266
- });
267
- return;
268
- }
269
-
270
- onChangeValue?.(option.key, {
271
- value: option.key,
272
- label: option.label,
273
- object: option.raw,
274
- });
275
- };
276
-
277
- const open = async () => {
278
- if (!canEdit && !isForm) return;
279
- try {
280
- if (!editing && !isForm) {
281
- const maybePromise = onStartEditing?.();
282
- if (maybePromise && typeof maybePromise.then === 'function') {
283
- await maybePromise;
284
- }
285
- }
286
-
261
+
262
+ const close = () => {
263
+ setIsOpen(false);
264
+ setSearchText('');
265
+ if (autoSave) onCancelEditing?.();
266
+ };
267
+
268
+ const selectOption = option => {
269
+ setIsOpen(false);
270
+ setSearchText('');
271
+
272
+ if (autoSave) {
273
+ onSave?.({
274
+ value: option.key,
275
+ label: option.label,
276
+ object: option.raw,
277
+ });
278
+ return;
279
+ }
280
+
281
+ onChangeValue?.(option.key, {
282
+ value: option.key,
283
+ label: option.label,
284
+ object: option.raw,
285
+ });
286
+ };
287
+
288
+ const open = async () => {
289
+ if (!canEdit && !isForm) return;
290
+ try {
291
+ if (!editing && !isForm) {
292
+ const maybePromise = onStartEditing?.();
293
+ if (maybePromise && typeof maybePromise.then === 'function') {
294
+ await maybePromise;
295
+ }
296
+ }
297
+
287
298
  const maybeLoad = onBeforeOpen?.();
288
299
  if (maybeLoad && typeof maybeLoad.then === 'function') {
289
300
  applyLoadedItems(await maybeLoad);
@@ -296,11 +307,11 @@ const DefaultSelect = ({
296
307
  setIsOpen(true);
297
308
  loadRemoteOptions().catch(() => {});
298
309
  };
299
-
300
- return (
301
- <View style={[inputStyles.wrap, containerStyle]}>
302
- {showLabel ? <Text style={inputStyles.fieldLabel}>{label || column?.label || fieldName}</Text> : null}
303
-
310
+
311
+ return (
312
+ <View style={[inputStyles.wrap, containerStyle]}>
313
+ {showLabel ? <Text style={inputStyles.fieldLabel}>{label || column?.label || fieldName}</Text> : null}
314
+
304
315
  <TouchableOpacity
305
316
  style={[inputStyles.readButton, isForm ? inputStyles.readButtonForm : null]}
306
317
  activeOpacity={canEdit || isForm ? 0.78 : 1}
@@ -359,33 +370,33 @@ const DefaultSelect = ({
359
370
  {saving ? (
360
371
  <Text style={[inputStyles.savingText, { color: accentColor }]}>Salvando</Text>
361
372
  ) : canEdit || isForm ? (
362
- <Icon style={inputStyles.editIcon} name="chevron-down" size={14} color="#64748B" />
363
- ) : null}
364
- </TouchableOpacity>
365
-
366
- <Modal visible={isOpen} transparent animationType="fade" onRequestClose={close}>
367
- <View style={styles.overlay}>
368
- <View style={styles.modalCard}>
369
- <View style={styles.modalHeader}>
370
- <Text style={styles.modalTitle} numberOfLines={1}>
371
- {label || column?.label || fieldName}
372
- </Text>
373
- <TouchableOpacity style={inputStyles.cancelButton} onPress={close}>
374
- <Icon name="x" size={16} color="#64748B" />
375
- </TouchableOpacity>
376
- </View>
377
-
378
- <View style={styles.searchBox}>
379
- <TextInput
373
+ <Icon style={inputStyles.editIcon} name="chevron-down" size={14} color="#64748B" />
374
+ ) : null}
375
+ </TouchableOpacity>
376
+
377
+ <Modal visible={isOpen} transparent animationType="fade" onRequestClose={close}>
378
+ <View style={styles.overlay}>
379
+ <View style={styles.modalCard}>
380
+ <View style={styles.modalHeader}>
381
+ <Text style={styles.modalTitle} numberOfLines={1}>
382
+ {resolvedFieldLabel}
383
+ </Text>
384
+ <TouchableOpacity style={inputStyles.cancelButton} onPress={close}>
385
+ <Icon name="x" size={16} color="#64748B" />
386
+ </TouchableOpacity>
387
+ </View>
388
+
389
+ <View style={styles.searchBox}>
390
+ <TextInput
380
391
  style={[inputStyles.input, inputStyles.formInput]}
381
392
  value={searchText}
382
- placeholder={global.t?.t(storeName, 'input', column?.searchParam || 'search')}
393
+ placeholder={resolvedFieldLabel || global.t?.t(storeName, 'input', column?.searchParam || 'search')}
383
394
  onChangeText={handleSearchChange}
384
395
  />
385
- </View>
386
-
387
- <ScrollView keyboardShouldPersistTaps="handled">
388
- {filteredOptions.length > 0 ? (
396
+ </View>
397
+
398
+ <ScrollView keyboardShouldPersistTaps="handled">
399
+ {filteredOptions.length > 0 ? (
389
400
  filteredOptions.map(option => {
390
401
  const isSelected = option.key === selectedKey;
391
402
  const optionColor = normalizeText(option.color);
@@ -401,10 +412,10 @@ const DefaultSelect = ({
401
412
  return (
402
413
  <TouchableOpacity
403
414
  key={`${option.key}_${option.label}`}
404
- style={styles.optionRow}
405
- activeOpacity={0.78}
406
- onPress={() => selectOption(option)}
407
- >
415
+ style={styles.optionRow}
416
+ activeOpacity={0.78}
417
+ onPress={() => selectOption(option)}
418
+ >
408
419
  {option.image ? (
409
420
  <Image
410
421
  source={option.image}
@@ -429,21 +440,21 @@ const DefaultSelect = ({
429
440
  {option.label}
430
441
  </Text>
431
442
  </TouchableOpacity>
432
- );
433
- })
434
- ) : (
435
- <View style={styles.emptyBox}>
436
- <Text style={styles.emptyText}>
437
- Nenhum resultado encontrado
438
- </Text>
439
- </View>
440
- )}
441
- </ScrollView>
442
- </View>
443
- </View>
444
- </Modal>
445
- </View>
446
- );
447
- };
448
-
449
- export default DefaultSelect;
443
+ );
444
+ })
445
+ ) : (
446
+ <View style={styles.emptyBox}>
447
+ <Text style={styles.emptyText}>
448
+ Nenhum resultado encontrado
449
+ </Text>
450
+ </View>
451
+ )}
452
+ </ScrollView>
453
+ </View>
454
+ </View>
455
+ </Modal>
456
+ </View>
457
+ );
458
+ };
459
+
460
+ export default DefaultSelect;
@@ -39,9 +39,40 @@ export const isDateRangeColumn = column =>
39
39
 
40
40
  export const isDateLikeColumn = column =>
41
41
  isDateColumn(column) || isDateRangeColumn(column);
42
-
43
- const normalizeColumnKey = value =>
44
- normalizeText(value)
42
+
43
+ export const isColorColumn = column => {
44
+ const fieldName = normalizeColumnKey(getColumnKey(column));
45
+ const inputType = normalizeColumnKey(column?.inputType || column?.type);
46
+
47
+ return (
48
+ inputType === 'color' ||
49
+ inputType === 'hexcolor' ||
50
+ fieldName === 'color' ||
51
+ fieldName === 'bgcolor' ||
52
+ fieldName === 'backgroundcolor' ||
53
+ fieldName === 'bordercolor'
54
+ );
55
+ };
56
+
57
+ export const isFileColumn = column => {
58
+ const inputType = normalizeColumnKey(column?.inputType || column?.type);
59
+
60
+ return [
61
+ 'document',
62
+ 'documents',
63
+ 'file',
64
+ 'files',
65
+ 'image',
66
+ 'images',
67
+ 'pdf',
68
+ ].includes(inputType);
69
+ };
70
+
71
+ export const isExtraDataColumn = column =>
72
+ normalizeColumnKey(column?.inputType || column?.type) === 'extradata';
73
+
74
+ const normalizeColumnKey = value =>
75
+ normalizeText(value)
45
76
  .replace(/[^a-zA-Z0-9]/g, '')
46
77
  .toLowerCase();
47
78
 
@@ -70,14 +101,14 @@ export const normalizeId = value => {
70
101
  return normalizeId(value?.id || value?.['@id'] || '');
71
102
  };
72
103
 
73
- export const isEditableColumn = column =>
74
- column?.editable !== false &&
75
- !column?.isIdentity &&
76
- column?.inputType !== 'increase' &&
77
- column?.inputType !== 'image' &&
78
- !String(getColumnKey(column)).includes('.');
104
+ export const isEditableColumn = column =>
105
+ column?.editable !== false &&
106
+ !column?.isIdentity &&
107
+ column?.inputType !== 'increase' &&
108
+ !isFileColumn(column) &&
109
+ !String(getColumnKey(column)).includes('.');
79
110
 
80
- export const normalizeOptionKey = option => {
111
+ export const normalizeOptionKey = option => {
81
112
  if (!option) return '';
82
113
  if (typeof option !== 'object') return normalizeId(option) || normalizeText(option);
83
114
  return normalizeText(
@@ -153,6 +184,29 @@ const filterItemsByListRequestParams = (items = [], column = null) => {
153
184
  );
154
185
  };
155
186
 
187
+ export const resolveColorToken = (value, themeColors = {}) => {
188
+ const normalized = normalizeText(value);
189
+ if (!normalized) return '';
190
+
191
+ if (normalized.startsWith('$')) {
192
+ return normalizeText(themeColors?.[normalized.slice(1)]);
193
+ }
194
+
195
+ const cssVarMatch = normalized.match(/^var\([^,]+,\s*(#[0-9a-fA-F]{3,8})\s*\)$/);
196
+ if (cssVarMatch?.[1]) return cssVarMatch[1];
197
+
198
+ return normalized;
199
+ };
200
+
201
+ export const resolveDefaultColorValue = (column, themeColors = {}, fallbackColor = '') =>
202
+ resolveColorToken(
203
+ column?.defaultColor ??
204
+ column?.defaultColour ??
205
+ column?.defaultValue ??
206
+ fallbackColor,
207
+ themeColors,
208
+ );
209
+
156
210
  const normalizeObject = value =>
157
211
  value && typeof value === 'object' && !Array.isArray(value) ? value : {};
158
212
 
@@ -174,18 +228,8 @@ export const mapOptions = (column, items = [], storeName = '') => {
174
228
  const formattedMeta = normalizeObject(formatted);
175
229
  const itemMeta = normalizeObject(item);
176
230
  const color = formattedMeta.color ?? itemMeta.color;
177
- const icon =
178
- formattedMeta.icon ??
179
- formattedMeta.iconName ??
180
- itemMeta.icon ??
181
- itemMeta.iconName;
182
- const image =
183
- formattedMeta.image ??
184
- formattedMeta.logo ??
185
- formattedMeta.source ??
186
- itemMeta.image ??
187
- itemMeta.logo ??
188
- itemMeta.source;
231
+ const icon = formattedMeta.icon ?? itemMeta.icon;
232
+ const image = formattedMeta.image ?? itemMeta.image;
189
233
 
190
234
  options.push({
191
235
  ...(color ? { color } : {}),
@@ -286,12 +330,8 @@ export const resolveCellPresentation = ({
286
330
  backgroundColor:
287
331
  formattedValueMeta.backgroundColor ??
288
332
  formattedMeta.backgroundColor ??
289
- formattedValueMeta.bgColor ??
290
- formattedMeta.bgColor ??
291
333
  rawMeta.backgroundColor ??
292
- rawMeta.bgColor ??
293
- styleMeta.backgroundColor ??
294
- styleMeta.bgColor,
334
+ styleMeta.backgroundColor,
295
335
  borderColor:
296
336
  formattedValueMeta.borderColor ??
297
337
  formattedMeta.borderColor ??
@@ -301,31 +341,17 @@ export const resolveCellPresentation = ({
301
341
  formattedValueMeta.color ??
302
342
  formattedMeta.color ??
303
343
  rawMeta.color ??
304
- rawMeta.statusColor ??
305
- rawMeta.categoryColor ??
306
344
  styleMeta.color,
307
345
  icon:
308
346
  formattedValueMeta.icon ??
309
- formattedValueMeta.iconName ??
310
347
  formattedMeta.icon ??
311
- formattedMeta.iconName ??
312
348
  rawMeta.icon ??
313
- rawMeta.iconName ??
314
- styleMeta.icon ??
315
- styleMeta.iconName,
349
+ styleMeta.icon,
316
350
  image:
317
351
  formattedValueMeta.image ??
318
- formattedValueMeta.logo ??
319
- formattedValueMeta.source ??
320
352
  formattedMeta.image ??
321
- formattedMeta.logo ??
322
- formattedMeta.source ??
323
353
  rawMeta.image ??
324
- rawMeta.logo ??
325
- rawMeta.source ??
326
- styleMeta.image ??
327
- styleMeta.logo ??
328
- styleMeta.source,
354
+ styleMeta.image,
329
355
  label,
330
356
  };
331
357
  };
@@ -38,8 +38,8 @@ export default StyleSheet.create({
38
38
  },
39
39
  popupLogoWrap: {
40
40
  width: 58,
41
- height: 58,
42
- borderRadius: 16,
41
+ height: 54,
42
+ borderRadius: 10,
43
43
  overflow: 'hidden',
44
44
  borderWidth: 1,
45
45
  alignItems: 'center',
@@ -52,6 +52,13 @@ export default StyleSheet.create({
52
52
  width: '100%',
53
53
  height: '100%',
54
54
  },
55
+ popupLogoFallback: {
56
+ fontSize: 24,
57
+ lineHeight: 26,
58
+ fontWeight: '900',
59
+ letterSpacing: 0.04,
60
+ textTransform: 'uppercase',
61
+ },
55
62
  popupTitleWrap: {
56
63
  flex: 1,
57
64
  minWidth: 0,
@@ -110,8 +110,20 @@ const extractCoordinates = address => {
110
110
  return {latitude, longitude};
111
111
  };
112
112
 
113
- const buildPopupTitle = item =>
114
- item?.unitAlias || item?.alias || item?.companyName || item?.title || 'Unidade';
113
+ const buildPopupTitle = item =>
114
+ item?.unitAlias || item?.alias || item?.companyName || item?.title || 'Unidade';
115
+
116
+ const buildLogoFallback = item => {
117
+ const title = buildPopupTitle(item);
118
+
119
+ return String(title || 'CO')
120
+ .trim()
121
+ .split(/\s+/)
122
+ .slice(0, 2)
123
+ .map(chunk => chunk[0] || '')
124
+ .join('')
125
+ .toUpperCase();
126
+ };
115
127
 
116
128
  const loadGoogleMapsApi = apiKey => {
117
129
  if (Platform.OS !== 'web' || typeof window === 'undefined') {
@@ -654,11 +666,12 @@ export default function DefaultGoogleMap({
654
666
  return null;
655
667
  }
656
668
 
657
- const selectedTitle = buildPopupTitle(selectedMarker);
658
- const selectedTravelSummary = buildPopupTravelSummaryText(
659
- selectedMarker,
660
- selectedRouteSummary,
661
- );
669
+ const selectedTitle = buildPopupTitle(selectedMarker);
670
+ const selectedLogoFallback = buildLogoFallback(selectedMarker);
671
+ const selectedTravelSummary = buildPopupTravelSummaryText(
672
+ selectedMarker,
673
+ selectedRouteSummary,
674
+ );
662
675
  const selectedNavigationPosition = selectedMarker
663
676
  ? {
664
677
  latitude: Number(selectedMarker.latitude),
@@ -688,19 +701,27 @@ export default function DefaultGoogleMap({
688
701
  },
689
702
  ]}>
690
703
  <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
705
+ style={[
706
+ styles.popupLogoWrap,
707
+ {borderColor: resolvedPopupTheme.dividerBorder},
708
+ ]}>
709
+ {selectedMarker.companyLogoUrl ? (
710
+ <Image
711
+ source={{uri: selectedMarker.companyLogoUrl}}
712
+ style={styles.popupLogoImage}
713
+ resizeMode="cover"
714
+ />
715
+ ) : (
716
+ <Text
717
+ style={[
718
+ styles.popupLogoFallback,
719
+ {color: resolvedPopupTheme.buttonBackground},
720
+ ]}>
721
+ {selectedLogoFallback}
722
+ </Text>
723
+ )}
724
+ </View>
704
725
  <View style={styles.popupTitleWrap}>
705
726
  <Text
706
727
  numberOfLines={1}
@@ -780,14 +801,14 @@ export default function DefaultGoogleMap({
780
801
  styles.popupActionButton,
781
802
  {backgroundColor: resolvedPopupTheme.buttonBackground},
782
803
  ]}>
783
- <Text
784
- style={[
785
- styles.popupActionText,
786
- {color: resolvedPopupTheme.buttonText},
787
- ]}>
788
- Abrir no Maps
789
- </Text>
790
- </TouchableOpacity>
804
+ <Text
805
+ style={[
806
+ styles.popupActionText,
807
+ {color: resolvedPopupTheme.buttonText},
808
+ ]}>
809
+ Abrir no Maps
810
+ </Text>
811
+ </TouchableOpacity>
791
812
  <TouchableOpacity
792
813
  activeOpacity={0.85}
793
814
  onPress={() => openExternalUrl(selectedWazeUrl)}