@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,377 +1,238 @@
1
- import React, { useMemo, useState } from 'react';
2
- import { FlatList, ScrollView, Text, TouchableOpacity, View } from 'react-native';
3
- import Icon from 'react-native-vector-icons/Feather';
4
- import { useStore } from '@store';
5
- import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
6
- import { getColumnKey } from '../inputs/defaultInputUtils';
7
- import DefaultColumnFilter from '../filters/DefaultColumnFilter';
1
+ import React, { useMemo, useState } from 'react';
2
+ import { FlatList, ScrollView, Text, TouchableOpacity, View } from 'react-native';
3
+ import Icon from 'react-native-vector-icons/Feather';
4
+ import { useStore } from '@store';
5
+ import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
6
+ import { getColumnKey } from '../inputs/defaultInputUtils';
8
7
  import DefaultTableEmptyState from './DefaultTableEmptyState';
9
8
  import DefaultTableInput from './DefaultTableInput';
10
9
  import DefaultTableRowActions, {
11
10
  hasDefaultTableRowActionsComponent,
12
11
  resolveDefaultTableRowActionsWidth,
13
12
  } from './DefaultTableRowActions';
14
- import {
15
- END_REACHED_THRESHOLD,
16
- getColumnStyle,
17
- getRowKey,
18
- getSortField,
19
- isSortableColumn,
20
- mergeSortedDataWithLiveItems,
21
- shouldTriggerEndReachedFromScroll,
22
- shouldIncludeColumn,
23
- } from './DefaultTable.utils';
24
- import styles from './DefaultTable.styles';
25
- import useDefaultTableTheme from './useDefaultTableTheme';
26
-
27
- const DefaultTableRows = ({ storeName }) => {
28
- const store = useStore(storeName);
29
- const configs = store?.getters?.configs || {};
13
+ import {
14
+ persistStoreSelection,
15
+ flattenGroupedTableItems,
16
+ isRowSelected,
17
+ resolveStoreSelectedIds,
18
+ toggleGroupSelection,
19
+ toggleSelectedId,
20
+ } from './DefaultTable.grouping';
21
+ import {
22
+ END_REACHED_THRESHOLD,
23
+ getColumnStyle,
24
+ getColumnMinWidth,
25
+ getRowKey,
26
+ getSortField,
27
+ isSortableColumn,
28
+ mergeSortedDataWithLiveItems,
29
+ shouldTriggerEndReachedFromScroll,
30
+ shouldIncludeColumn,
31
+ } from './DefaultTable.utils';
32
+ import styles from './DefaultTable.styles';
33
+ import useDefaultTableTheme from './useDefaultTableTheme';
34
+
35
+ const SELECTION_COLUMN_WIDTH = 44;
36
+
37
+ /** Row stripe colors from themes-map listItemEvenRow / listItemOddRow — never status.color. */
38
+ export const resolveRowBackgroundColor = ({
39
+ selected,
40
+ index,
41
+ tableOddColor,
42
+ tableEvenColor,
43
+ selectedBackground = '#ECFEFF',
44
+ }) => {
45
+ if (selected) return selectedBackground;
46
+ return index % 2 === 0 ? tableOddColor : tableEvenColor;
47
+ };
48
+
49
+ const DefaultTableRows = ({ storeName }) => {
50
+ const store = useStore(storeName);
51
+ const configs = store?.getters?.configs || {};
30
52
  const { palette, resolvedAccentColor, tableBorderColors, themeTokens } = useDefaultTableTheme();
31
- const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
32
- const visibleColumns = store?.getters?.visibleColumns || {};
33
- const tableColumns = columns.filter(
34
- column => shouldIncludeColumn(column) && visibleColumns[getColumnKey(column)] !== false,
35
- );
36
- const sortedData = mergeSortedDataWithLiveItems({
37
- liveItems: store?.getters?.items,
38
- sortedData: configs.sortedData,
39
- });
40
- const resolvedFilters = store?.getters?.filters || {};
41
- const isLoading = Boolean(store?.getters?.isLoadingList || store?.getters?.isLoading);
42
- const emptyStateLabel = isLoading
43
- ? global.t?.t(storeName, 'label', 'loading')
44
- : global.t?.t(storeName, 'label', 'empty');
53
+ const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
54
+ const visibleColumns = store?.getters?.visibleColumns || {};
55
+ const tableColumns = columns.filter(
56
+ column => shouldIncludeColumn(column) && visibleColumns[getColumnKey(column)] !== false,
57
+ );
58
+ const sortedData = mergeSortedDataWithLiveItems({
59
+ liveItems: store?.getters?.items,
60
+ sortedData: configs.sortedData,
61
+ });
62
+ const listData = useMemo(() => flattenGroupedTableItems(sortedData, columns), [columns, sortedData]);
63
+ const selectable = configs.selectable === true;
64
+ const selectedIds = resolveStoreSelectedIds(store);
65
+ const isLoading = Boolean(store?.getters?.isLoadingList || store?.getters?.isLoading);
66
+ const emptyStateLabel = isLoading ? global.t?.t(storeName, 'label', 'loading') : global.t?.t(storeName, 'label', 'empty');
45
67
  const tableBorderColor = tableBorderColors.rowBorderColor;
46
68
  const tableHeaderBorderColor = tableBorderColors.headerBorderColor || tableBorderColor;
47
- const tableEvenColor = themeTokens.listItemEvenRow || themeTokens['bg-even-light'] || palette.background;
48
- const tableHeaderColor = themeTokens['bg-headers-light'] || resolvedAccentColor;
49
- const tableMutedColor = palette.textSecondary;
50
- const tableOddColor = themeTokens.listItemOddRow || themeTokens['bg-odd-light'] || palette.background;
51
- const tableSurfaceColor = palette.background;
52
- const tableTextColor = palette.text;
53
- const rowStyle = configs.rowStyle;
54
- const hasRowPress = typeof configs.onRowPress === 'function';
55
- const hasCustomRowActions = hasDefaultTableRowActionsComponent(configs.rowActionsComponent);
69
+ const tableEvenColor = themeTokens.listItemEvenRow || themeTokens['bg-even-light'] || palette.background;
70
+ const tableHeaderColor = themeTokens['bg-headers-light'] || resolvedAccentColor;
71
+ const tableMutedColor = palette.textSecondary;
72
+ const tableOddColor = themeTokens.listItemOddRow || themeTokens['bg-odd-light'] || palette.background;
73
+ const tableTextColor = palette.text;
74
+ const rowStyle = configs.rowStyle;
75
+ const hasRowPress = typeof configs.onRowPress === 'function';
76
+ const RowActionsComponent = configs.rowActionsComponent;
77
+ const hasCustomRowActions = hasDefaultTableRowActionsComponent(RowActionsComponent);
56
78
  const hasEditAction = typeof configs.onEditRow === 'function';
57
- const hasRowActions = configs.showRowActions !== false && (hasCustomRowActions || hasEditAction);
58
- const shouldPinRowActions = configs.pinRowActions !== false;
59
- const hasColumnFilters =
60
- configs.showColumnFiltersButton !== false &&
61
- configs.tableFiltersVisible === true;
79
+ const hasRowActions =
80
+ configs.showRowActions !== false && (hasCustomRowActions || hasEditAction);
81
+ const pinRowActions = configs.pinRowActions !== false;
62
82
  const actionsCellWidth = hasRowActions ? resolveDefaultTableRowActionsWidth(configs) : 0;
63
- const [horizontalMetrics, setHorizontalMetrics] = useState({
64
- contentWidth: 0,
65
- viewportWidth: 0,
66
- x: 0,
67
- });
68
- const updateHorizontalMetrics = patch => {
69
- setHorizontalMetrics(current => {
70
- const next = {
71
- ...current,
72
- ...patch,
73
- };
74
-
75
- return next.contentWidth === current.contentWidth &&
76
- next.viewportWidth === current.viewportWidth &&
77
- next.x === current.x
78
- ? current
79
- : next;
80
- });
81
- };
82
- const stickyBodyTransforms = useMemo(() => {
83
- const viewportWidth = Number(horizontalMetrics.viewportWidth || 0);
84
- const contentWidth = Number(horizontalMetrics.contentWidth || 0);
85
- const x = Number(horizontalMetrics.x || 0);
86
- const canScrollHorizontally = viewportWidth > 0 && contentWidth > viewportWidth;
87
- const actionsTranslateX = canScrollHorizontally
88
- ? x + viewportWidth - contentWidth
89
- : 0;
90
-
91
- return {
92
- actions:
93
- shouldPinRowActions && actionsTranslateX !== 0
94
- ? [{ translateX: actionsTranslateX }]
95
- : null,
96
- identity: x !== 0 ? [{ translateX: x }] : null,
97
- };
98
- }, [horizontalMetrics, shouldPinRowActions]);
99
- const handleHorizontalScroll = event => {
100
- updateHorizontalMetrics({
101
- x: Number(event?.nativeEvent?.contentOffset?.x || 0),
102
- });
103
- };
104
- const handleHorizontalLayout = event => {
105
- updateHorizontalMetrics({
106
- viewportWidth: Number(event?.nativeEvent?.layout?.width || 0),
107
- });
108
- };
109
- const handleHorizontalContentSizeChange = width => {
110
- updateHorizontalMetrics({
111
- contentWidth: Number(width || 0),
112
- });
113
- };
114
- const handleListScroll = event => {
115
- if (shouldTriggerEndReachedFromScroll(event)) {
116
- configs.onEndReached?.();
117
- }
118
- };
119
-
120
- const renderTableItem = ({ item: row, index }) => {
121
- const RowComponent = hasRowPress ? TouchableOpacity : View;
122
- const rowStyleValue = typeof rowStyle === 'function' ? rowStyle(row, index) : rowStyle;
123
- const rowPressProps = hasRowPress
124
- ? {
125
- activeOpacity: 0.84,
126
- onPress: () => configs.onRowPress(row),
127
- }
128
- : {};
129
- const rowBackgroundColor = index % 2 === 0 ? tableOddColor : tableEvenColor;
130
- const RowActionsComponent = configs.rowActionsComponent;
131
-
132
- return (
133
- <RowComponent
134
- key={getRowKey(row)}
135
- style={[
136
- styles.row,
83
+ const selectionWidth = selectable ? SELECTION_COLUMN_WIDTH : 0;
84
+ const tableMinWidth = useMemo(
85
+ () => tableColumns.reduce((totalWidth, column) => totalWidth + getColumnMinWidth(column), actionsCellWidth + selectionWidth),
86
+ [actionsCellWidth, selectionWidth, tableColumns],
87
+ );
88
+ const [horizontalMetrics, setHorizontalMetrics] = useState({ contentWidth: 0, viewportWidth: 0, x: 0 });
89
+ const updateHorizontalMetrics = patch => {
90
+ setHorizontalMetrics(current => {
91
+ const next = { ...current, ...patch };
92
+ return next.contentWidth === current.contentWidth && next.viewportWidth === current.viewportWidth && next.x === current.x ? current : next;
93
+ });
94
+ };
95
+ const tableWidth = Math.max(tableMinWidth, Number(horizontalMetrics.viewportWidth || 0));
96
+ const emitSelection = nextIds => persistStoreSelection(store, nextIds, sortedData, columns);
97
+
98
+ const renderSelectionCell = (selected, onPress, accessibilityLabel, backgroundColor) => (
99
+ <TouchableOpacity accessibilityRole="checkbox" accessibilityState={{ checked: selected }} accessibilityLabel={accessibilityLabel} onPress={onPress} style={[styles.cell, { minWidth: SELECTION_COLUMN_WIDTH, width: SELECTION_COLUMN_WIDTH, alignItems: 'center', justifyContent: 'center', backgroundColor }]}>
100
+ <Icon name={selected ? 'check-square' : 'square'} size={16} color={selected ? '#0284C7' : tableMutedColor} />
101
+ </TouchableOpacity>
102
+ );
103
+
104
+ const renderActionsCell = (row, rowBackgroundColor) => {
105
+ if (!hasRowActions) return null;
106
+
107
+ const customRowActions = hasCustomRowActions ? (
108
+ <DefaultTableRowActions
109
+ component={RowActionsComponent}
110
+ helpers={{
111
+ openEdit: () => configs.onEditRow?.(row),
112
+ openRow: hasRowPress ? () => configs.onRowPress(row) : null,
113
+ }}
114
+ openEdit={() => configs.onEditRow?.(row)}
115
+ openRow={hasRowPress ? () => configs.onRowPress(row) : null}
116
+ row={row}
117
+ storeName={storeName}
118
+ />
119
+ ) : null;
120
+
121
+ const editButton = hasEditAction ? (
122
+ <Text onPress={() => configs.onEditRow?.(row)} style={{ color: '#0284C7', fontWeight: '800', fontSize: 12 }}>
123
+ {global.t?.t(storeName, 'button', 'edit') || 'Editar'}
124
+ </Text>
125
+ ) : null;
126
+
127
+ return (
128
+ <View
129
+ style={[
130
+ styles.cell,
131
+ styles.actionsCell,
132
+ pinRowActions ? styles.stickyActionsCell : null,
133
+ pinRowActions ? styles.pinnedActionsCell : null,
137
134
  {
135
+ minWidth: actionsCellWidth,
136
+ width: actionsCellWidth,
137
+ maxWidth: actionsCellWidth,
138
+ flexBasis: actionsCellWidth,
138
139
  backgroundColor: rowBackgroundColor,
139
- borderBottomColor: tableBorderColor,
140
- borderBottomWidth: tableBorderColor ? 1 : 0,
141
140
  },
142
- rowStyleValue,
143
- ]}
144
- {...rowPressProps}
145
- >
146
- {tableColumns.map(column => (
147
- <React.Fragment key={getColumnKey(column)}>
148
- <DefaultTableInput
149
- column={column}
150
- options={column?.isIdentity ? {
151
- cellStyle: [
152
- styles.pinnedIdentityCell,
153
- {
154
- backgroundColor: rowBackgroundColor,
155
- ...(stickyBodyTransforms.identity
156
- ? { transform: stickyBodyTransforms.identity }
157
- : {}),
158
- },
159
- ],
160
- } : {}}
161
- row={row}
162
- storeName={storeName}
163
- variant="cell"
164
- />
165
- </React.Fragment>
166
- ))}
167
- {hasRowActions ? (
168
- <View
169
- style={[
170
- styles.cell,
171
- styles.actionsCell,
172
- shouldPinRowActions ? styles.pinnedActionsCell : null,
173
- {
174
- minWidth: actionsCellWidth,
175
- width: actionsCellWidth,
176
- flexBasis: actionsCellWidth,
177
- maxWidth: actionsCellWidth,
178
- backgroundColor: rowBackgroundColor,
179
- ...(stickyBodyTransforms.actions
180
- ? { transform: stickyBodyTransforms.actions }
181
- : {}),
182
- },
183
- ]}
184
- >
185
- <View style={styles.rowActionsGroup}>
186
- {hasCustomRowActions ? (
187
- <DefaultTableRowActions
188
- component={RowActionsComponent}
189
- helpers={{
190
- openEdit: () => configs.onEditRow?.(row),
191
- openRow: hasRowPress ? () => configs.onRowPress(row) : null,
192
- }}
193
- openEdit={() => configs.onEditRow?.(row)}
194
- openRow={hasRowPress ? () => configs.onRowPress(row) : null}
195
- row={row}
196
- storeName={storeName}
197
- />
198
- ) : null}
199
- {hasEditAction ? (
200
- <TouchableOpacity
201
- style={[styles.iconButton, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}
202
- activeOpacity={0.82}
203
- onPress={() => configs.onEditRow?.(row)}
204
- >
205
- <Icon name="edit-2" size={14} color={tableMutedColor} />
206
- </TouchableOpacity>
207
- ) : null}
208
- </View>
209
- </View>
210
- ) : null}
211
- </RowComponent>
212
- );
213
- };
214
-
215
- return (
216
- <>
217
- <ScrollView
218
- horizontal
219
- onContentSizeChange={handleHorizontalContentSizeChange}
220
- onLayout={handleHorizontalLayout}
221
- onScroll={handleHorizontalScroll}
222
- scrollEventThrottle={16}
223
- style={styles.scroll}
224
- contentContainerStyle={styles.horizontalScrollContent}
225
- >
226
- <View style={styles.content}>
227
- <View
228
- style={[
229
- styles.headerRow,
230
- {
231
- backgroundColor: tableHeaderColor,
232
- borderBottomColor: tableHeaderBorderColor,
233
- borderBottomWidth: tableHeaderBorderColor ? 1 : 0,
234
- },
235
- ]}
236
- >
237
- {tableColumns.map(column => {
238
- const fieldName = getColumnKey(column);
239
- const label = formatStoreColumnLabel({
240
- columns,
241
- fieldName,
242
- fallbackLabel: column?.label || fieldName,
243
- storeName,
244
- });
245
- const sortFieldName = getSortField(column);
246
-
247
- return (
248
- <TouchableOpacity
249
- key={fieldName}
250
- style={[
251
- getColumnStyle(column),
252
- column?.isIdentity
253
- ? [styles.stickyIdentityCell, styles.stickyHeaderCell, { backgroundColor: tableHeaderColor }]
254
- : null,
255
- ]}
256
- activeOpacity={isSortableColumn(column) ? 0.8 : 1}
257
- onPress={() => configs.requestSort?.(column)}
258
- >
259
- <View style={styles.sortableHeader}>
260
- <Text style={[styles.headerText, { color: tableTextColor }]} numberOfLines={1}>{label}</Text>
261
- {isSortableColumn(column) && configs.resolvedSort?.field === sortFieldName ? (
262
- <Icon name={configs.resolvedSort?.direction === 'desc' ? 'chevron-down' : 'chevron-up'} size={12} color={tableTextColor} />
263
- ) : isSortableColumn(column) ? (
264
- <Icon name="chevrons-up" size={12} color={tableBorderColor} />
265
- ) : null}
266
- {resolvedFilters?.[fieldName] ? <Icon name="filter" size={11} color={tableTextColor} /> : null}
267
- </View>
268
- </TouchableOpacity>
269
- );
270
- })}
271
- {hasRowActions ? (
272
- <View
273
- style={[
274
- styles.cell,
275
- styles.actionsCell,
276
- shouldPinRowActions ? styles.stickyActionsCell : null,
277
- shouldPinRowActions ? styles.stickyHeaderCell : null,
278
- {
279
- minWidth: actionsCellWidth,
280
- width: actionsCellWidth,
281
- flexBasis: actionsCellWidth,
282
- maxWidth: actionsCellWidth,
283
- backgroundColor: tableHeaderColor,
284
- },
285
- ]}
286
- >
287
- <Text style={[styles.headerText, { color: tableTextColor }]}>
288
- {global.t?.t(storeName, 'label', 'actions')}
289
- </Text>
290
- </View>
291
- ) : null}
292
- </View>
293
- {hasColumnFilters ? (
294
- <View
295
- style={[
296
- styles.filterRow,
297
- {
298
- backgroundColor: tableHeaderColor,
299
- borderBottomColor: tableHeaderBorderColor,
300
- borderBottomWidth: tableHeaderBorderColor ? 1 : 0,
301
- },
302
- ]}
303
- >
304
- {tableColumns.map(column => {
305
- const fieldName = getColumnKey(column);
306
-
307
- return (
308
- <View
309
- key={`${fieldName}-filter`}
310
- style={[
311
- getColumnStyle(column),
312
- column?.isIdentity
313
- ? [styles.stickyIdentityCell, { backgroundColor: tableHeaderColor }]
314
- : null,
315
- ]}
316
- >
317
- {column?.filter !== false && column?.filters !== false ? (
318
- <DefaultColumnFilter
319
- column={column}
320
- filters={resolvedFilters}
321
- storeName={storeName}
322
- style={styles.filterCell}
323
- />
324
- ) : null}
325
- </View>
326
- );
327
- })}
328
- {hasRowActions ? (
329
- <View
330
- style={[
331
- styles.cell,
332
- styles.actionsCell,
333
- shouldPinRowActions ? styles.stickyActionsCell : null,
334
- {
335
- minWidth: actionsCellWidth,
336
- width: actionsCellWidth,
337
- flexBasis: actionsCellWidth,
338
- maxWidth: actionsCellWidth,
339
- backgroundColor: tableHeaderColor,
340
- },
341
- ]}
342
- />
343
- ) : null}
344
- </View>
345
- ) : null}
346
-
347
- <FlatList
348
- data={sortedData}
349
- keyExtractor={getRowKey}
350
- renderItem={renderTableItem}
351
- style={styles.tableList}
352
- contentContainerStyle={styles.tableListContent}
353
- ListEmptyComponent={(
354
- <DefaultTableEmptyState
355
- emptyStateLabel={emptyStateLabel}
356
- isLoading={isLoading}
357
- isTable
358
- tableMutedColor={tableMutedColor}
359
- />
360
- )}
361
- ListFooterComponent={null}
362
- nestedScrollEnabled
363
- onMomentumScrollBegin={configs.onMomentumScrollBegin || undefined}
364
- onScroll={handleListScroll}
365
- onScrollBeginDrag={configs.onScrollBeginDrag || undefined}
366
- onEndReached={configs.onEndReached}
367
- onEndReachedThreshold={END_REACHED_THRESHOLD}
368
- scrollEventThrottle={120}
369
- showsVerticalScrollIndicator={false}
370
- />
371
- </View>
372
- </ScrollView>
373
- </>
374
- );
375
- };
376
-
377
- export default DefaultTableRows;
141
+ ]}
142
+ >
143
+ <View style={styles.rowActionsGroup}>
144
+ {customRowActions}
145
+ {editButton}
146
+ </View>
147
+ </View>
148
+ );
149
+ };
150
+
151
+ const renderActionsHeader = () => {
152
+ if (!hasRowActions) return null;
153
+ return (
154
+ <View
155
+ style={[
156
+ styles.cell,
157
+ styles.actionsCell,
158
+ pinRowActions ? styles.stickyActionsCell : null,
159
+ pinRowActions ? styles.stickyHeaderCell : null,
160
+ {
161
+ minWidth: actionsCellWidth,
162
+ width: actionsCellWidth,
163
+ maxWidth: actionsCellWidth,
164
+ flexBasis: actionsCellWidth,
165
+ backgroundColor: tableHeaderColor,
166
+ },
167
+ ]}
168
+ >
169
+ <Text style={[styles.headerText, { color: tableTextColor }]} numberOfLines={1}>
170
+ {global.t?.t(storeName, 'label', 'actions') || 'Ações'}
171
+ </Text>
172
+ </View>
173
+ );
174
+ };
175
+
176
+ const renderTableItem = ({ item, index }) => {
177
+ if (item?.type === 'group') {
178
+ const groupRows = item.group?.rows || [];
179
+ const groupSelected = selectable && groupRows.length > 0 && groupRows.every(row => isRowSelected(row, selectedIds));
180
+ return (
181
+ <View testID={`default-table-group-${item.group?.id}`} style={[styles.row, { backgroundColor: '#ECFEFF', borderBottomColor: tableBorderColor, borderBottomWidth: tableBorderColor ? 1 : 0, minWidth: tableWidth, width: tableWidth, paddingVertical: 8 }]}>
182
+ {selectable ? renderSelectionCell(groupSelected, () => emitSelection(toggleGroupSelection(selectedIds, groupRows)), `Selecionar grupo ${item.group?.label || ''}`, '#ECFEFF') : null}
183
+ <View style={{ flex: 1, paddingHorizontal: 8 }}>
184
+ <Text style={[styles.headerText, { color: tableTextColor }]} numberOfLines={2}>{item.group?.label}</Text>
185
+ <Text style={{ color: tableMutedColor, fontSize: 12 }}>{item.group?.count} item(s)</Text>
186
+ </View>
187
+ {hasRowActions ? <View style={{ minWidth: actionsCellWidth, width: actionsCellWidth }} /> : null}
188
+ </View>
189
+ );
190
+ }
191
+
192
+ const row = item?.row || item;
193
+ const RowComponent = hasRowPress || selectable ? TouchableOpacity : View;
194
+ const selected = selectable && isRowSelected(row, selectedIds);
195
+ const rowBackgroundColor = resolveRowBackgroundColor({
196
+ selected,
197
+ index,
198
+ tableOddColor,
199
+ tableEvenColor,
200
+ });
201
+ return (
202
+ <RowComponent key={item?.id || getRowKey(row)} style={[styles.row, { backgroundColor: rowBackgroundColor, borderBottomColor: selected ? '#67E8F9' : tableBorderColor, borderBottomWidth: tableBorderColor ? 1 : 0, minWidth: tableWidth, width: tableWidth }, typeof rowStyle === 'function' ? rowStyle(row, index) : rowStyle]} {...(hasRowPress || selectable ? { activeOpacity: 0.84, onPress: () => { if (selectable) emitSelection(toggleSelectedId(selectedIds, row)); configs.onRowPress?.(row); } } : {})}>
203
+ {selectable ? renderSelectionCell(selected, () => emitSelection(toggleSelectedId(selectedIds, row)), `Selecionar linha ${getRowKey(row)}`, rowBackgroundColor) : null}
204
+ {tableColumns.map(column => (
205
+ <React.Fragment key={getColumnKey(column)}>
206
+ <DefaultTableInput column={column} options={column?.isIdentity ? { cellStyle: [styles.pinnedIdentityCell, styles.stickyIdentityCell, { backgroundColor: rowBackgroundColor }] } : { cellStyle: { backgroundColor: rowBackgroundColor } }} row={row} storeName={storeName} variant="cell" />
207
+ </React.Fragment>
208
+ ))}
209
+ {renderActionsCell(row, rowBackgroundColor)}
210
+ </RowComponent>
211
+ );
212
+ };
213
+
214
+ return (
215
+ <ScrollView horizontal onContentSizeChange={width => updateHorizontalMetrics({ contentWidth: Number(width || 0) })} onLayout={event => updateHorizontalMetrics({ viewportWidth: Number(event?.nativeEvent?.layout?.width || 0) })} onScroll={event => updateHorizontalMetrics({ x: Number(event?.nativeEvent?.contentOffset?.x || 0) })} scrollEventThrottle={16} style={styles.scroll} contentContainerStyle={styles.horizontalScrollContent}>
216
+ <View style={[styles.content, { minWidth: tableWidth, width: tableWidth }]}>
217
+ <View style={[styles.headerRow, { backgroundColor: tableHeaderColor, borderBottomColor: tableHeaderBorderColor, borderBottomWidth: tableHeaderBorderColor ? 1 : 0, minWidth: tableWidth, width: tableWidth }]}>
218
+ {selectable ? <View style={[styles.cell, { minWidth: SELECTION_COLUMN_WIDTH, width: SELECTION_COLUMN_WIDTH }]} /> : null}
219
+ {tableColumns.map(column => {
220
+ const fieldName = getColumnKey(column);
221
+ return (
222
+ <TouchableOpacity key={fieldName} style={[getColumnStyle(column), column?.isIdentity ? [styles.stickyIdentityCell, styles.stickyHeaderCell, { backgroundColor: tableHeaderColor }] : null]} activeOpacity={isSortableColumn(column) ? 0.8 : 1} onPress={() => configs.requestSort?.(column)}>
223
+ <View style={styles.sortableHeader}>
224
+ <Text style={[styles.headerText, { color: tableTextColor }]} numberOfLines={1}>{formatStoreColumnLabel({ columns, fieldName, fallbackLabel: column?.label || fieldName, storeName })}</Text>
225
+ {isSortableColumn(column) && configs.resolvedSort?.field === getSortField(column) ? <Icon name={configs.resolvedSort?.direction === 'desc' ? 'chevron-down' : 'chevron-up'} size={12} color={tableTextColor} /> : isSortableColumn(column) ? <Icon name="chevrons-up" size={12} color={tableBorderColor} /> : null}
226
+ </View>
227
+ </TouchableOpacity>
228
+ );
229
+ })}
230
+ {renderActionsHeader()}
231
+ </View>
232
+ <FlatList data={listData} extraData={selectedIds} keyExtractor={item => item?.id || getRowKey(item?.row || item)} renderItem={renderTableItem} style={styles.tableList} contentContainerStyle={styles.tableListContent} ListEmptyComponent={<DefaultTableEmptyState emptyStateLabel={emptyStateLabel} isLoading={isLoading} isTable tableMutedColor={tableMutedColor} />} nestedScrollEnabled onEndReached={configs.onEndReached} onEndReachedThreshold={END_REACHED_THRESHOLD} onRefresh={configs.onRefresh} refreshing={Boolean(configs.refreshing)} onScroll={event => { if (shouldTriggerEndReachedFromScroll(event)) configs.onEndReached?.(); }} scrollEventThrottle={120} showsVerticalScrollIndicator={false} />
233
+ </View>
234
+ </ScrollView>
235
+ );
236
+ };
237
+
238
+ export default DefaultTableRows;