@controleonline/ui-default 1.0.270 → 1.0.273

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 (100) hide show
  1. package/.github/workflows/integration-source-gate.yml +68 -0
  2. package/.github/workflows/manager-worker.yml +19 -0
  3. package/.github/workflows/publish-npm.yml +91 -0
  4. package/AGENTS.md +46 -50
  5. package/docs/technical/Contrato-de-colunas.md +67 -0
  6. package/docs/technical/Default-Components.md +76 -0
  7. package/docs/technical/DefaultForm.md +80 -0
  8. package/docs/technical/DefaultInput-e-DefaultSelect.md +73 -0
  9. package/docs/technical/DefaultTable.md +272 -0
  10. package/docs/wiki.md +1 -0
  11. package/package.json +4 -3
  12. package/scripts/validate-wiki-pointer.cjs +13 -0
  13. package/src/react/components/address/DefaultAddress.js +446 -0
  14. package/src/react/components/address/DefaultAddress.styles.js +133 -0
  15. package/src/react/components/address/DefaultAddressCoordFields.js +59 -0
  16. package/src/react/components/address/DefaultAddressMapPane.js +55 -0
  17. package/src/react/components/address/LatLonReadonlyFields.js +51 -0
  18. package/src/react/components/address/defaultAddressHelpers.js +163 -0
  19. package/src/react/components/address/defaultAddressStyles.js +148 -0
  20. package/src/react/components/address/defaultAddressUtils.js +129 -0
  21. package/src/react/components/extra-data/DefaultExtraData.js +152 -0
  22. package/src/react/components/extra-data/DefaultExtraData.styles.js +61 -0
  23. package/src/react/components/files/DefaultFile.js +231 -46
  24. package/src/react/components/files/DefaultFileColumn.js +123 -0
  25. package/src/react/components/files/DefaultFileColumn.styles.js +52 -0
  26. package/src/react/components/files/DefaultFileColumn.utils.js +104 -0
  27. package/src/react/components/filters/CompactFilterSelector.js +51 -35
  28. package/src/react/components/filters/DefaultColumnFilter.js +60 -48
  29. package/src/react/components/filters/DefaultExternalFilters.js +324 -265
  30. package/src/react/components/filters/filterValue.js +65 -0
  31. package/src/react/components/form/DefaultForm.js +99 -79
  32. package/src/react/components/help/DefaultTooltip.js +48 -12
  33. package/src/react/components/help/DefaultTooltip.styles.js +20 -0
  34. package/src/react/components/inputs/DefaultColorInput.js +153 -0
  35. package/src/react/components/inputs/DefaultFeatherIconPicker.js +146 -0
  36. package/src/react/components/inputs/DefaultFeatherIconPicker.styles.js +113 -0
  37. package/src/react/components/inputs/DefaultInput.js +296 -141
  38. package/src/react/components/inputs/DefaultInput.styles.js +26 -0
  39. package/src/react/components/inputs/DefaultSelect.js +134 -123
  40. package/src/react/components/inputs/defaultInputUtils.js +262 -213
  41. package/src/react/components/map/DefaultMap.native.js +73 -73
  42. package/src/react/components/map/DefaultMap.shared.js +911 -762
  43. package/src/react/components/map/DefaultMap.web.js +124 -68
  44. package/src/react/components/table/DefaultColumnMenu.js +22 -3
  45. package/src/react/components/table/DefaultDebug.js +44 -37
  46. package/src/react/components/table/DefaultSearchModal.js +9 -1
  47. package/src/react/components/table/DefaultTable.grouping.js +98 -0
  48. package/src/react/components/table/DefaultTable.js +217 -111
  49. package/src/react/components/table/DefaultTable.styles.js +369 -310
  50. package/src/react/components/table/DefaultTable.utils.js +38 -4
  51. package/src/react/components/table/DefaultTableBody.js +13 -8
  52. package/src/react/components/table/DefaultTableCards.js +20 -1
  53. package/src/react/components/table/DefaultTableControls.js +180 -205
  54. package/src/react/components/table/DefaultTableImportModal.js +108 -0
  55. package/src/react/components/table/DefaultTableInput.js +1 -0
  56. package/src/react/components/table/DefaultTableRowActions.js +1 -1
  57. package/src/react/components/table/DefaultTableRows.js +357 -325
  58. package/src/react/components/table/DefaultTableSearch.js +28 -5
  59. package/src/react/components/table/DefaultTableToolbar.js +92 -3
  60. package/src/react/components/table/useDefaultTablePagination.js +56 -7
  61. package/src/react/components/table/useDefaultTableStoreSync.js +164 -0
  62. package/src/react/components/upload/DefaultUpload.js +406 -0
  63. package/src/react/components/upload/DefaultUpload.styles.js +153 -0
  64. package/src/react/components/upload/DefaultUploadAttachmentsList.js +146 -0
  65. package/src/react/components/upload/DefaultUploadManagerModal.js +247 -0
  66. package/src/react/components/upload/defaultUploadHelpers.js +108 -0
  67. package/src/react/components/upload/defaultUploadLibrary.js +248 -0
  68. package/src/react/components/upload/fileUpload.js +93 -0
  69. package/src/react/hooks/usePostalCodeLookup.js +159 -0
  70. package/src/react/index.js +6 -0
  71. package/src/react/services/addressFormUtils.js +262 -0
  72. package/src/react/services/addressGeo.js +230 -0
  73. package/src/react/utils/tableVisibleColumnsPreferences.js +113 -15
  74. package/src/tests/react/components/address/defaultAddressHelpers.test.js +104 -0
  75. package/src/tests/react/components/files/DefaultFile.test.js +22 -6
  76. package/src/tests/react/components/files/DefaultFileColumn.company.test.js +24 -0
  77. package/src/tests/react/components/form/DefaultForm.test.js +123 -0
  78. package/src/tests/react/components/help/DefaultTooltip.test.js +93 -0
  79. package/src/tests/react/components/inputs/DefaultSelect.test.js +108 -79
  80. package/src/tests/react/components/table/DefaultTable.grouping.test.js +49 -0
  81. package/src/tests/react/components/table/DefaultTable.test.js +53 -2
  82. package/src/tests/react/components/table/DefaultTable.utils.test.js +10 -0
  83. package/src/tests/react/components/table/DefaultTableControls.filters.test.mjs +21 -0
  84. package/src/tests/react/components/table/DefaultTableRowActions.test.js +23 -0
  85. package/src/tests/react/components/table/DefaultTableRows.rowActions.test.js +152 -0
  86. package/src/tests/react/components/table/resolveRowBackgroundColor.test.mjs +45 -0
  87. package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +161 -0
  88. package/src/tests/react/components/upload/DefaultUpload.test.js +353 -0
  89. package/src/tests/react/components/upload/defaultUploadLibrary.test.js +104 -0
  90. package/src/tests/react/components/upload/peopleMediaLibraryPreview.test.mjs +57 -0
  91. package/src/tests/react/filters/filterValue.test.js +46 -0
  92. package/src/tests/react/hooks/usePostalCodeLookup.test.js +107 -0
  93. package/src/tests/react/inputs/defaultInputUtils.test.js +9 -1
  94. package/src/tests/react/services/SMOKE-637-cep-hydrate.md +45 -0
  95. package/src/tests/react/services/addressFormUtils.test.js +179 -0
  96. package/src/tests/react/services/addressGeoLookup.async.test.js +104 -0
  97. package/src/tests/react/services/addressGeoLookup.test.js +105 -0
  98. package/src/tests/react/services/smoke-637/form-cep-12941040.png +0 -0
  99. package/src/tests/react/services/smoke-637/index.html +132 -0
  100. package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +96 -49
