@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,1895 +1,394 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
- import {
3
- FlatList,
4
- Modal,
5
- ScrollView,
6
- Text,
7
- TouchableOpacity,
8
- View,
9
- useWindowDimensions,
10
- } from 'react-native';
11
- import { useIsFocused } from '@react-navigation/native';
12
- import Icon from 'react-native-vector-icons/Feather';
13
- import { getAllStores, useStore } from '@store';
14
- import { useMessage } from '@controleonline/ui-common/src/react/components/MessageService';
15
- import Formatter from '@controleonline/ui-common/src/utils/formatter.js';
16
- import StateStore from '@controleonline/ui-layout/src/react/components/StateStore';
17
- import { getDateRange } from '@controleonline/ui-common/src/react/utils/dateRangeFilter';
18
- import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
19
- import { resolveThemePalette, withOpacity } from '@controleonline/../../src/styles/branding';
20
- import { colors } from '@controleonline/../../src/styles/colors';
21
- import DefaultColumnFilter from '../filters/DefaultColumnFilter';
22
- import DefaultSearch from '../filters/DefaultSearch';
23
- import DefaultForm from '../form/DefaultForm';
24
- import DefaultInput from '../inputs/DefaultInput';
1
+ import React, { useEffect, useMemo, useRef } from 'react';
2
+ import { View, useWindowDimensions } from 'react-native';
3
+ import { useIsFocused, useRoute } from '@react-navigation/native';
4
+ import { useStore } from '@store';
5
+ import { useMessage } from '@controleonline/ui-common/src/react/components/MessageService';
6
+ import { normalizeText } from '../inputs/defaultInputUtils';
7
+ import { DEFAULT_COMPACT_BREAKPOINT, isObject, stableSerialize } from './DefaultTable.utils';
25
8
  import {
26
- formatSaveValue,
27
- getColumnKey,
28
- isEditableColumn,
29
- normalizeId,
30
- normalizeOptionKey,
31
- normalizeText,
32
- resolveStoreNameFromList,
33
- resolveCellText,
34
- resolveEditValue,
35
- } from '../inputs/defaultInputUtils';
36
- import {
37
- persistTableSortPreference,
38
- persistTableViewModePreference,
39
- persistVisibleColumnsPreference,
40
- resolveStoredTableSortPreference,
41
- resolveStoredTableViewModePreference,
42
- resolveStoredVisibleColumnsPreference,
43
- sanitizeVisibleColumnsPreference,
44
- } from '../../utils/tableVisibleColumnsPreferences';
9
+ resolveDefaultTablePreferenceScope,
10
+ resolveStoredTableFiltersPreference,
11
+ resolveStoredTableViewModePreference,
12
+ resolveStoredVisibleColumnsPreference,
13
+ sanitizeTableFiltersPreference,
14
+ sanitizeVisibleColumnsPreference,
15
+ } from '../../utils/tableVisibleColumnsPreferences';
16
+ import DefaultTableBody from './DefaultTableBody';
17
+ import DefaultTableFooter from './DefaultTableFooter';
18
+ import DefaultTableToolbar from './DefaultTableToolbar';
45
19
  import styles from './DefaultTable.styles';