@@ -438,6 +438,42 @@ export const normalizeSortText = value =>
438
438
  .replace(/[\u0300-\u036f]/g, '')
439
439
  .toLowerCase();
440
440
 
441
+ export const parseSortNumber = value => {
442
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
443
+ if (value == null) return NaN;
444
+
445
+ const raw = String(value).trim();
446
+ if (!raw || !/[0-9]/.test(raw)) return NaN;
447
+
448
+ const compact = raw.replace(/[^0-9,.-]/g, '');
449
+ if (!compact) return NaN;
450
+
451
+ const hasComma = compact.includes(',');
452
+ const hasDot = compact.includes('.');
453
+ let normalized = compact;
454
+
455
+ if (hasComma && hasDot) {
456
+ normalized = compact.lastIndexOf(',') > compact.lastIndexOf('.')
457
+ ? compact.replace(/\./g, '').replace(',', '.')
458
+ : compact.replace(/,/g, '');
459
+ } else if (hasComma) {
460
+ const parts = compact.split(',');
461
+ normalized = parts.length === 2 && parts[1].length <= 2
462
+ ? `${parts[0].replace(/\./g, '')}.${parts[1]}`
463
+ : compact.replace(/,/g, '');
464
+ } else if (hasDot) {
465
+ const parts = compact.split('.');
466
+ if (parts.length > 2) {
467
+ normalized = compact.replace(/\./g, '');
468
+ } else if (parts.length === 2 && parts[1].length === 3 && parts[0].length <= 3) {
469
+ normalized = compact.replace(/\./g, '');
470
+ }
471
+ }
472
+
473
+ const parsed = Number(normalized);
474
+ return Number.isFinite(parsed) ? parsed : NaN;
475
+ };
476
+
441
477
  export const resolveSortComparable = ({ column, row, storeName, columns }) => {
442
478
  const fieldName = getColumnKey(column);
443
479
  const sortField = getSortField(column);
@@ -472,11 +508,9 @@ export const resolveSortComparable = ({ column, row, storeName, columns }) => {
472
508
  storeName,
473
509
  }));
474
510
 
475
- const normalizedNumber = Number(
476
- String(resolvedValue).replace(/[^0-9,.-]/g, '').replace(',', '.'),
477
- );
511
+ const normalizedNumber = parseSortNumber(resolvedValue);
478
512
 
479
- if (Number.isFinite(normalizedNumber) && String(resolvedValue).match(/[0-9]/)) {
513
+ if (Number.isFinite(normalizedNumber)) {
480
514
  return normalizedNumber;
481
515
  }
482
516
 
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
- import { useWindowDimensions } from 'react-native';
2
+ import { useWindowDimensions, View } from 'react-native';
3
3
  import { useStore } from '@store';
4
- import DefaultTableCards from './DefaultTableCards';
5
- import DefaultTableRows from './DefaultTableRows';
6
- import { DEFAULT_COMPACT_BREAKPOINT } from './DefaultTable.utils';
4
+ import DefaultTableCards from './DefaultTableCards';
5
+ import DefaultTableRows from './DefaultTableRows';
6
+ import { DEFAULT_COMPACT_BREAKPOINT } from './DefaultTable.utils';
7
+ import styles from './DefaultTable.styles';
7
8
 
8
9
  const DefaultTableBody = ({ storeName }) => {
9
10
  const store = useStore(storeName);
@@ -18,9 +19,13 @@ const DefaultTableBody = ({ storeName }) => {
18
19
  configs.initialViewMode ||
19
20
  'table';
20
21
 
21
- return effectiveViewMode === 'cards'
22
- ? <DefaultTableCards storeName={storeName} />
23
- : <DefaultTableRows storeName={storeName} />;
24
- };
22
+ return (
23
+ <View style={styles.body}>
24
+ {effectiveViewMode === 'cards'
25
+ ? <DefaultTableCards storeName={storeName} />
26
+ : <DefaultTableRows storeName={storeName} />}
27
+ </View>
28
+ );
29
+ };
25
30
 
26
31
  export default DefaultTableBody;
@@ -31,6 +31,16 @@ const DefaultTableCards = ({ storeName }) => {
31
31
  liveItems: store?.getters?.items,
32
32
  sortedData: configs.sortedData,
33
33
  });
34
+ const cardListProps =
35
+ configs.cardListProps && typeof configs.cardListProps === 'object'
36
+ ? configs.cardListProps
37
+ : {};
38
+ const {
39
+ contentContainerStyle,
40
+ key: cardListKey,
41
+ listKey,
42
+ ...flatListProps
43
+ } = cardListProps;
34
44
  const isLoading = Boolean(store?.getters?.isLoadingList || store?.getters?.isLoading);
35
45
  const emptyStateLabel = isLoading
36
46
  ? global.t?.t(storeName, 'label', 'loading')