46
-
47
- const DEFAULT_CELL_MIN_WIDTH = 118;
48
- const DEFAULT_COMPACT_BREAKPOINT = 768;
49
- const END_REACHED_THRESHOLD = 0.35;
50
- const IDENTITY_CELL_MIN_WIDTH = 76;
51
- const MONEY_CELL_MIN_WIDTH = 132;
52
- const ACTIONS_CELL_WIDTH = 60;
53
- const SUMMARY_OPERATIONS = ['sum', 'count', 'avg', 'min', 'max'];
54
- const LIST_OPTIONS_PAGE_SIZE = 100;
55
-
56
- const shouldIncludeColumn = column =>
57
- Boolean(getColumnKey(column)) &&
58
- column?.show !== false &&
59
- column?.visible !== false &&
60
- column?.table !== false;
61
-
62
- const resolveListActionName = list =>
63
- normalizeText(list).split('/')[1] || 'getItems';
64
-
65
- const COMPANY_SCOPED_LIST_STORES = new Set([
66
- 'categories',
67
- 'paymentType',
68
- 'wallet',
69
- ]);
70
-
71
- const resolveListLoadParams = ({currentCompanyId, listStoreName}) => {
72
- if (!currentCompanyId || !COMPANY_SCOPED_LIST_STORES.has(listStoreName)) {
73
- return {};
74
- }
75
-
76
- return {people: currentCompanyId};
77
- };
78
-
79
- const resolveColumnListLoadParams = ({column, currentCompanyId}) => {
80
- const listStoreName = resolveStoreNameFromList(column?.list);
81
- const companyScopedParams = resolveListLoadParams({
82
- currentCompanyId,
83
- listStoreName,
84
- });
85
- const customParams =
86
- column?.listRequestParams &&
87
- typeof column.listRequestParams === 'object' &&
88
- !Array.isArray(column.listRequestParams)
89
- ? column.listRequestParams
90
- : {};
91
-
92
- return {
93
- ...companyScopedParams,
94
- ...customParams,
95
- };
96
- };
97
-
98
- const isObject = value =>
99
- value !== null && typeof value === 'object' && !Array.isArray(value);
100
-
101
- const getRowKey = (row, index = 0) =>
102
- String(row?.['@id'] || row?.id || index);
103
-
104
- const stableSerialize = value => {
105
- if (value === null || value === undefined) {
106
- return 'null';
107
- }
108
-
109
- if (Array.isArray(value)) {
110
- return `[${value.map(item => stableSerialize(item)).join(',')}]`;
111
- }
112
-
113
- if (value instanceof Date) {
114
- return JSON.stringify(value.toISOString());
115
- }
116
-
117
- if (isObject(value)) {
118
- return `{${Object.keys(value)
119
- .sort()
120
- .map(key => `${JSON.stringify(key)}:${stableSerialize(value[key])}`)
121
- .join(',')}}`;
122
- }
123
-
124
- return JSON.stringify(value);
125
- };
126
-
127
- const resolveDateRangeQuery = value => {
128
- if (!value || typeof value !== 'object') {
129
- return {};
130
- }
131
-
132
- const shortcut = value.shortcut || value.value || 'all';
133
- const customRange = value.customRange || { from: '', to: '' };
134
- const dateRange = getDateRange(shortcut, customRange, {
135
- relativeMode: 'rolling',
136
- useCurrentMoment: true,
137
- });
138
-
139
- return {
140
- after: dateRange?.after || '',
141
- before: dateRange?.before || '',
142
- };
143
- };
144
-
145
- const resolveFilterQueryValue = value => {
146
- if (Array.isArray(value)) {
147
- return value
148
- .map(item => resolveFilterQueryValue(item))
149
- .filter(item => item !== '' && item !== null && item !== undefined);
150
- }
151
-
152
- if (value && typeof value === 'object') {
153
- return resolveFilterQueryValue(
154
- value.value ?? value.id ?? value['@id'] ?? value.key ?? '',
155
- );
156
- }
157
-
158
- return normalizeText(value);
159
- };
160
-
161
- const normalizeCollectionItems = response => {
162
- if (Array.isArray(response)) return response;
163
- if (Array.isArray(response?.member)) return response.member;
164
- if (Array.isArray(response?.['hydra:member'])) return response['hydra:member'];
165
-
166
- return [];
167
- };
168
-
169
- const resolveHasMore = ({ hasMore, dataLength, totalItems }) => {
170
- if (hasMore !== null && hasMore !== undefined) {
171
- return hasMore;
172
- }
173
-
174
- const resolvedTotalItems = Number(totalItems);
175
- if (!Number.isFinite(resolvedTotalItems)) {
176
- return false;
177
- }
178
-
179
- return dataLength < resolvedTotalItems;
180
- };
181
-
182
- const humanizeSummaryLabel = value =>
183
- normalizeText(value)
184
- .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
185
- .replace(/[._-]+/g, ' ')
186
- .replace(/\s+/g, ' ')
187
- .trim();
188
-
189
- const isMoneySummaryPath = path =>
190
- /(amount|price|total|value|paid|open|receivable|payable|pending)/i.test(
191
- Array.isArray(path) ? path.join('.') : normalizeText(path),
192
- );
193
-
194
- const normalizeSummaryMoneyValue = value => {
195
- if (typeof value === 'number') return Number.isFinite(value) ? value : 0;
196
-
197
- const rawValue = normalizeText(value).replace(/[^0-9,.-]/g, '');
198
- if (!rawValue) return 0;
199
-
200
- const normalizedValue = rawValue.includes(',')
201
- ? rawValue.replace(/\./g, '').replace(',', '.')
202
- : rawValue;
203
- const parsedValue = Number(normalizedValue);
204
-
205
- return Number.isFinite(parsedValue) ? parsedValue : 0;
206
- };
207
-
208
- const getSummaryOperations = column => {
209
- if (typeof column?.summary === 'string') return [column.summary];
210
- if (Array.isArray(column?.summary)) return column.summary;
211
- if (isObject(column?.summary)) {
212
- return Object.entries(column.summary)
213
- .filter(([, value]) => value)
214
- .map(([operation]) => operation);
20
+ import { useDefaultTablePagination } from './useDefaultTablePagination';
21
+ import {
22
+ useDefaultTableSortedData,
23
+ useDefaultTableSortState,
24
+ } from './useDefaultTableSorting';
25
+ import useDefaultTableTheme from './useDefaultTableTheme';
26
+
27
+ export {
28
+ mergeSortedDataWithLiveItems,
29
+ resolveColumnListLoadParams,
30
+ shouldTriggerEndReachedFromScroll,
31
+ } from './DefaultTable.utils';
32
+
33
+ const publishStoreValue = (store, actionName, value, getterName) => {
34
+ if (typeof store?.actions?.[actionName] === 'function') {
35
+ store.actions[actionName](value);
36
+ return;
215
37
  }
216
38
 
217
- return SUMMARY_OPERATIONS.filter(operation => column?.[operation] === true);
218
- };
219
-
220
- const getSummaryField = (column, operation) => {
221
- if (isObject(column?.summary)) {
222
- const summaryField = column.summary[operation];
223
- if (typeof summaryField === 'string') return summaryField;
39
+ if (store?.getters) {
40
+ store.getters[getterName] = value;
224
41
  }
225
-
226
- return getColumnKey(column);
227
42
  };
228
43
 
229
- const formatSummaryValue = ({ column, columns, path, storeName, value }) => {
230
- const columnKey = column ? getColumnKey(column) : '';
231
- const shouldFormatMoney = isMoneySummaryPath(path) || isMoneySummaryPath(columnKey);
232
-
233
- if (shouldFormatMoney) {
234
- return Formatter.formatMoney(normalizeSummaryMoneyValue(value));
235
- }
236
-
237
- if (column) {
238
- return resolveCellText({
239
- column,
240
- columns,
241
- row: { [columnKey]: value },
242
- storeName,
243
- value,
244
- });
44
+ const assignGetterValue = (store, getterName, value) => {
45
+ if (store?.getters) {
46
+ store.getters[getterName] = value;
245
47
  }
246
-
247
- return normalizeText(value);
248
48
  };
249
49
 
250
- const flattenSummaryEntries = ({
251
- path = [],
50
+ const DefaultTable = ({
51
+ accentColor = null,
52
+ actions = {},
53
+ add = null,
54
+ compactBreakpoint = DEFAULT_COMPACT_BREAKPOINT,
55
+ columns = [],
56
+ data = undefined,
57
+ filters = {},
58
+ footerComponent = null,
59
+ forceCardsOnCompact = true,
60
+ getOptionsForColumn = null,
61
+ hasMore = null,
62
+ initialViewMode = 'table',
63
+ isLoading = false,
64
+ onAdd = null,
65
+ onDataLoaded = null,
66
+ onEditRow = null,
67
+ onEndReached = null,
68
+ onFilterChange = null,
69
+ onMomentumScrollBegin = null,
70
+ onRowPress = null,
71
+ onSaved = null,
72
+ onScrollBeginDrag = null,
73
+ onSortChange = null,
74
+ pageSize = null,
75
+ renderCard = null,
76
+ requestParams = {},
77
+ rowActionsComponent = null,
78
+ rowStyle = null,
79
+ showColumnFiltersButton = true,
80
+ showRowActions = true,
81
+ showTotalItemsInCompactToolbar = false,
82
+ showTotalItemsInFooter = true,
83
+ sort = null,
84
+ storeName = '',
85
+ summary = undefined,
252
86
  summaryLabels = {},
253
- usedPaths,
254
- value,
87
+ toolbarActions = [],
88
+ visibleColumnsPreferenceKey = '',
255
89
  }) => {
256
- if (!isObject(value)) {
257
- const pathKey = path.join('.');
258
- if (!pathKey || usedPaths.has(pathKey)) return [];
259
-
260
- const fallbackLabel = humanizeSummaryLabel(path[path.length - 1] || pathKey);
261
-
262
- return [{
263
- key: pathKey,
264
- label: summaryLabels[pathKey] || summaryLabels[path[path.length - 1]] || fallbackLabel,
265
- path,
266
- value,
267
- }];
268
- }
269
-
270
- return Object.entries(value).flatMap(([key, childValue]) =>
271
- flattenSummaryEntries({
272
- path: [...path, key],
273
- summaryLabels,
274
- usedPaths,
275
- value: childValue,
276
- }),
277
- );
278
- };
279
-
280
- const isSortableColumn = column => column?.sortable === true;
281
-
282
- const getSortField = column => column?.sortField || getColumnKey(column);
283
-
284
- const resolveDefaultSort = columns => {
285
- if (!Array.isArray(columns)) {
286
- return null;
287
- }
288
-
289
- for (const column of columns) {
290
- if (!column || column?.defaultSort === undefined || column?.defaultSort === null || column?.defaultSort === false) {
291
- continue;
292
- }
293
-
294
- const field = getSortField(column);
295
- const defaultSort = column.defaultSort;
296
-
297
- if (defaultSort && typeof defaultSort === 'object' && !Array.isArray(defaultSort)) {
298
- const resolvedDirection = normalizeText(defaultSort.direction || defaultSort.order || 'desc').toLowerCase();
299
- const resolvedField = normalizeText(
300
- defaultSort.field || defaultSort.sortField || defaultSort.key || defaultSort.name || field,
301
- );
302
-
303
- return {
304
- direction: resolvedDirection === 'asc' ? 'asc' : 'desc',
305
- field: resolvedField || field,
306
- };
307
- }
308
-
309
- if (typeof defaultSort === 'string') {
310
- const normalizedSort = normalizeText(defaultSort).toLowerCase();
311
- if (normalizedSort === 'asc' || normalizedSort === 'desc') {
312
- return {
313
- direction: normalizedSort,
314
- field,
315
- };
316
- }
317
-
318
- return {
319
- direction: 'desc',
320
- field: normalizeText(defaultSort) || field,
321
- };
322
- }
323
-
324
- if (defaultSort === true) {
325
- return {
326
- direction: 'asc',
327
- field,
328
- };
329
- }
330
- }
331
-
332
- return null;
333
- };
334
-
335
- const sanitizeStoredSortPreference = ({
336
- columns = [],
337
- fallbackSort = null,
338
- sort = null,
339
- }) => {
340
- const normalizedFallback =
341
- fallbackSort &&
342
- typeof fallbackSort === 'object' &&
343
- typeof fallbackSort.field === 'string' &&
344
- fallbackSort.field.trim()
345
- ? {
346
- direction: fallbackSort.direction === 'asc' ? 'asc' : 'desc',
347
- field: fallbackSort.field.trim(),
348
- }
349
- : null;
350
-
351
- if (
352
- !sort ||
353
- typeof sort !== 'object' ||
354
- typeof sort.field !== 'string' ||
355
- !sort.field.trim()
356
- ) {
357
- return normalizedFallback;
358
- }
359
-
360
- const normalizedDirection = sort.direction === 'asc' ? 'asc' : 'desc';
361
- const normalizedField = sort.field.trim();
362
- const sortableFields = new Set(
363
- (Array.isArray(columns) ? columns : [])
364
- .filter(isSortableColumn)
365
- .map(column => getSortField(column))
366
- .filter(Boolean),
367
- );
368
-
369
- if (!sortableFields.has(normalizedField)) {
370
- return normalizedFallback;
371
- }
372
-
373
- return {
374
- direction: normalizedDirection,
375
- field: normalizedField,
376
- };
377
- };
378
-
379
- const readValueByPath = (object, path) => {
380
- if (!object || !path) return object;
381
-
382
- return String(path)
383
- .split('.')
384
- .reduce((currentValue, key) => {
385
- if (currentValue === null || currentValue === undefined) return currentValue;
386
- return currentValue?.[key];
387
- }, object);
388
- };
389
-
390
- const normalizeSortText = value =>
391
- normalizeText(value)
392
- .normalize('NFD')
393
- .replace(/[\u0300-\u036f]/g, '')
394
- .toLowerCase();
395
-
396
- const isDateLikeColumn = column =>
397
- column?.inputType === 'date' ||
398
- column?.inputType === 'date-range' ||
399
- column?.type === 'date' ||
400
- column?.type === 'range-date' ||
401
- /date/i.test(getColumnKey(column));
402
-
403
- const resolveSortComparable = ({ column, row, storeName, columns }) => {
404
- const fieldName = getColumnKey(column);
405
- const sortField = getSortField(column);
406
- const rawValue = sortField === fieldName ? row?.[fieldName] : readValueByPath(row, sortField);
407
-
408
- if (isDateLikeColumn(column)) {
409
- const dateValue =
410
- rawValue && typeof rawValue === 'object'
411
- ? rawValue?.value ??
412
- rawValue?.date ??
413
- rawValue?.createdAt ??
414
- rawValue?.updatedAt ??
415
- rawValue?.['@id'] ??
416
- rawValue?.[fieldName] ??
417
- rawValue
418
- : rawValue;
419
- const parsedDate = Date.parse(dateValue);
420
- return Number.isFinite(parsedDate) ? parsedDate : Number.NEGATIVE_INFINITY;
421
- }
422
-
423
- const resolvedValue = sortField === fieldName
424
- ? resolveCellText({
425
- column,
426
- columns,
427
- row,
428
- storeName,
429
- })
430
- : normalizeText(rawValue ?? resolveCellText({
431
- column,
432
- columns,
433
- row,
90
+ const { width } = useWindowDimensions();
91
+ const route = useRoute?.();
92
+ const store = useStore(storeName);
93
+ const isFocused = useIsFocused();
94
+ const configsSignatureRef = useRef('');
95
+ const { showError } = useMessage() || {};
96
+ const { themeColors } = useDefaultTableTheme(accentColor);
97
+ const tablePreferenceScope = useMemo(
98
+ () =>
99
+ resolveDefaultTablePreferenceScope({
100
+ preferenceKey: visibleColumnsPreferenceKey,
101
+ route,
434
102
  storeName,
435
- }));
436
-
437
- const normalizedNumber = Number(
438
- String(resolvedValue).replace(/[^0-9,.-]/g, '').replace(',', '.'),
103
+ }),
104
+ [route?.key, route?.name, storeName, visibleColumnsPreferenceKey],
439
105
  );
440
-
441
- if (Number.isFinite(normalizedNumber) && String(resolvedValue).match(/[0-9]/)) {
442
- return normalizedNumber;
106
+ const storedViewMode = useMemo(
107
+ () => resolveStoredTableViewModePreference(tablePreferenceScope, null),
108
+ [initialViewMode, tablePreferenceScope],
109
+ );
110
+ const resolvedActions = useMemo(
111
+ () => ({
112
+ ...(store?.actions || {}),
113
+ ...(actions || {}),
114
+ }),
115
+ [actions, store?.actions],
116
+ );
117
+ const autoMode = data === undefined && normalizeText(storeName) !== '';
118
+ const storeColumns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
119
+ const columnsForTable = storeColumns.length > 0 ? storeColumns : columns;
120
+ const storeFilters = isObject(store?.getters?.filters) ? store.getters.filters : {};
121
+ const requestParamsSeed = isObject(requestParams) ? requestParams : {};
122
+ const resolvedTotalItems = store?.getters?.totalItems;
123
+ const currentConfigs = store?.getters?.configs || {};
124
+ const isCompactView = width > 0 && width <= compactBreakpoint;
125
+
126
+ if (storeColumns.length === 0 && Array.isArray(columns) && columns.length > 0) {
127
+ assignGetterValue(store, 'columns', columns);
443
128
  }
444
129
 
445
- return normalizeSortText(resolvedValue);
446
- };
447
-
448
- const getColumnStyle = column => {
449
- const key = getColumnKey(column);
450
- if (column?.isIdentity) return [styles.cell, styles.identityCell];
451
- if (['price', 'total', 'amount', 'value'].includes(key)) {
452
- return [styles.cell, styles.moneyCell];
130
+ if (data !== undefined && Array.isArray(data)) {
131
+ assignGetterValue(store, 'items', data);
453
132
  }
454
- return styles.cell;
455
- };
456
133
 
457
- const getColumnMinWidth = column => {
458
- const key = getColumnKey(column);
459
- if (column?.isIdentity) return IDENTITY_CELL_MIN_WIDTH;
460
- if (['price', 'total', 'amount', 'value'].includes(key)) {
461
- return MONEY_CELL_MIN_WIDTH;
462
- }
463
- return DEFAULT_CELL_MIN_WIDTH;
464
- };
134
+ const { requestSort, resolvedSort } = useDefaultTableSortState({
135
+ autoMode,
136
+ columnsForTable,
137
+ onSortChange,
138
+ sort,
139
+ tablePreferenceScope,
140
+ });
141
+ const {
142
+ buildRequestQuery,
143
+ currentPage,
144
+ handleEndReached,
145
+ resolvedData,
146
+ } = useDefaultTablePagination({
147
+ autoMode,
148
+ columnsForTable,
149
+ data,
150
+ filters: storeFilters,
151
+ hasMore,
152
+ isFocused,
153
+ isLoading,
154
+ onEndReached,
155
+ pageSize,
156
+ requestParams: requestParamsSeed,
157
+ resolvedActions,
158
+ resolvedSort,
159
+ resolvedTotalItems,
160
+ showError,
161
+ store,
162
+ storeName,
163
+ });
164
+ const sortedData = useDefaultTableSortedData({
165
+ resolvedData,
166
+ resolvedSort,
167
+ storeName,
168
+ tableColumns: columnsForTable,
169
+ });
170
+ const debugFallbackParameters = useMemo(() => {
171
+ if (autoMode) {
172
+ return buildRequestQuery(currentPage || 1, false);
173
+ }
465
174
 
466
- const DefaultTable = ({
467
- accentColor = null,
468
- actions = {},
469
- add = null,
470
- compactBreakpoint = DEFAULT_COMPACT_BREAKPOINT,
471
- columns = [],
472
- data = undefined,
473
- filters = {},
474
- forceCardsOnCompact = true,
475
- getOptionsForColumn = null,
476
- hasMore = null,
477
- initialViewMode = 'table',
478
- pageSize = null,
479
- isLoading = false,
480
- onEditRow = null,
481
- onEndReached = null,
482
- onMomentumScrollBegin = null,
483
- onScrollBeginDrag = null,
484
- onAdd = null,
485
- onFilterChange = null,
486
- onRowPress = null,
487
- onSaved = null,
488
- onSortChange = null,
489
- onDataLoaded = null,
490
- requestParams = {},
491
- renderCard = null,
492
- searchProps = null,
493
- toolbarActions = [],
494
- showColumnFiltersButton = true,
495
- showTotalItemsInFooter = true,
496
- showTotalItemsInCompactToolbar = false,
497
- showRowActions = true,
498
- sort = null,
499
- storeName = '',
500
- summary = null,
501
- summaryLabels = null,
502
- totalItems = null,
503
- totalItemsLabel = null,
504
- totalItemsText = null,
505
- visibleColumnsPreferenceKey = '',
506
- }) => {
507
- const { width } = useWindowDimensions();
508
- const store = useStore(storeName);
509
- const peopleStore = useStore('people');
510
- const themeStore = useStore('theme');
511
- const [editingCell, setEditingCell] = useState(null);
512
- const [editingRow, setEditingRow] = useState(null);
513
- const [formMode, setFormMode] = useState('edit');
514
- const [isColumnMenuOpen, setIsColumnMenuOpen] = useState(false);
515
- const [, setListOptionsVersion] = useState(0);
516
- const [savingCell, setSavingCell] = useState(null);
517
- const [showColumnFilters, setShowColumnFilters] = useState(false);
518
- const [tableContainerWidth, setTableContainerWidth] = useState(0);
519
- const endReachedLockRef = useRef(false);
520
- const loadedListStoresRef = useRef(new Set());
521
- const listOptionsWasFocusedRef = useRef(false);
522
- const previousPaginationStateRef = useRef({
523
- dataLength: Array.isArray(data) ? data.length : 0,
524
- filtersKey: JSON.stringify(filters || {}),
525
- sortDirection: sort?.direction,
526
- sortField: sort?.field,
527
- });
528
- const visibleColumnsSeed = useMemo(
529
- () =>
530
- sanitizeVisibleColumnsPreference({
531
- columns: Array.isArray(store?.getters?.columns) && store.getters.columns.length > 0
532
- ? store.getters.columns
533
- : columns,
534
- visibleColumns: resolveStoredVisibleColumnsPreference(
535
- visibleColumnsPreferenceKey,
536
- ),
537
- }),
538
- [columns, store?.getters?.columns, visibleColumnsPreferenceKey],
539
- );
540
- const viewModeSeed = useMemo(
175
+ return {
176
+ filters: storeFilters,
177
+ requestParams: requestParamsSeed,
178
+ sort: resolvedSort || null,
179
+ };
180
+ }, [autoMode, buildRequestQuery, currentPage, requestParamsSeed, resolvedSort, storeFilters]);
181
+ const effectiveViewMode =
182
+ currentConfigs.viewMode ||
183
+ storedViewMode ||
184
+ (isCompactView && forceCardsOnCompact !== false ? 'cards' : initialViewMode);
185
+ const tableFiltersVisible = Boolean(currentConfigs.tableFiltersVisible);
186
+ const defaultTableConfigs = useMemo(
187
+ () => ({
188
+ add,
189
+ compactBreakpoint,
190
+ debugFallbackParameters,
191
+ effectiveViewMode,
192
+ filters,
193
+ footerComponent,
194
+ forceCardsOnCompact,
195
+ getOptionsForColumn,
196
+ initialViewMode,
197
+ onAdd,
198
+ onDataLoaded,
199
+ onEditRow,
200
+ onEndReached: handleEndReached,
201
+ onFilterChange,
202
+ onMomentumScrollBegin,
203
+ onRowPress,
204
+ onSaved,
205
+ onScrollBeginDrag,
206
+ renderCard,
207
+ requestParams: requestParamsSeed,
208
+ requestSort,
209
+ resolvedSort,
210
+ rowActionsComponent,
211
+ rowStyle,
212
+ showColumnFiltersButton,
213
+ showRowActions,
214
+ showTotalItemsInCompactToolbar,
215
+ showTotalItemsInFooter,
216
+ sortedData,
217
+ summary,
218
+ summaryLabels,
219
+ tableFiltersVisible,
220
+ tablePreferenceScope,
221
+ toolbarActions,
222
+ viewMode: effectiveViewMode,
223
+ }),
224
+ [
225
+ add,
226
+ compactBreakpoint,
227
+ debugFallbackParameters,
228
+ effectiveViewMode,
229
+ filters,
230
+ footerComponent,
231
+ forceCardsOnCompact,
232
+ getOptionsForColumn,
233
+ handleEndReached,
234
+ initialViewMode,
235
+ onAdd,
236
+ onDataLoaded,
237
+ onEditRow,
238
+ onFilterChange,
239
+ onMomentumScrollBegin,
240
+ onRowPress,
241
+ onSaved,
242
+ onScrollBeginDrag,
243
+ renderCard,
244
+ requestParamsSeed,
245
+ requestSort,
246
+ resolvedSort,
247
+ rowActionsComponent,
248
+ rowStyle,
249
+ showColumnFiltersButton,
250
+ showRowActions,
251
+ showTotalItemsInCompactToolbar,
252
+ showTotalItemsInFooter,
253
+ sortedData,
254
+ summary,
255
+ summaryLabels,
256
+ tableFiltersVisible,
257
+ tablePreferenceScope,
258
+ toolbarActions,
259
+ ],
260
+ );
261
+ const defaultTableConfigsSignature = useMemo(
541
262
  () =>
542
- resolveStoredTableViewModePreference(
543
- visibleColumnsPreferenceKey,
263
+ stableSerialize({
264
+ add,
265
+ compactBreakpoint,
266
+ debugFallbackParameters,
267
+ effectiveViewMode,
268
+ filters,
269
+ forceCardsOnCompact,
544
270
  initialViewMode,
545
- ),
546
- [initialViewMode, visibleColumnsPreferenceKey],
547
- );
548
- const [visibleColumns, setVisibleColumns] = useState(() => visibleColumnsSeed);
549
- const [viewMode, setViewMode] = useState(() => viewModeSeed);
550
- const { currentCompany } = peopleStore.getters || {};
551
- const { colors: themeColors } = themeStore.getters || {};
552
- const themeTokens = useMemo(
553
- () => ({...themeColors, ...(currentCompany?.theme?.colors || {})}),
554
- [currentCompany?.theme?.colors, themeColors],
555
- );
556
- const palette = useMemo(
557
- () => resolveThemePalette(themeTokens, colors),
558
- [themeTokens],
559
- );
560
- const resolvedAccentColor = accentColor || palette.primary;
561
- const tableHeaderColor = themeTokens['bg-headers-light'] || resolvedAccentColor;
562
- const tableEvenColor = themeTokens.listItemEvenRow || themeTokens['bg-even-light'] || palette.background;
563
- const tableOddColor = themeTokens.listItemOddRow || themeTokens['bg-odd-light'] || palette.background;
564
- const tableBorderColor = palette.border;
565
- const tableSurfaceColor = palette.background;
566
- const tableTextColor = palette.text;
567
- const tableMutedColor = palette.textSecondary;
568
- const tableOnAccentColor = palette.secondary || palette.text;
569
- const isFocused = useIsFocused();
570
- const {showError} = useMessage() || {};
571
- const autoMode = data === undefined && normalizeText(storeName) !== '';
572
- const searchKey = searchProps?.searchKey || 'search';
573
- const storeActions = store?.actions || {};
574
- const storeColumns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
575
- const columnsForTable = storeColumns.length > 0 ? storeColumns : columns;
576
- const listColumnsSignature = useMemo(
577
- () =>
578
- columnsForTable
579
- .filter(column => normalizeText(column?.list))
580
- .map(column => `${getColumnKey(column)}:${normalizeText(column.list)}`)
581
- .join('|'),
582
- [columnsForTable],
583
- );
584
- const storeFilters = isObject(store?.getters?.filters) ? store.getters.filters : {};
585
- const requestParamsSeed = isObject(requestParams) ? requestParams : {};
586
- const initialFiltersSeed = autoMode
587
- ? (Object.keys(filters || {}).length > 0 ? filters : storeFilters)
588
- : (isObject(filters) ? filters : {});
589
- const [autoFilters, setAutoFilters] = useState(() => initialFiltersSeed);
590
- const defaultSortSeed = useMemo(
591
- () => resolveDefaultSort(columnsForTable),
592
- [columnsForTable],
593
- );
594
- const storedSortSeed = useMemo(
595
- () =>
596
- sanitizeStoredSortPreference({
597
- columns: columnsForTable,
598
- fallbackSort: sort || defaultSortSeed || null,
599
- sort: resolveStoredTableSortPreference(visibleColumnsPreferenceKey),
600
- }),
601
- [columnsForTable, defaultSortSeed, sort, visibleColumnsPreferenceKey],
602
- );
603
- const [autoSort, setAutoSort] = useState(() => storedSortSeed);
604
- const [autoHasLoaded, setAutoHasLoaded] = useState(false);
605
- const [autoLoading, setAutoLoading] = useState(false);
606
- const [autoLoadingMore, setAutoLoadingMore] = useState(false);
607
- const [autoLastPageCount, setAutoLastPageCount] = useState(0);
608
- const autoRequestIdRef = useRef(0);
609
- const autoLoadedQueryKeyRef = useRef('');
610
- const autoErroredQueryKeyRef = useRef('');
611
- const autoPageRef = useRef(0);
612
- const pageSizeNumber = Number(requestParamsSeed.itemsPerPage || pageSize || 50) || 50;
613
- const resolvedSort = autoMode ? autoSort : sort;
614
- const resolvedFilters = autoMode ? autoFilters : (filters || {});
615
- const resolvedSearchValue = normalizeText(resolvedFilters?.[searchKey]);
616
-
617
- useEffect(() => {
618
- const shouldRefreshCompanyScopedLists =
619
- isFocused && !listOptionsWasFocusedRef.current;
620
-
621
- listOptionsWasFocusedRef.current = isFocused;
622
-
623
- if (!isFocused || !listColumnsSignature) return;
624
-
625
- const stores = getAllStores?.() || {};
626
- const loadPromises = [];
627
-
628
- columnsForTable.forEach(column => {
629
- if (!column?.list) return;
630
-
631
- const explicitOptions = getOptionsForColumn?.(column);
632
- if (Array.isArray(explicitOptions) && explicitOptions.length > 0) return;
633
-
634
- const listStoreName = resolveStoreNameFromList(column.list);
635
- const actionName = resolveListActionName(column.list);
636
- const listStore = stores?.[listStoreName];
637
- const listAction = listStore?.actions?.[actionName];
638
-
639
- const listLoadParams = resolveColumnListLoadParams({
640
- column,
641
- currentCompanyId: currentCompany?.id,
642
- });
643
- const isCompanyScopedList = Object.keys(listLoadParams).length > 0;
644
-
645
- if (!listStoreName || typeof listAction !== 'function') return;
646
- if (
647
- !isCompanyScopedList &&
648
- Array.isArray(listStore?.getters?.items) &&
649
- listStore.getters.items.length > 0
650
- ) {
651
- return;
652
- }
653
-
654
- const loadKey = `${listStoreName}:${actionName}:${stableSerialize(listLoadParams)}`;
655
- if (shouldRefreshCompanyScopedLists && isCompanyScopedList) {
656
- loadedListStoresRef.current.delete(loadKey);
657
- }
658
- if (loadedListStoresRef.current.has(loadKey)) return;
659
-
660
- loadedListStoresRef.current.add(loadKey);
661
- loadPromises.push(
662
- Promise.resolve(
663
- listAction({
664
- ...listLoadParams,
665
- itemsPerPage: LIST_OPTIONS_PAGE_SIZE,
666
- __storeMeta: {
667
- dedupeKey: `default-table-list-options:${loadKey}`,
668
- skipSystemError: true,
669
- },
670
- }),
671
- ).catch(() => {
672
- loadedListStoresRef.current.delete(loadKey);
673
- }),
674
- );
675
- });
676
-
677
- if (loadPromises.length === 0) return;
678
-
679
- let cancelled = false;
680
- Promise.allSettled(loadPromises).then(() => {
681
- if (!cancelled) {
682
- setListOptionsVersion(version => version + 1);
683
- }
684
- });
685
-
686
- return () => {
687
- cancelled = true;
688
- };
689
- }, [columnsForTable, currentCompany?.id, getOptionsForColumn, isFocused, listColumnsSignature]);
690
-
691
- const buildRequestQuery = useCallback(
692
- (page, append = false) => {
693
- const query = {
694
- ...requestParamsSeed,
695
- itemsPerPage: pageSizeNumber,
696
- page,
697
- };
698
-
699
- if (autoSort?.field && autoSort?.direction) {
700
- query[`order[${autoSort.field}]`] = autoSort.direction;
701
- }
702
-
703
- Object.entries(autoFilters || {}).forEach(([fieldName, value]) => {
704
- if (!fieldName) return;
705
-
706
- if (fieldName === searchKey) {
707
- const normalizedSearch = normalizeText(value).replace(/^#/, '');
708
- if (normalizedSearch) {
709
- query.search = normalizedSearch;
710
- if (searchKey !== 'search') {
711
- query[searchKey] = normalizedSearch;
712
- }
713
- }
714
- return;
715
- }
716
-
717
- const column = columnsForTable.find(item => getColumnKey(item) === fieldName);
718
-
719
- if (column?.inputType === 'date-range' || column?.type === 'range-date') {
720
- const dateRange = resolveDateRangeQuery(value);
721
- if (dateRange.after) {
722
- query[`${fieldName}[after]`] = dateRange.after;
723
- }
724
- if (dateRange.before) {
725
- query[`${fieldName}[before]`] = dateRange.before;
726
- }
727
- return;
728
- }
729
-
730
- const normalizedValue = resolveFilterQueryValue(value);
731
- if (Array.isArray(normalizedValue)) {
732
- if (normalizedValue.length > 0) {
733
- query[fieldName] = normalizedValue;
734
- }
735
- return;
736
- }
737
-
738
- if (normalizedValue !== '') {
739
- query[fieldName] = normalizedValue;
740
- }
741
- });
742
-
743
- if (append) {
744
- query.append = true;
745
- }
746
-
747
- return query;
748
- },
749
- [autoFilters, autoSort?.direction, autoSort?.field, columnsForTable, pageSizeNumber, requestParamsSeed, searchKey],
750
- );
751
- const autoQuerySignature = useMemo(
752
- () =>
753
- stableSerialize({
754
- filters: autoFilters,
755
- pageSize: pageSizeNumber,
756
- requestParams: requestParamsSeed,
757
- searchKey,
758
- sort: autoSort,
759
- }),
760
- [autoFilters, autoSort, pageSizeNumber, requestParamsSeed, searchKey],
761
- );
762
-
763
- const loadAutoPage = useCallback(
764
- (page, { append = false } = {}) => {
765
- if (!autoMode || typeof storeActions.getItems !== 'function') {
766
- return Promise.resolve([]);
767
- }
768
-
769
- const requestId = autoRequestIdRef.current + 1;
770
- autoRequestIdRef.current = requestId;
771
-
772
- if (append) {
773
- setAutoLoadingMore(true);
774
- } else {
775
- setAutoLoading(true);
776
- setAutoHasLoaded(false);
777
- setAutoLastPageCount(0);
778
- autoPageRef.current = 0;
779
- }
780
-
781
- const query = buildRequestQuery(page, append);
782
-
783
- return Promise.resolve(storeActions.getItems(query))
784
- .then(response => {
785
- if (autoRequestIdRef.current !== requestId) {
786
- return response;
787
- }
788
-
789
- const pageItems = normalizeCollectionItems(response);
790
- autoPageRef.current = page;
791
- autoLoadedQueryKeyRef.current = autoQuerySignature;
792
- autoErroredQueryKeyRef.current = '';
793
- setAutoHasLoaded(true);
794
- setAutoLastPageCount(pageItems.length);
795
- return pageItems;
796
- })
797
- .catch(error => {
798
- if (autoRequestIdRef.current === requestId) {
799
- autoErroredQueryKeyRef.current = autoQuerySignature;
800
- showError?.(error?.message || 'Nao foi possivel carregar os registros.');
801
- }
802
-
803
- return [];
804
- })
805
- .finally(() => {
806
- if (autoRequestIdRef.current === requestId) {
807
- setAutoLoading(false);
808
- setAutoLoadingMore(false);
809
- endReachedLockRef.current = false;
810
- }
811
- });
812
- },
813
- [autoMode, autoQuerySignature, buildRequestQuery, showError, storeActions],
814
- );
815
-
816
- const availableColumns = useMemo(
817
- () => columnsForTable.filter(column => shouldIncludeColumn(column)),
818
- [columnsForTable],
819
- );
820
-
821
- useEffect(() => {
822
- setVisibleColumns(prev =>
823
- stableSerialize(prev) === stableSerialize(visibleColumnsSeed)
824
- ? prev
825
- : visibleColumnsSeed
826
- );
827
- }, [visibleColumnsSeed]);
828
-
829
- useEffect(() => {
830
- setViewMode(viewModeSeed);
831
- }, [viewModeSeed]);
832
-
833
- useEffect(() => {
834
- if (!autoMode) return;
835
-
836
- setAutoSort(prev =>
837
- stableSerialize(prev) === stableSerialize(storedSortSeed)
838
- ? prev
839
- : storedSortSeed,
840
- );
841
- }, [autoMode, storedSortSeed]);
842
-
843
- useEffect(() => {
844
- if (!autoMode) return;
845
-
846
- setAutoFilters(prev => {
847
- if (stableSerialize(prev) === stableSerialize(storeFilters)) {
848
- return prev;
849
- }
850
-
851
- return storeFilters;
852
- });
853
- }, [autoMode, storeFilters]);
854
-
855
- useEffect(() => {
856
- if (!autoMode || !isFocused) return;
857
- if (typeof storeActions.getItems !== 'function') return;
858
- if (
859
- autoLoading ||
860
- autoLoadingMore ||
861
- autoLoadedQueryKeyRef.current === autoQuerySignature ||
862
- autoErroredQueryKeyRef.current === autoQuerySignature
863
- ) {
864
- return;
865
- }
866
-
867
- endReachedLockRef.current = false;
868
- loadAutoPage(1, { append: false });
869
- }, [
870
- autoLoading,
871
- autoLoadingMore,
872
- autoMode,
873
- autoQuerySignature,
874
- isFocused,
875
- loadAutoPage,
876
- storeActions,
877
- ]);
878
-
879
- const tableColumns = useMemo(
880
- () => columnsForTable.filter(column => shouldIncludeColumn(column) && visibleColumns[getColumnKey(column)] !== false),
881
- [columnsForTable, visibleColumns],
882
- );
883
-
884
- const editableColumns = useMemo(
885
- () => tableColumns.filter(isEditableColumn),
886
- [tableColumns],
887
- );
888
-
889
- const activeFilterCount = useMemo(
890
- () => Object.values(resolvedFilters || {}).filter(value => normalizeText(value) !== '').length,
891
- [resolvedFilters],
892
- );
893
- const hasRowActions =
894
- showRowActions !== false &&
895
- (editableColumns.length > 0 || typeof onEditRow === 'function');
896
- const storeAddConfig = store?.getters?.add;
897
- const addConfig = add !== null && add !== undefined ? add : storeAddConfig;
898
- const hasAddInstruction = addConfig === true || (isObject(addConfig) && addConfig.enabled !== false);
899
- const shouldRenderAddButton =
900
- hasAddInstruction &&
901
- (typeof onAdd === 'function' || typeof actions.save === 'function');
902
- const resolvedIsLoading = autoMode ? (autoLoading || autoLoadingMore) : Boolean(isLoading);
903
- const resolvedData = autoMode
904
- ? (autoHasLoaded && Array.isArray(store?.getters?.items) ? store.getters.items : [])
905
- : (Array.isArray(data) ? data : []);
906
- const tableMinimumWidth = useMemo(
907
- () =>
908
- tableColumns.reduce(
909
- (totalWidth, column) => totalWidth + getColumnMinWidth(column),
910
- hasRowActions ? ACTIONS_CELL_WIDTH : 0,
911
- ),
912
- [hasRowActions, tableColumns],
913
- );
914
- const tableWidth = Math.max(tableContainerWidth, tableMinimumWidth);
915
- const tableLayoutStyle = useMemo(
916
- () => (tableWidth > 0 ? { minWidth: tableWidth, width: tableWidth } : null),
917
- [tableWidth],
271
+ resolvedSort,
272
+ showColumnFiltersButton,
273
+ showRowActions,
274
+ showTotalItemsInCompactToolbar,
275
+ showTotalItemsInFooter,
276
+ storedViewMode,
277
+ tableFiltersVisible,
278
+ sortedDataLength: sortedData.length,
279
+ toolbarActionsLength: Array.isArray(toolbarActions) ? toolbarActions.length : 0,
280
+ }),
281
+ [
282
+ add,
283
+ compactBreakpoint,
284
+ debugFallbackParameters,
285
+ effectiveViewMode,
286
+ filters,
287
+ forceCardsOnCompact,
288
+ initialViewMode,
289
+ resolvedSort,
290
+ showColumnFiltersButton,
291
+ showRowActions,
292
+ showTotalItemsInCompactToolbar,
293
+ showTotalItemsInFooter,
294
+ storedViewMode,
295
+ tableFiltersVisible,
296
+ sortedData.length,
297
+ toolbarActions,
298
+ ],
918
299
  );
919
- const isCompactView = width > 0 && width <= compactBreakpoint;
920
- const shouldForceCardsOnCompact = forceCardsOnCompact !== false;
921
- const effectiveViewMode = isCompactView && shouldForceCardsOnCompact ? 'cards' : viewMode;
922
- const emptyStateLabel = resolvedIsLoading
923
- ? global.t?.t(storeName, 'label', 'loading') || 'Carregando...'
924
- : 'Nenhum registro encontrado';
925
- const storeTotalItems = store?.getters?.totalItems;
926
- const resolvedTotalItems = totalItems !== null && totalItems !== undefined
927
- ? totalItems
928
- : storeTotalItems;
929
- const totalItemsNumber = Number(resolvedTotalItems);
930
- const shouldRenderTotalItems =
931
- resolvedTotalItems !== null &&
932
- resolvedTotalItems !== undefined &&
933
- Number.isFinite(totalItemsNumber);
934
- const shouldRenderFooterTotalItems =
935
- shouldRenderTotalItems &&
936
- showTotalItemsInFooter !== false;
937
- const shouldRenderCompactToolbarTotalItems =
938
- showTotalItemsInCompactToolbar === true &&
939
- isCompactView &&
940
- shouldRenderTotalItems;
941
- const resolvedTotalItemsText =
942
- normalizeText(totalItemsText) !== ''
943
- ? totalItemsText
944
- : shouldRenderTotalItems
945
- ? `${totalItemsNumber} ${totalItemsLabel || global.t?.t(storeName, 'label', 'items') || 'registros'}`
946
- : '';
947
- const storeSummary = store?.getters?.summary;
948
- const resolvedSummary = summary !== null && summary !== undefined ? summary : storeSummary;
949
- const shouldReadSummary = resolvedSummary !== false && isObject(resolvedSummary);
950
- const summaryEntries = useMemo(() => {
951
- if (!shouldReadSummary) return [];
952
-
953
- const labels = summaryLabels || {};
954
- const usedPaths = new Set();
955
- const columnEntries = tableColumns.flatMap(column => {
956
- const operations = getSummaryOperations(column);
957
- if (!operations.length) return [];
958
-
959
- const columnLabel = formatStoreColumnLabel({
960
- columns: columnsForTable,
961
- fieldName: getColumnKey(column),
962
- fallbackLabel: column?.label || getColumnKey(column),
963
- storeName,
964
- });
965
-
966
- return operations.map(operation => {
967
- const fieldName = getSummaryField(column, operation);
968
- const path = [operation, fieldName];
969
- const pathKey = path.join('.');
970
- const value = resolvedSummary?.[operation]?.[fieldName];
971
- if (value === undefined) return null;
972
300
 
973
- usedPaths.add(pathKey);
974
-
975
- return {
976
- key: pathKey,
977
- label: labels[pathKey] || (operations.length > 1 ? `${columnLabel} ${operation}` : columnLabel),
978
- path,
979
- value,
980
- column,
981
- };
982
- }).filter(Boolean);
983
- });
301
+ if (store?.getters) {
302
+ assignGetterValue(store, 'configs', defaultTableConfigs);
303
+ }
984
304
 
985
- const genericEntries = flattenSummaryEntries({
986
- summaryLabels: labels,
987
- usedPaths,
988
- value: resolvedSummary,
989
- });
305
+ useEffect(() => {
306
+ if (storeColumns.length > 0 || !Array.isArray(columns) || columns.length === 0) {
307
+ return;
308
+ }
990
309
 
991
- return [...columnEntries, ...genericEntries].filter(entry =>
992
- entry?.value !== undefined &&
993
- entry?.value !== null &&
994
- normalizeText(entry.value) !== '',
995
- );
996
- }, [columnsForTable, resolvedSummary, shouldReadSummary, storeName, summaryLabels, tableColumns]);
997
- const shouldRenderFooterBar =
998
- (shouldRenderFooterTotalItems && !shouldRenderCompactToolbarTotalItems) ||
999
- summaryEntries.length > 0;
310
+ publishStoreValue(store, 'setColumns', columns, 'columns');
311
+ }, [columns, store, storeColumns.length]);
1000
312
 
1001
- const sortedData = useMemo(() => {
1002
- const items = Array.isArray(resolvedData) ? [...resolvedData] : [];
1003
- const sortField = resolvedSort?.field;
1004
- const sortDirection = resolvedSort?.direction === 'desc' ? 'desc' : 'asc';
1005
- const sortColumn = tableColumns.find(column => getSortField(column) === sortField);
313
+ useEffect(() => {
314
+ if (data === undefined || !Array.isArray(data)) {
315
+ return;
316
+ }
1006
317
 
1007
- if (!sortField || !sortColumn) return items;
318
+ publishStoreValue(store, 'setItems', data, 'items');
319
+ }, [data, store]);
1008
320
 
1009
- return items.sort((left, right) => {
1010
- const leftValue = resolveSortComparable({
1011
- column: sortColumn,
1012
- columns: tableColumns,
1013
- row: left,
1014
- storeName,
1015
- });
1016
- const rightValue = resolveSortComparable({
1017
- column: sortColumn,
1018
- columns: tableColumns,
1019
- row: right,
1020
- storeName,
1021
- });
321
+ useEffect(() => {
322
+ if (!columnsForTable.length) {
323
+ return;
324
+ }
1022
325
 
1023
- const comparison =
1024
- typeof leftValue === 'number' && typeof rightValue === 'number'
1025
- ? leftValue - rightValue
1026
- : normalizeSortText(leftValue).localeCompare(normalizeSortText(rightValue));
326
+ const storedVisibleColumns = resolveStoredVisibleColumnsPreference(tablePreferenceScope);
327
+ if (!storedVisibleColumns) {
328
+ return;
329
+ }
1027
330
 
1028
- return sortDirection === 'desc' ? comparison * -1 : comparison;
331
+ const nextVisibleColumns = sanitizeVisibleColumnsPreference({
332
+ columns: columnsForTable,
333
+ visibleColumns: storedVisibleColumns,
1029
334
  });
1030
- }, [resolvedData, resolvedSort?.direction, resolvedSort?.field, storeName, tableColumns]);
1031
-
1032
- const resolvedHasMore = useMemo(
1033
- () => {
1034
- if (autoMode) {
1035
- if (Number.isFinite(Number(resolvedTotalItems)) && Number(resolvedTotalItems) > 0) {
1036
- return sortedData.length < Number(resolvedTotalItems);
1037
- }
1038
-
1039
- return autoLastPageCount >= pageSizeNumber && sortedData.length > 0;
1040
- }
1041
-
1042
- return resolveHasMore({
1043
- hasMore,
1044
- dataLength: sortedData.length,
1045
- totalItems: resolvedTotalItems,
1046
- });
1047
- },
1048
- [autoLastPageCount, autoMode, hasMore, pageSizeNumber, resolvedTotalItems, sortedData.length],
1049
- );
1050
-
1051
- useEffect(() => {
1052
- const nextPaginationState = {
1053
- dataLength: sortedData.length,
1054
- filtersKey: stableSerialize(resolvedFilters || {}),
1055
- sortDirection: resolvedSort?.direction,
1056
- sortField: resolvedSort?.field,
1057
- };
335
+ const currentVisibleColumns = store?.getters?.visibleColumns || {};
1058
336
 
1059
- const previousPaginationState = previousPaginationStateRef.current;
1060
- const didPaginationStateChange =
1061
- previousPaginationState.dataLength !== nextPaginationState.dataLength ||
1062
- previousPaginationState.filtersKey !== nextPaginationState.filtersKey ||
1063
- previousPaginationState.sortDirection !== nextPaginationState.sortDirection ||
1064
- previousPaginationState.sortField !== nextPaginationState.sortField;
1065
-
1066
- if (didPaginationStateChange) {
1067
- endReachedLockRef.current = false;
1068
- previousPaginationStateRef.current = nextPaginationState;
1069
- }
1070
- }, [resolvedFilters, resolvedSort?.direction, resolvedSort?.field, sortedData]);
1071
-
1072
- useEffect(() => {
1073
- if (typeof onDataLoaded !== 'function') {
1074
- return;
1075
- }
1076
-
1077
- onDataLoaded(sortedData);
1078
- }, [onDataLoaded, sortedData]);
1079
-
1080
- const beginEdit = useCallback((row, column) => {
1081
- if (!isEditableColumn(column)) return;
1082
- setEditingCell(`${row?.id || row?.['@id']}:${getColumnKey(column)}`);
1083
- }, []);
1084
-
1085
- const clearEdit = useCallback(() => {
1086
- setEditingCell(null);
1087
- }, []);
1088
-
1089
- const saveCell = useCallback((row, column, nextValue) => {
1090
- const fieldName = getColumnKey(column);
1091
- if (!fieldName || typeof actions.save !== 'function') {
1092
- clearEdit();
1093
- return Promise.resolve(null);
337
+ if (stableSerialize(currentVisibleColumns) === stableSerialize(nextVisibleColumns)) {
338
+ return;
1094
339
  }
1095
340
 
1096
- const currentValue = resolveEditValue(column, row);
1097
- const normalizedNextValue = nextValue && typeof nextValue === 'object'
1098
- ? normalizeOptionKey(nextValue)
1099
- : normalizeText(nextValue);
341
+ publishStoreValue(store, 'setVisibleColumns', nextVisibleColumns, 'visibleColumns');
342
+ }, [columnsForTable, store, tablePreferenceScope]);
1100
343
 
1101
- if (normalizeText(currentValue) === normalizeText(normalizedNextValue)) {
1102
- clearEdit();
1103
- return Promise.resolve(null);
344
+ useEffect(() => {
345
+ if (!columnsForTable.length || Object.keys(storeFilters).length > 0) {
346
+ return;
1104
347
  }
1105
348
 
1106
- const cellKey = `${row?.id || row?.['@id']}:${fieldName}`;
1107
- const payload = {
1108
- id: normalizeId(row?.['@id'] || row?.id),
1109
- [fieldName]: formatSaveValue(column, nextValue, row),
1110
- };
1111
-
1112
- setSavingCell(cellKey);
1113
-
1114
- return actions.save(payload)
1115
- .then(savedItem => {
1116
- onSaved?.(savedItem || { ...row, [fieldName]: nextValue }, row);
1117
- return savedItem;
1118
- })
1119
- .catch(error => {
1120
- console.error(error);
1121
- return null;
1122
- })
1123
- .finally(() => {
1124
- setSavingCell(null);
1125
- clearEdit();
1126
- });
1127
- }, [actions, clearEdit, onSaved]);
1128
-
1129
- const requestSort = useCallback(column => {
1130
- if (!isSortableColumn(column)) return;
1131
-
1132
- const fieldName = getSortField(column);
1133
- const nextDirection =
1134
- resolvedSort?.field === fieldName && resolvedSort?.direction === 'asc'
1135
- ? 'desc'
1136
- : 'asc';
1137
- const nextSort = {
1138
- direction: nextDirection,
1139
- field: fieldName,
1140
- };
1141
-
1142
- if (visibleColumnsPreferenceKey) {
1143
- persistTableSortPreference(
1144
- visibleColumnsPreferenceKey,
1145
- nextSort,
1146
- );
1147
- }
1148
-
1149
- if (autoMode) {
1150
- setAutoSort(nextSort);
1151
- return;
1152
- }
1153
-
1154
- onSortChange?.(nextSort);
1155
- }, [
1156
- autoMode,
1157
- onSortChange,
1158
- resolvedSort?.direction,
1159
- resolvedSort?.field,
1160
- visibleColumnsPreferenceKey,
1161
- ]);
1162
-
1163
- const openEditModal = useCallback(row => {
1164
- if (typeof onEditRow === 'function') {
1165
- onEditRow(row);
349
+ const storedFilters = resolveStoredTableFiltersPreference(tablePreferenceScope);
350
+ if (!storedFilters) {
1166
351
  return;
1167
352
  }
1168
353
 
1169
- setFormMode('edit');
1170
- setEditingRow(row);
1171
- }, [onEditRow]);
1172
-
1173
- const openAddForm = useCallback(() => {
1174
- if (typeof onAdd === 'function') {
1175
- onAdd();
1176
- return;
1177
- }
1178
-
1179
- if (typeof actions.save !== 'function') return;
1180
-
1181
- setFormMode('create');
1182
- setEditingRow({});
1183
- }, [actions, onAdd]);
1184
-
1185
- const closeEditModal = useCallback(() => {
1186
- setEditingRow(null);
1187
- setFormMode('edit');
1188
- }, []);
1189
-
1190
- const commitFilters = useCallback(nextFilters => {
1191
- const resolvedNextFilters = isObject(nextFilters) ? nextFilters : {};
1192
-
1193
- if (autoMode) {
1194
- setAutoFilters(resolvedNextFilters);
1195
- storeActions.setFilters?.(resolvedNextFilters);
1196
- return;
1197
- }
1198
-
1199
- onFilterChange?.(resolvedNextFilters);
1200
- }, [autoMode, onFilterChange, storeActions]);
1201
-
1202
- const updateFilter = useCallback((fieldName, value) => {
1203
- const nextFilters = { ...(resolvedFilters || {}) };
1204
- const isEmpty =
1205
- value === null ||
1206
- value === undefined ||
1207
- normalizeText(value) === '' ||
1208
- (Array.isArray(value) && value.length === 0);
1209
-
1210
- if (isEmpty) delete nextFilters[fieldName];
1211
- else nextFilters[fieldName] = value;
1212
-
1213
- commitFilters(nextFilters);
1214
- }, [commitFilters, resolvedFilters]);
1215
-
1216
- const toggleColumn = useCallback(column => {
1217
- const fieldName = getColumnKey(column);
1218
- if (!fieldName) return;
1219
-
1220
- setVisibleColumns(prev => {
1221
- const next = {
1222
- ...sanitizeVisibleColumnsPreference({
1223
- columns: columnsForTable,
1224
- visibleColumns: prev,
1225
- }),
1226
- [fieldName]: prev[fieldName] === false,
1227
- };
1228
-
1229
- actions?.setVisibleColumns?.(next);
1230
- if (visibleColumnsPreferenceKey) {
1231
- persistVisibleColumnsPreference(
1232
- visibleColumnsPreferenceKey,
1233
- next,
1234
- );
1235
- }
1236
- return next;
1237
- });
1238
- }, [
1239
- actions,
1240
- columnsForTable,
1241
- visibleColumnsPreferenceKey,
1242
- ]);
1243
-
1244
- const toggleViewMode = useCallback(() => {
1245
- setViewMode(prev => {
1246
- const nextViewMode = prev === 'table' ? 'cards' : 'table';
1247
-
1248
- if (!visibleColumnsPreferenceKey) {
1249
- return nextViewMode;
1250
- }
1251
-
1252
- persistTableViewModePreference(
1253
- visibleColumnsPreferenceKey,
1254
- nextViewMode,
1255
- );
1256
-
1257
- return nextViewMode;
354
+ const nextFilters = sanitizeTableFiltersPreference({
355
+ columns: columnsForTable,
356
+ filters: storedFilters,
1258
357
  });
1259
- }, [
1260
- visibleColumnsPreferenceKey,
1261
- ]);
1262
-
1263
- const handleEndReached = useCallback(() => {
1264
- if (
1265
- !resolvedHasMore ||
1266
- resolvedIsLoading ||
1267
- endReachedLockRef.current === true
1268
- ) {
1269
- return;
1270
- }
1271
-
1272
- endReachedLockRef.current = true;
1273
-
1274
- if (autoMode) {
1275
- const nextPage = autoPageRef.current + 1;
1276
- loadAutoPage(nextPage, { append: true });
1277
- return;
1278
- }
1279
-
1280
- if (typeof onEndReached === 'function') {
1281
- onEndReached();
1282
- }
1283
- }, [autoMode, loadAutoPage, onEndReached, resolvedHasMore, resolvedIsLoading]);
1284
-
1285
- const handleLayout = useCallback(event => {
1286
- const nextWidth = Math.floor(event?.nativeEvent?.layout?.width || 0);
1287
- if (nextWidth > 0) setTableContainerWidth(nextWidth);
1288
- }, []);
1289
-
1290
- const renderEditableCell = (row, column) => {
1291
- const fieldName = getColumnKey(column);
1292
- const cellKey = `${row?.id || row?.['@id']}:${fieldName}`;
1293
- const isEditing = editingCell === cellKey;
1294
- const isSaving = savingCell === cellKey;
1295
- const shouldDelegatePress =
1296
- typeof onRowPress === 'function' &&
1297
- !isEditableColumn(column);
1298
-
1299
- return (
1300
- <View
1301
- style={[getColumnStyle(column), isEditing ? styles.editingCell : null]}
1302
- pointerEvents={shouldDelegatePress ? 'none' : 'auto'}
1303
- >
1304
- <DefaultInput
1305
- accentColor={resolvedAccentColor}
1306
- column={column}
1307
- columns={columnsForTable}
1308
- editing={isEditing}
1309
- getOptionsForColumn={getOptionsForColumn}
1310
- onCancelEditing={clearEdit}
1311
- onSave={value => saveCell(row, column, value)}
1312
- onStartEditing={() => beginEdit(row, column)}
1313
- row={row}
1314
- saving={isSaving}
1315
- storeName={storeName}
1316
- variant="cell"
1317
- />
1318
- </View>
1319
- );
1320
- };
1321
-
1322
- const renderColumnFilter = column => {
1323
- return (
1324
- <DefaultColumnFilter
1325
- accentColor={resolvedAccentColor}
1326
- column={column}
1327
- filters={resolvedFilters}
1328
- getOptionsForColumn={getOptionsForColumn}
1329
- onChange={updateFilter}
1330
- storeName={storeName}
1331
- style={getColumnStyle(column)}
1332
- />
1333
- );
1334
- };
1335
-
1336
- const renderToolbarAction = action => {
1337
- if (!action || action.hidden) return null;
1338
-
1339
- const isActive = action.active === true;
1340
- const hasLabel = normalizeText(action.label) !== '';
1341
- const actionColor = action.color || (isActive ? resolvedAccentColor : tableMutedColor);
1342
358
 
1343
- return (
1344
- <TouchableOpacity
1345
- key={action.key || action.icon || action.label}
1346
- style={[
1347
- styles.toolbarButton,
1348
- isActive
1349
- ? { backgroundColor: withOpacity(resolvedAccentColor, 0.12), borderColor: resolvedAccentColor }
1350
- : null,
1351
- action.style,
1352
- ]}
1353
- activeOpacity={0.82}
1354
- disabled={action.disabled === true}
1355
- onPress={action.onPress}
1356
- >
1357
- {action.icon ? (
1358
- <Icon
1359
- name={action.icon}
1360
- size={action.iconSize || 14}
1361
- color={actionColor}
1362
- />
1363
- ) : null}
1364
- {hasLabel ? (
1365
- <Text style={[styles.toolbarActionLabel, { color: actionColor }, action.labelStyle]} numberOfLines={1}>
1366
- {action.label}
1367
- </Text>
1368
- ) : null}
1369
- {action.badge !== undefined && action.badge !== null ? (
1370
- <Text style={[styles.toolbarBadgeText, { color: action.badgeColor || resolvedAccentColor }]}>
1371
- {action.badge}
1372
- </Text>
1373
- ) : null}
1374
- </TouchableOpacity>
1375
- );
1376
- };
1377
-
1378
- const renderToolbarActions = () =>
1379
- Array.isArray(toolbarActions) && toolbarActions.length > 0 ? (
1380
- <View style={styles.toolbarActionGroup}>{toolbarActions.map(renderToolbarAction)}</View>
1381
- ) : null;
1382
-
1383
- const getColumnByField = useCallback(
1384
- fieldName => columnsForTable.find(column => getColumnKey(column) === fieldName),
1385
- [columnsForTable],
1386
- );
1387
-
1388
- const buildRowHelpers = useCallback(
1389
- row => {
1390
- const openEdit = () => openEditModal(row);
1391
- const openRow = typeof onRowPress === 'function' ? () => onRowPress(row) : null;
1392
- const renderValue = (fieldName, fallback = '-') => {
1393
- const column = getColumnByField(fieldName);
1394
- if (!column) return fallback;
1395
- return resolveCellText({ column, columns: columnsForTable, row, storeName });
1396
- };
1397
- const renderField = (fieldName, options = {}) => {
1398
- const column = getColumnByField(fieldName);
1399
- if (!column) return null;
1400
-
1401
- const cellKey = `${row?.id || row?.['@id']}:${fieldName}`;
1402
- const isEditing = editingCell === cellKey;
1403
- const isSaving = savingCell === cellKey;
1404
-
1405
- return (
1406
- <DefaultInput
1407
- accentColor={options.accentColor || resolvedAccentColor}
1408
- column={column}
1409
- columns={columnsForTable}
1410
- containerStyle={options.containerStyle}
1411
- displayValue={options.displayValue}
1412
- editing={isEditing}
1413
- getOptionsForColumn={getOptionsForColumn}
1414
- inputStyle={options.inputStyle}
1415
- label={options.label}
1416
- numberOfLines={options.numberOfLines}
1417
- onCancelEditing={clearEdit}
1418
- onSave={value => saveCell(row, column, value)}
1419
- onStartEditing={() => beginEdit(row, column)}
1420
- readTextStyle={options.readTextStyle || options.textStyle}
1421
- row={row}
1422
- saving={isSaving}
1423
- showLabel={options.showLabel}
1424
- storeName={storeName}
1425
- variant={options.variant || 'card'}
1426
- />
1427
- );
1428
- };
1429
-
1430
- return {
1431
- openEdit,
1432
- openRow,
1433
- renderField,
1434
- renderValue,
1435
- };
1436
- },
1437
- [
1438
- resolvedAccentColor,
1439
- beginEdit,
1440
- clearEdit,
1441
- columnsForTable,
1442
- editingCell,
1443
- getColumnByField,
1444
- getOptionsForColumn,
1445
- openEditModal,
1446
- onRowPress,
1447
- saveCell,
1448
- savingCell,
1449
- storeName,
1450
- ],
1451
- );
1452
-
1453
- const renderCardItem = row => {
1454
- const helpers = buildRowHelpers(row);
1455
-
1456
- if (typeof renderCard === 'function') {
1457
- return (
1458
- <View key={row?.['@id'] || row?.id} style={styles.cardItem}>
1459
- {renderCard({
1460
- item: row,
1461
- openEdit: helpers.openEdit,
1462
- openRow: helpers.openRow,
1463
- renderField: helpers.renderField,
1464
- renderValue: helpers.renderValue,
1465
- row,
1466
- })}
1467
- {hasRowActions ? (
1468
- <View style={styles.cardActions}>
1469
- <TouchableOpacity
1470
- style={[styles.iconButton, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}
1471
- activeOpacity={0.82}
1472
- onPress={helpers.openEdit}>
1473
- <Icon name="edit-2" size={14} color={tableMutedColor} />
1474
- </TouchableOpacity>
1475
- </View>
1476
- ) : null}
1477
- </View>
1478
- );
359
+ if (Object.keys(nextFilters).length === 0) {
360
+ return;
1479
361
  }
1480
362
 
1481
- return (
1482
- <View key={row?.['@id'] || row?.id} style={[styles.defaultCard, { backgroundColor: tableSurfaceColor, borderColor: tableBorderColor }]}>
1483
- {tableColumns.map(column => (
1484
- <View key={getColumnKey(column)} style={styles.defaultCardLine}>
1485
- <Text style={[styles.defaultCardLabel, { color: tableMutedColor }]}>
1486
- {formatStoreColumnLabel({
1487
- columns: columnsForTable,
1488
- fieldName: getColumnKey(column),
1489
- fallbackLabel: column?.label || getColumnKey(column),
1490
- storeName,
1491
- })}
1492
- </Text>
1493
- {helpers.renderField(getColumnKey(column), {
1494
- readTextStyle: [styles.defaultCardValue, { color: tableTextColor }],
1495
- numberOfLines: 1,
1496
- })}
1497
- </View>
1498
- ))}
1499
- {hasRowActions ? (
1500
- <TouchableOpacity
1501
- style={[styles.cardEditButton, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}
1502
- activeOpacity={0.82}
1503
- onPress={helpers.openEdit}>
1504
- <Icon name="edit-2" size={14} color={tableMutedColor} />
1505
- </TouchableOpacity>
1506
- ) : null}
1507
- </View>
1508
- );
1509
- };
1510
-
1511
- const renderEmptyState = isTable => {
1512
- return (
1513
- <View style={[styles.emptyBox, isTable ? tableLayoutStyle : null]}>
1514
- {resolvedIsLoading ? (
1515
- <StateStore compact loading={emptyStateLabel} />
1516
- ) : (
1517
- <Text style={[styles.emptyText, { color: tableMutedColor }]}>{emptyStateLabel}</Text>
1518
- )}
1519
- </View>
1520
- );
1521
- };
1522
-
1523
- const renderLoadingOverlay = () => {
1524
- if (!resolvedIsLoading || sortedData.length === 0) {
1525
- return null;
1526
- }
1527
-
1528
- return (
1529
- <View pointerEvents="none" style={styles.loadingOverlay}>
1530
- <View
1531
- style={[
1532
- styles.loadingOverlayCard,
1533
- { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor },
1534
- ]}
1535
- >
1536
- <StateStore compact loading="Carregando mais registros..." />
1537
- </View>
1538
- </View>
1539
- );
1540
- };
1541
-
1542
- const renderTableItem = ({ item: row, index }) => {
1543
- const hasRowPress = typeof onRowPress === 'function';
1544
- const RowComponent = hasRowPress ? TouchableOpacity : View;
1545
- const rowPressProps = hasRowPress
1546
- ? {
1547
- activeOpacity: 0.84,
1548
- onPress: () => onRowPress(row),
1549
- }
1550
- : {};
1551
- const rowBackgroundColor = index % 2 === 0 ? tableOddColor : tableEvenColor;
1552
-
1553
- return (
1554
- <RowComponent
1555
- key={getRowKey(row)}
1556
- style={[styles.row, tableLayoutStyle, { backgroundColor: rowBackgroundColor, borderBottomColor: tableBorderColor }]}
1557
- {...rowPressProps}
1558
- >
1559
- {tableColumns.map(column => (
1560
- <React.Fragment key={getColumnKey(column)}>
1561
- {renderEditableCell(row, column)}
1562
- </React.Fragment>
1563
- ))}
1564
- {hasRowActions ? (
1565
- <View style={[styles.cell, styles.actionsCell]}>
1566
- <TouchableOpacity
1567
- style={[styles.iconButton, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}
1568
- activeOpacity={0.82}
1569
- onPress={() => openEditModal(row)}>
1570
- <Icon name="edit-2" size={14} color={tableMutedColor} />
1571
- </TouchableOpacity>
1572
- </View>
1573
- ) : null}
1574
- </RowComponent>
1575
- );
1576
- };
1577
-
1578
- const renderEditModal = () => {
1579
- const isCreate = formMode === 'create';
1580
-
1581
- return (
1582
- <Modal visible={Boolean(editingRow)} transparent animationType="fade" onRequestClose={closeEditModal}>
1583
- <View style={[styles.modalOverlay, { backgroundColor: withOpacity(tableTextColor, 0.42) }]}>
1584
- <View style={[styles.modalCard, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}>
1585
- <View style={[styles.modalHeader, { borderBottomColor: tableBorderColor }]}>
1586
- <Text style={[styles.modalTitle, { color: tableTextColor }]}>
1587
- {isCreate
1588
- ? global.t?.t(storeName, 'button', 'add') || 'Adicionar'
1589
- : global.t?.t(storeName, 'button', 'edit') || 'Editar'}
1590
- </Text>
1591
- <TouchableOpacity
1592
- style={[styles.modalCloseButton, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}
1593
- onPress={closeEditModal}>
1594
- <Icon name="x" size={18} color={tableMutedColor} />
1595
- </TouchableOpacity>
1596
- </View>
1597
-
1598
- <DefaultForm
1599
- accentColor={resolvedAccentColor}
1600
- actions={actions}
1601
- columns={isCreate ? columnsForTable : editableColumns}
1602
- getOptionsForColumn={getOptionsForColumn}
1603
- mode={formMode}
1604
- onCancel={closeEditModal}
1605
- onSaved={(savedItem, originalRow) => {
1606
- onSaved?.(savedItem, originalRow);
1607
- closeEditModal();
1608
- }}
1609
- row={editingRow || {}}
1610
- storeName={storeName}
1611
- />
1612
- </View>
1613
- </View>
1614
- </Modal>
1615
- );
1616
- };
1617
-
1618
- const renderColumnMenuModal = () => {
1619
- if (!isColumnMenuOpen) return null;
1620
-
1621
- return (
1622
- <Modal visible transparent animationType="fade" onRequestClose={() => setIsColumnMenuOpen(false)}>
1623
- <View style={[styles.modalOverlay, { backgroundColor: withOpacity(tableTextColor, 0.42) }]}>
1624
- <View style={[styles.modalCard, styles.columnMenuModalCard, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}>
1625
- <View style={[styles.modalHeader, { borderBottomColor: tableBorderColor }]}>
1626
- <Text style={[styles.modalTitle, { color: tableTextColor }]} numberOfLines={1}>
1627
- Colunas
1628
- </Text>
1629
- <TouchableOpacity
1630
- style={[styles.modalCloseButton, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}
1631
- activeOpacity={0.82}
1632
- onPress={() => setIsColumnMenuOpen(false)}
1633
- >
1634
- <Icon name="x" size={16} color={tableMutedColor} />
1635
- </TouchableOpacity>
1636
- </View>
1637
- <ScrollView style={styles.columnMenuModalBody} contentContainerStyle={styles.columnMenuModalList}>
1638
- {availableColumns.map(column => {
1639
- const fieldName = getColumnKey(column);
1640
- const label = formatStoreColumnLabel({
1641
- columns: columnsForTable,
1642
- fieldName,
1643
- fallbackLabel: column?.label || fieldName,
1644
- storeName,
1645
- });
1646
- const checked = visibleColumns[fieldName] !== false;
1647
-
1648
- return (
1649
- <TouchableOpacity key={fieldName} style={styles.columnMenuItem} activeOpacity={0.82} onPress={() => toggleColumn(column)}>
1650
- <Icon name={checked ? 'check-square' : 'square'} size={16} color={checked ? resolvedAccentColor : tableMutedColor} />
1651
- <Text style={[styles.columnMenuText, { color: tableTextColor }]} numberOfLines={1}>{label}</Text>
1652
- </TouchableOpacity>
1653
- );
1654
- })}
1655
- </ScrollView>
1656
- </View>
1657
- </View>
1658
- </Modal>
1659
- );
1660
- };
1661
-
1662
- return (
1663
- <View style={[styles.wrap, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]} onLayout={handleLayout}>
1664
- <View style={[styles.toolbar, { borderBottomColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}>
1665
- {shouldRenderCompactToolbarTotalItems ? (
1666
- <View style={styles.toolbarCompactLead}>
1667
- <View style={[styles.toolbarCountPill, { backgroundColor: withOpacity(resolvedAccentColor, 0.12) }]}>
1668
- <Text
1669
- style={[styles.toolbarCountText, { color: resolvedAccentColor }]}
1670
- numberOfLines={1}
1671
- adjustsFontSizeToFit
1672
- minimumFontScale={0.82}
1673
- >
1674
- {resolvedTotalItemsText}
1675
- </Text>
1676
- </View>
1677
- {searchProps ? (
1678
- <DefaultSearch
1679
- accentColor={resolvedAccentColor}
1680
- compact
1681
- storeName={storeName}
1682
- {...searchProps}
1683
- filters={autoMode ? resolvedFilters : searchProps?.filters}
1684
- onChangeFilters={autoMode ? commitFilters : searchProps?.onChangeFilters}
1685
- value={autoMode ? resolvedSearchValue : searchProps?.value}
1686
- style={[styles.toolbarSearch, styles.toolbarCompactSearch, searchProps?.style]}
1687
- />
1688
- ) : null}
1689
- {renderToolbarActions()}
1690
- </View>
1691
- ) : null}
1692
-
1693
- <View style={shouldRenderCompactToolbarTotalItems ? styles.toolbarCompactActions : styles.toolbarLeft}>
1694
- {!shouldRenderCompactToolbarTotalItems && searchProps ? (
1695
- <DefaultSearch
1696
- accentColor={resolvedAccentColor}
1697
- compact
1698
- storeName={storeName}
1699
- {...searchProps}
1700
- filters={autoMode ? resolvedFilters : searchProps?.filters}
1701
- onChangeFilters={autoMode ? commitFilters : searchProps?.onChangeFilters}
1702
- value={autoMode ? resolvedSearchValue : searchProps?.value}
1703
- style={[styles.toolbarSearch, searchProps?.style]}
1704
- />
1705
- ) : null}
1706
- {!shouldRenderCompactToolbarTotalItems ? renderToolbarActions() : null}
1707
- {showColumnFiltersButton ? (
1708
- <TouchableOpacity
1709
- style={[
1710
- styles.toolbarButton,
1711
- { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor },
1712
- showColumnFilters ? { backgroundColor: withOpacity(resolvedAccentColor, 0.12), borderColor: resolvedAccentColor } : null,
1713
- ]}
1714
- activeOpacity={0.82}
1715
- onPress={() => setShowColumnFilters(prev => !prev)}
1716
- >
1717
- <Icon name="filter" size={14} color={showColumnFilters ? resolvedAccentColor : tableMutedColor} />
1718
- {activeFilterCount > 0 ? (
1719
- <Text style={[styles.toolbarBadgeText, { color: resolvedAccentColor }]}>{activeFilterCount}</Text>
1720
- ) : null}
1721
- </TouchableOpacity>
1722
- ) : null}
1723
- {(!isCompactView || !shouldForceCardsOnCompact) ? (
1724
- <TouchableOpacity
1725
- style={[styles.toolbarButton, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}
1726
- activeOpacity={0.82}
1727
- onPress={toggleViewMode}
1728
- >
1729
- <Icon name={viewMode === 'table' ? 'grid' : 'list'} size={14} color={tableMutedColor} />
1730
- </TouchableOpacity>
1731
- ) : null}
1732
- <TouchableOpacity
1733
- style={[styles.toolbarButton, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}
1734
- activeOpacity={0.82}
1735
- onPress={() => setIsColumnMenuOpen(prev => !prev)}>
1736
- <Icon name="columns" size={14} color={tableMutedColor} />
1737
- </TouchableOpacity>
1738
- {shouldRenderAddButton ? (
1739
- <TouchableOpacity
1740
- style={[
1741
- styles.toolbarButton,
1742
- styles.toolbarAddButton,
1743
- { backgroundColor: resolvedAccentColor, borderColor: resolvedAccentColor },
1744
- ]}
1745
- activeOpacity={0.85}
1746
- onPress={openAddForm}
1747
- >
1748
- <Icon name="plus" size={16} color={tableOnAccentColor} />
1749
- </TouchableOpacity>
1750
- ) : null}
1751
- </View>
363
+ publishStoreValue(store, 'setFilters', nextFilters, 'filters');
364
+ }, [columnsForTable, store, storeFilters, tablePreferenceScope]);
1752
365
 
1753
- </View>
1754
-
1755
- {effectiveViewMode === 'cards' ? (
1756
- <FlatList
1757
- data={sortedData}
1758
- keyExtractor={getRowKey}
1759
- renderItem={({ item }) => renderCardItem(item)}
1760
- style={styles.cardsScroll}
1761
- contentContainerStyle={styles.cardsGrid}
1762
- ListEmptyComponent={renderEmptyState(false)}
1763
- ListFooterComponent={null}
1764
- nestedScrollEnabled
1765
- onMomentumScrollBegin={onMomentumScrollBegin || undefined}
1766
- onScrollBeginDrag={onScrollBeginDrag || undefined}
1767
- onEndReached={handleEndReached}
1768
- onEndReachedThreshold={END_REACHED_THRESHOLD}
1769
- showsVerticalScrollIndicator={false}
1770
- />
1771
- ) : (
1772
- <ScrollView horizontal style={styles.scroll}>
1773
- <View style={[styles.content, tableLayoutStyle]}>
1774
- <View style={[styles.headerRow, tableLayoutStyle, { backgroundColor: tableHeaderColor, borderBottomColor: tableBorderColor }]}>
1775
- {tableColumns.map(column => {
1776
- const fieldName = getColumnKey(column);
1777
- const label = formatStoreColumnLabel({
1778
- columns: columnsForTable,
1779
- fieldName,
1780
- fallbackLabel: column?.label || fieldName,
1781
- storeName,
1782
- });
1783
-
1784
- const sortFieldName = getSortField(column);
1785
-
1786
- return (
1787
- <TouchableOpacity
1788
- key={fieldName}
1789
- style={getColumnStyle(column)}
1790
- activeOpacity={isSortableColumn(column) ? 0.8 : 1}
1791
- onPress={() => requestSort(column)}
1792
- >
1793
- <View style={styles.sortableHeader}>
1794
- <Text style={[styles.headerText, { color: tableTextColor }]} numberOfLines={1}>{label}</Text>
1795
- {isSortableColumn(column) && resolvedSort?.field === sortFieldName ? (
1796
- <Icon name={resolvedSort?.direction === 'desc' ? 'chevron-down' : 'chevron-up'} size={12} color={tableTextColor} />
1797
- ) : isSortableColumn(column) ? (
1798
- <Icon name="chevrons-up" size={12} color={tableBorderColor} />
1799
- ) : null}
1800
- {resolvedFilters?.[fieldName] ? <Icon name="filter" size={11} color={tableTextColor} /> : null}
1801
- </View>
1802
- </TouchableOpacity>
1803
- );
1804
- })}
1805
- {hasRowActions ? (
1806
- <View style={[styles.cell, styles.actionsCell]}>
1807
- <Text style={[styles.headerText, { color: tableTextColor }]}>Acoes</Text>
1808
- </View>
1809
- ) : null}
1810
- </View>
1811
-
1812
- {showColumnFiltersButton && showColumnFilters ? (
1813
- <View style={[styles.filterRow, tableLayoutStyle, { backgroundColor: tableSurfaceColor, borderBottomColor: tableBorderColor }]}>
1814
- {tableColumns.map(column => (
1815
- <React.Fragment key={getColumnKey(column)}>
1816
- {renderColumnFilter(column)}
1817
- </React.Fragment>
1818
- ))}
1819
- {hasRowActions ? <View style={[styles.cell, styles.actionsCell]} /> : null}
1820
- </View>
1821
- ) : null}
1822
-
1823
- <FlatList
1824
- data={sortedData}
1825
- keyExtractor={getRowKey}
1826
- renderItem={renderTableItem}
1827
- style={styles.tableList}
1828
- contentContainerStyle={styles.tableListContent}
1829
- ListEmptyComponent={renderEmptyState(true)}
1830
- ListFooterComponent={null}
1831
- nestedScrollEnabled
1832
- onMomentumScrollBegin={onMomentumScrollBegin || undefined}
1833
- onScrollBeginDrag={onScrollBeginDrag || undefined}
1834
- onEndReached={handleEndReached}
1835
- onEndReachedThreshold={END_REACHED_THRESHOLD}
1836
- showsVerticalScrollIndicator={false}
1837
- />
1838
- </View>
1839
- </ScrollView>
1840
- )}
1841
-
1842
- {renderLoadingOverlay()}
1843
-
1844
- {shouldRenderFooterBar ? (
1845
- <View style={[styles.footerBar, { backgroundColor: tableSurfaceColor, borderTopColor: tableBorderColor }]}>
1846
- {summaryEntries.length > 0 ? (
1847
- <View style={styles.footerSummaryList}>
1848
- {summaryEntries.map(entry => (
1849
- <View key={entry.key} style={styles.footerSummaryItem}>
1850
- <Text style={[styles.footerSummaryLabel, { color: tableMutedColor }]} numberOfLines={1}>
1851
- {entry.label}
1852
- </Text>
1853
- <Text
1854
- style={[
1855
- styles.footerSummaryValue,
1856
- entry.path?.[0] === 'sum' || isMoneySummaryPath(entry.path)
1857
- ? { color: resolvedAccentColor }
1858
- : { color: tableTextColor },
1859
- ]}
1860
- numberOfLines={1}
1861
- adjustsFontSizeToFit
1862
- minimumFontScale={0.78}
1863
- >
1864
- {formatSummaryValue({
1865
- column: entry.column,
1866
- columns: columnsForTable,
1867
- path: entry.path,
1868
- storeName,
1869
- value: entry.value,
1870
- })}
1871
- </Text>
1872
- </View>
1873
- ))}
1874
- </View>
1875
- ) : null}
1876
- {shouldRenderFooterTotalItems && !shouldRenderCompactToolbarTotalItems ? (
1877
- <View style={[styles.footerCountPill, { backgroundColor: withOpacity(resolvedAccentColor, 0.12) }]}>
1878
- <Text
1879
- style={[styles.footerCountText, { color: resolvedAccentColor }]}
1880
- numberOfLines={1}
1881
- adjustsFontSizeToFit
1882
- minimumFontScale={0.82}
1883
- >
1884
- {resolvedTotalItemsText}
1885
- </Text>
1886
- </View>
1887
- ) : null}
1888
- </View>
1889
- ) : null}
366
+ useEffect(() => {
367
+ if (configsSignatureRef.current === defaultTableConfigsSignature) {
368
+ return;
369
+ }
1890
370
 
1891
- {renderColumnMenuModal()}
1892
- {renderEditModal()}
371
+ configsSignatureRef.current = defaultTableConfigsSignature;
372
+ publishStoreValue(store, 'setConfigs', defaultTableConfigs, 'configs');
373
+ }, [defaultTableConfigs, defaultTableConfigsSignature, store]);
374
+
375
+ useEffect(() => {
376
+ onDataLoaded?.(sortedData);
377
+ }, [onDataLoaded, sortedData]);
378
+
379
+ return (
380
+ <View
381
+ style={[
382
+ styles.wrap,
383
+ {
384
+ borderColor: themeColors.panelBorder,
385
+ backgroundColor: themeColors.panelBackground,
386
+ },
387
+ ]}
388
+ >
389
+ <DefaultTableToolbar storeName={storeName} />
390
+ <DefaultTableBody storeName={storeName} />
391
+ <DefaultTableFooter storeName={storeName} />
1893
392
  </View>
1894
393
  );
1895
394
  };