@@ -39,6 +49,7 @@ const DefaultTableCards = ({ storeName }) => {
39
49
  const tableMutedColor = palette.textSecondary;
40
50
  const tableSurfaceColor = palette.background;
41
51
  const tableTextColor = palette.text;
52
+ const hasBottomAddButton = configs.addButtonPlacement === 'bottom';
42
53
  const handleListScroll = event => {
43
54
  if (shouldTriggerEndReachedFromScroll(event)) {
44
55
  configs.onEndReached?.();
@@ -159,11 +170,17 @@ const DefaultTableCards = ({ storeName }) => {
159
170
 
160
171
  return (
161
172
  <FlatList
173
+ key={listKey || cardListKey || `cards-${flatListProps.numColumns || 1}`}
174
+ {...flatListProps}
162
175
  data={sortedData}
163
176
  keyExtractor={getRowKey}
164
177
  renderItem={({ item, index }) => renderCardItem(item, index)}
165
178
  style={styles.cardsScroll}
166
- contentContainerStyle={styles.cardsGrid}
179
+ contentContainerStyle={[
180
+ styles.cardsGrid,
181
+ contentContainerStyle,
182
+ hasBottomAddButton ? styles.cardsGridWithBottomAdd : null,
183
+ ]}
167
184
  ListEmptyComponent={(
168
185
  <DefaultTableEmptyState
169
186
  emptyStateLabel={emptyStateLabel}
@@ -180,6 +197,8 @@ const DefaultTableCards = ({ storeName }) => {
180
197
  onScrollBeginDrag={configs.onScrollBeginDrag || undefined}
181
198
  onEndReached={configs.onEndReached}
182
199
  onEndReachedThreshold={END_REACHED_THRESHOLD}
200
+ onRefresh={configs.onRefresh}
201
+ refreshing={Boolean(configs.refreshing)}
183
202
  scrollEventThrottle={120}
184
203
  showsVerticalScrollIndicator={false}
185
204
  />
@@ -1,205 +1,180 @@
1
- import React from 'react';
2
- import { Text, TouchableOpacity } from 'react-native';
3
- import Icon from 'react-native-vector-icons/Feather';
4
- import { useStore } from '@store';
5
- import DefaultColumnMenu from './DefaultColumnMenu';
6
- import DefaultDebug from './DefaultDebug';
7
- import DefaultFiltersModal from './DefaultFiltersModal';
8
- import DefaultModalButton from './DefaultModalButton';
9
- import styles from './DefaultTable.styles';
10
- import { normalizeText } from '../inputs/defaultInputUtils';
11
- import {
12
- persistTableViewModePreference,
13
- resolveDefaultTablePreferenceScope,
14
- } from '../../utils/tableVisibleColumnsPreferences';
15
- import { shouldIncludeColumn } from './DefaultTable.utils';
16
- import useDefaultTableTheme from './useDefaultTableTheme';
17
-
18
- const DefaultTableControls = ({ storeName }) => {
19
- const store = useStore(storeName);
20
- const configs = store?.getters?.configs || {};
21
- const effectiveViewMode =
22
- configs.effectiveViewMode ||
23
- configs.viewMode ||
24
- configs.initialViewMode ||
25
- 'table';
26
- const tablePreferenceScope =
27
- configs.tablePreferenceScope ||
28
- resolveDefaultTablePreferenceScope({ storeName });
29
- const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
30
- const filters = store?.getters?.filters || {};
31
- const hasFilterableColumns = columns.some(
32
- column =>
33
- configs.showColumnFiltersButton !== false &&
34
- shouldIncludeColumn(column) &&
35
- column?.filter !== false &&
36
- column?.filters !== false,
37
- );
38
- const tableFiltersVisible = Boolean(configs.tableFiltersVisible);
39
- const activeFilterCount = Object.values(filters).filter(value => normalizeText(value) !== '').length;
40
- const addConfig = store?.getters?.add;
41
- const shouldRenderAddButton =
42
- (addConfig === true || configs.add === true) &&
43
- (typeof configs.onAdd === 'function' || typeof store?.actions?.save === 'function');
44
- const { tableButtonColors } = useDefaultTableTheme();
45
- const {
46
- backgroundColor,
47
- borderColor,
48
- iconColor,
49
- pressedBackgroundColor,
50
- pressedBorderColor,
51
- pressedIconColor,
52
- textColor,
53
- } = tableButtonColors;
54
-
55
- const buttonStyle = [
56
- styles.toolbarButton,
57
- { borderColor, backgroundColor },
58
- ];
59
- const pressedStyle = {
60
- backgroundColor: pressedBackgroundColor,
61
- borderColor: pressedBorderColor,
62
- };
63
- const updateConfigs = nextConfigs => {
64
- if (typeof store?.actions?.setConfigs === 'function') {
65
- store.actions.setConfigs(nextConfigs);
66
- return;
67
- }
68
-
69
- if (store?.getters) {
70
- store.getters.configs = nextConfigs;
71
- }
72
- };
73
- const nextViewMode = effectiveViewMode === 'table' ? 'cards' : 'table';
74
-
75
- return (
76
- <>
77
- <DefaultDebug storeName={storeName} />
78
- {hasFilterableColumns && effectiveViewMode === 'table' ? (
79
- <TouchableOpacity
80
- style={[
81
- ...buttonStyle,
82
- tableFiltersVisible ? pressedStyle : null,
83
- ]}
84
- activeOpacity={0.82}
85
- onPress={() =>
86
- updateConfigs({
87
- ...configs,
88
- tableFiltersVisible: !tableFiltersVisible,
89
- })
90
- }
91
- >
92
- <Icon
93
- name="filter"
94
- size={14}
95
- color={tableFiltersVisible ? pressedIconColor : textColor}
96
- />
97
- {activeFilterCount > 0 ? (
98
- <Text
99
- style={[
100
- styles.toolbarBadgeText,
101
- { color: tableFiltersVisible ? pressedIconColor : textColor },
102
- ]}
103
- >
104
- {activeFilterCount}
105
- </Text>
106
- ) : null}
107
- </TouchableOpacity>
108
- ) : null}
109
- {hasFilterableColumns && effectiveViewMode === 'cards' ? (
110
- <DefaultModalButton
111
- renderButton={({ isOpen, open }) => (
112
- <TouchableOpacity
113
- style={[
114
- ...buttonStyle,
115
- isOpen ? pressedStyle : null,
116
- ]}
117
- activeOpacity={0.82}
118
- onPress={open}
119
- >
120
- <Icon
121
- name="filter"
122
- size={14}
123
- color={isOpen ? pressedIconColor : textColor}
124
- />
125
- {activeFilterCount > 0 ? (
126
- <Text
127
- style={[
128
- styles.toolbarBadgeText,
129
- { color: isOpen ? pressedIconColor : textColor },
130
- ]}>
131
- {activeFilterCount}
132
- </Text>
133
- ) : null}
134
- </TouchableOpacity>
135
- )}
136
- >
137
- {({ close, isOpen }) => (
138
- <DefaultFiltersModal
139
- storeName={storeName}
140
- visible={isOpen}
141
- onClose={close}
142
- />
143
- )}
144
- </DefaultModalButton>
145
- ) : null}
146
- <TouchableOpacity
147
- style={buttonStyle}
148
- activeOpacity={0.82}
149
- onPress={() => {
150
- const nextConfigs = {
151
- ...configs,
152
- effectiveViewMode: nextViewMode,
153
- viewMode: nextViewMode,
154
- };
155
-
156
- persistTableViewModePreference(tablePreferenceScope, nextConfigs.viewMode);
157
- updateConfigs(nextConfigs);
158
- }}
159
- >
160
- <Icon
161
- name={nextViewMode === 'cards' ? 'grid' : 'list'}
162
- size={14}
163
- color={textColor}
164
- />
165
- </TouchableOpacity>
166
- <DefaultModalButton
167
- renderButton={({ isOpen, toggle }) => (
168
- <TouchableOpacity
169
- style={[
170
- ...buttonStyle,
171
- isOpen ? pressedStyle : null,
172
- ]}
173
- activeOpacity={0.82}
174
- onPress={toggle}
175
- >
176
- <Icon name="columns" size={14} color={isOpen ? pressedIconColor : textColor} />
177
- </TouchableOpacity>
178
- )}
179
- >
180
- {({ close, isOpen }) => (
181
- <DefaultColumnMenu
182
- storeName={storeName}
183
- visible={isOpen}
184
- onClose={close}
185
- />
186
- )}
187
- </DefaultModalButton>
188
- {shouldRenderAddButton ? (
189
- <TouchableOpacity
190
- style={[
191
- styles.toolbarButton,
192
- styles.toolbarAddButton,
193
- { backgroundColor: backgroundColor, borderColor: backgroundColor },
194
- ]}
195
- activeOpacity={0.85}
196
- onPress={() => configs.onAdd?.()}
197
- >
198
- <Icon name="plus" size={16} color={textColor} />
199
- </TouchableOpacity>
200
- ) : null}
201
- </>
202
- );
203
- };
204
-
205
- export default DefaultTableControls;
1
+ // fluxo: outros | etapa: order-history-page-filtro | https://github.com/ControleOnline/app-community/wiki/Smoke-Test-Flows
2
+ import React from 'react';
3
+ import { Text, TouchableOpacity } from 'react-native';
4
+ import Icon from 'react-native-vector-icons/Feather';
5
+ import { useStore } from '@store';
6
+ import DefaultColumnMenu from './DefaultColumnMenu';
7
+ import DefaultDebug from './DefaultDebug';
8
+ import DefaultFiltersModal from './DefaultFiltersModal';
9
+ import DefaultModalButton from './DefaultModalButton';
10
+ import styles from './DefaultTable.styles';
11
+ import { normalizeText } from '../inputs/defaultInputUtils';
12
+ import {
13
+ persistTableViewModePreference,
14
+ resolveDefaultTablePreferenceScope,
15
+ } from '../../utils/tableVisibleColumnsPreferences';
16
+ import { shouldIncludeColumn } from './DefaultTable.utils';
17
+ import useDefaultTableTheme from './useDefaultTableTheme';
18
+
19
+ const DefaultTableControls = ({ storeName }) => {
20
+ const store = useStore(storeName);
21
+ const configs = store?.getters?.configs || {};
22
+ const effectiveViewMode =
23
+ configs.effectiveViewMode ||
24
+ configs.viewMode ||
25
+ configs.initialViewMode ||
26
+ 'table';
27
+ const tablePreferenceScope =
28
+ configs.tablePreferenceScope ||
29
+ resolveDefaultTablePreferenceScope({ storeName });
30
+ const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
31
+ const filters = store?.getters?.filters || {};
32
+ const hasFilterableColumns = columns.some(
33
+ column =>
34
+ configs.showColumnFiltersButton !== false &&
35
+ shouldIncludeColumn(column) &&
36
+ column?.filter !== false &&
37
+ column?.filters !== false,
38
+ );
39
+ const activeFilterCount = Object.values(filters).filter(value => normalizeText(value) !== '').length;
40
+ const addConfig = store?.getters?.add;
41
+ const addButtonPlacement = normalizeText(configs.addButtonPlacement) || 'toolbar';
42
+ const shouldRenderAddButton =
43
+ addButtonPlacement === 'toolbar' &&
44
+ (addConfig === true || configs.add === true) &&
45
+ (typeof configs.onAdd === 'function' || typeof store?.actions?.save === 'function');
46
+ const { tableButtonColors } = useDefaultTableTheme();
47
+ const {
48
+ backgroundColor,
49
+ borderColor,
50
+ iconColor,
51
+ pressedBackgroundColor,
52
+ pressedBorderColor,
53
+ pressedIconColor,
54
+ textColor,
55
+ } = tableButtonColors;
56
+
57
+ const buttonStyle = [
58
+ styles.toolbarButton,
59
+ { borderColor, backgroundColor },
60
+ ];
61
+ const pressedStyle = {
62
+ backgroundColor: pressedBackgroundColor,
63
+ borderColor: pressedBorderColor,
64
+ };
65
+ const updateConfigs = nextConfigs => {
66
+ if (typeof store?.actions?.setConfigs === 'function') {
67
+ store.actions.setConfigs(nextConfigs);
68
+ return;
69
+ }
70
+
71
+ if (store?.getters) {
72
+ store.getters.configs = nextConfigs;
73
+ }
74
+ };
75
+ const nextViewMode = effectiveViewMode === 'table' ? 'cards' : 'table';
76
+
77
+ return (
78
+ <>
79
+ <DefaultDebug storeName={storeName} />
80
+ {/* app-community#811: LISTA (table) e CARD abrem o mesmo DefaultFiltersModal */}
81
+ {hasFilterableColumns ? (
82
+ <DefaultModalButton
83
+ renderButton={({ isOpen, open }) => (
84
+ <TouchableOpacity
85
+ accessibilityLabel="Filtros"
86
+ testID="default-table-filter-button"
87
+ style={[
88
+ ...buttonStyle,
89
+ isOpen ? pressedStyle : null,
90
+ ]}
91
+ activeOpacity={0.82}
92
+ onPress={open}
93
+ >
94
+ <Icon
95
+ name="filter"
96
+ size={14}
97
+ color={isOpen ? pressedIconColor : textColor}
98
+ />
99
+ {activeFilterCount > 0 ? (
100
+ <Text
101
+ style={[
102
+ styles.toolbarBadgeText,
103
+ { color: isOpen ? pressedIconColor : textColor },
104
+ ]}
105
+ >
106
+ {activeFilterCount}
107
+ </Text>
108
+ ) : null}
109
+ </TouchableOpacity>
110
+ )}
111
+ >
112
+ {({ close, isOpen }) => (
113
+ <DefaultFiltersModal
114
+ storeName={storeName}
115
+ visible={isOpen}
116
+ onClose={close}
117
+ />
118
+ )}
119
+ </DefaultModalButton>
120
+ ) : null}
121
+ <TouchableOpacity
122
+ style={buttonStyle}
123
+ activeOpacity={0.82}
124
+ onPress={() => {
125
+ const nextConfigs = {
126
+ ...configs,
127
+ effectiveViewMode: nextViewMode,
128
+ viewMode: nextViewMode,
129
+ };
130
+
131
+ persistTableViewModePreference(tablePreferenceScope, nextConfigs.viewMode);
132
+ updateConfigs(nextConfigs);
133
+ }}
134
+ >
135
+ <Icon
136
+ name={nextViewMode === 'cards' ? 'grid' : 'list'}
137
+ size={14}
138
+ color={textColor}
139
+ />
140
+ </TouchableOpacity>
141
+ <DefaultModalButton
142
+ renderButton={({ isOpen, toggle }) => (
143
+ <TouchableOpacity
144
+ style={[
145
+ ...buttonStyle,
146
+ isOpen ? pressedStyle : null,
147
+ ]}
148
+ activeOpacity={0.82}
149
+ onPress={toggle}
150
+ >
151
+ <Icon name="columns" size={14} color={isOpen ? pressedIconColor : textColor} />
152
+ </TouchableOpacity>
153
+ )}
154
+ >
155
+ {({ close, isOpen }) => (
156
+ <DefaultColumnMenu
157
+ storeName={storeName}
158
+ visible={isOpen}
159
+ onClose={close}
160
+ />
161
+ )}
162
+ </DefaultModalButton>
163
+ {shouldRenderAddButton ? (
164
+ <TouchableOpacity
165
+ style={[
166
+ styles.toolbarButton,
167
+ styles.toolbarAddButton,
168
+ { backgroundColor: backgroundColor, borderColor: backgroundColor },
169
+ ]}
170
+ activeOpacity={0.85}
171
+ onPress={() => configs.onAdd?.()}
172
+ >
173
+ <Icon name="plus" size={16} color={textColor} />
174
+ </TouchableOpacity>
175
+ ) : null}
176
+ </>
177
+ );
178
+ };
179
+
180
+ export default DefaultTableControls;
@@ -0,0 +1,108 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Modal } from 'react-native';
3
+ import ImportsPage from '@controleonline/ui-common/src/react/pages/Imports';
4
+ import { useStore } from '@store';
5
+
6
+ const normalizeText = value => String(value ?? '').trim();
7
+
8
+ const FORBIDDEN_EXTENSION_TOKENS = new Set(['*', '*.*', '.', '', '.*']);
9
+
10
+ export const sanitizeAllowedExtensions = (extensions, fallback = ['csv']) => {
11
+ const source = Array.isArray(extensions) ? extensions : [];
12
+ const cleaned = source
13
+ .map(item => normalizeText(item).replace(/^\./, '').toLowerCase())
14
+ .filter(item => item && !FORBIDDEN_EXTENSION_TOKENS.has(item) && !item.includes('*'));
15
+
16
+ if (cleaned.length === 0) {
17
+ return (fallback || ['csv'])
18
+ .map(item => normalizeText(item).replace(/^\./, '').toLowerCase())
19
+ .filter(item => item && !FORBIDDEN_EXTENSION_TOKENS.has(item) && !item.includes('*'));
20
+ }
21
+
22
+ return [...new Set(cleaned)];
23
+ };
24
+
25
+ const translateConfigKey = key => {
26
+ if (!Array.isArray(key) || key.length < 3) {
27
+ return '';
28
+ }
29
+
30
+ return normalizeText(global.t?.t?.(key[0], key[1], key[2]));
31
+ };
32
+
33
+ const resolveConfiguredText = ({ config, fallbackKey, literalKey, translationKey }) => {
34
+ const literalValue = normalizeText(config?.[literalKey]);
35
+ if (literalValue) {
36
+ return literalValue;
37
+ }
38
+
39
+ const configuredTranslation = translateConfigKey(config?.[translationKey]);
40
+ if (configuredTranslation) {
41
+ return configuredTranslation;
42
+ }
43
+
44
+ return normalizeText(global.t?.t?.('defaultTable', 'label', fallbackKey));
45
+ };
46
+
47
+ const DefaultTableImportModal = ({
48
+ onClose,
49
+ storeName,
50
+ visible,
51
+ importType: importTypeProp,
52
+ allowedExtensions: allowedExtensionsProp,
53
+ title: titleProp,
54
+ }) => {
55
+ const store = useStore(storeName);
56
+ const configs = store?.getters?.configs || {};
57
+ const resolvedConfig = configs.import || {};
58
+ const importType =
59
+ normalizeText(importTypeProp) || normalizeText(resolvedConfig.importType) || 'csv';
60
+ const allowedExtensions = sanitizeAllowedExtensions(
61
+ allowedExtensionsProp || resolvedConfig.allowedExtensions,
62
+ importType === 'invoice_tax' || importType === 'xml' ? ['xml', 'zip'] : ['csv'],
63
+ );
64
+
65
+ const context = useMemo(
66
+ () => ({
67
+ context: importType,
68
+ storeName,
69
+ allowedExtensions,
70
+ title:
71
+ normalizeText(titleProp) ||
72
+ resolveConfiguredText({
73
+ config: resolvedConfig,
74
+ fallbackKey: 'import',
75
+ literalKey: 'title',
76
+ translationKey: 'titleKey',
77
+ }),
78
+ searchPlaceholder: resolveConfiguredText({
79
+ config: resolvedConfig,
80
+ fallbackKey: 'importSearch',
81
+ literalKey: 'searchPlaceholder',
82
+ translationKey: 'searchPlaceholderKey',
83
+ }),
84
+ }),
85
+ [
86
+ allowedExtensions,
87
+ importType,
88
+ resolvedConfig.searchPlaceholder,
89
+ resolvedConfig.searchPlaceholderKey,
90
+ resolvedConfig.title,
91
+ resolvedConfig.titleKey,
92
+ storeName,
93
+ titleProp,
94
+ ],
95
+ );
96
+
97
+ if (!importType) {
98
+ return null;
99
+ }
100
+
101
+ return (
102
+ <Modal visible={visible} animationType="slide" transparent={false}>
103
+ <ImportsPage context={context} onClose={onClose} />
104
+ </Modal>
105
+ );
106
+ };
107
+
108
+ export default DefaultTableImportModal;
@@ -72,6 +72,7 @@ const DefaultTableInput = ({
72
72
  column={column}
73
73
  columns={columns}
74
74
  containerStyle={options.containerStyle}
75
+ defaultColor={options.defaultColor || configs.defaultColor}
75
76
  displayValue={options.displayValue}
76
77
  editing={isEditing}
77
78
  getOptionsForColumn={configs.getOptionsForColumn}
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
 
3
- const DEFAULT_ROW_ACTIONS_WIDTH = 96;
3
+ const DEFAULT_ROW_ACTIONS_WIDTH = 140;
4
4
 
5
5
  const isReactComponentType = value =>
6
6
  value &&