@controleonline/ui-default 1.0.263
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.
- package/.github/agents/developer.agent.md +30 -0
- package/.github/agents/devops.agent.md +30 -0
- package/.github/agents/qa.agent.md +30 -0
- package/.github/agents/security.agent.md +30 -0
- package/.scrutinizer.yml +20 -0
- package/AGENTS.md +47 -0
- package/FUNDING.yml +1 -0
- package/package.json +21 -0
- package/src/react/components/errors/DefaultErrors.js +360 -0
- package/src/react/components/files/DefaultFile.js +46 -0
- package/src/react/components/filters/CompactFilterSelector.js +262 -0
- package/src/react/components/filters/CompactFilterSelector.styles.js +124 -0
- package/src/react/components/filters/DateShortcutFilter.js +264 -0
- package/src/react/components/filters/DateShortcutFilter.styles.js +82 -0
- package/src/react/components/filters/DefaultColumnFilter.js +97 -0
- package/src/react/components/filters/DefaultColumnFilter.styles.js +21 -0
- package/src/react/components/filters/DefaultExternalFilters.js +441 -0
- package/src/react/components/filters/DefaultExternalFilters.styles.js +177 -0
- package/src/react/components/filters/DefaultSearch.js +103 -0
- package/src/react/components/filters/DefaultSearch.styles.js +70 -0
- package/src/react/components/filters/dateFilterSelection.js +29 -0
- package/src/react/components/form/DefaultForm.js +198 -0
- package/src/react/components/form/DefaultForm.styles.js +70 -0
- package/src/react/components/help/DefaultTooltip.js +87 -0
- package/src/react/components/help/DefaultTooltip.styles.js +61 -0
- package/src/react/components/inputs/DefaultInput.js +160 -0
- package/src/react/components/inputs/DefaultInput.styles.js +93 -0
- package/src/react/components/inputs/DefaultSelect.js +192 -0
- package/src/react/components/inputs/DefaultSelect.styles.js +65 -0
- package/src/react/components/inputs/defaultInputUtils.js +230 -0
- package/src/react/components/map/DefaultGoogleMap.styles.js +9 -0
- package/src/react/components/map/DefaultGoogleMap.web.js +698 -0
- package/src/react/components/map/DefaultMap.native.js +71 -0
- package/src/react/components/map/DefaultMap.shared.js +762 -0
- package/src/react/components/map/DefaultMap.styles.js +16 -0
- package/src/react/components/map/DefaultMap.web.js +66 -0
- package/src/react/components/map/DefaultNativeMap.native.js +615 -0
- package/src/react/components/map/DefaultNativeMap.shared.js +532 -0
- package/src/react/components/map/DefaultNativeMap.styles.js +122 -0
- package/src/react/components/table/DefaultTable.js +1897 -0
- package/src/react/components/table/DefaultTable.styles.js +610 -0
- package/src/react/index.js +10 -0
- package/src/react/utils/tableVisibleColumnsPreferences.js +264 -0
- package/src/store/default/actions.js +444 -0
- package/src/store/default/getters.js +28 -0
- package/src/store/default/mutation_types.js +26 -0
- package/src/store/default/mutations.js +138 -0
- package/src/tests/react/components/DateShortcutFilter.test.js +96 -0
- package/src/tests/react/components/errors/DefaultErrors.test.js +162 -0
- package/src/tests/react/components/files/DefaultFile.test.js +80 -0
- package/src/tests/react/components/map/DefaultMap.shared.test.js +162 -0
- package/src/tests/react/filters/dateFilterSelection.test.js +46 -0
- package/src/tests/react/inputs/defaultInputUtils.test.js +45 -0
- package/src/tests/react/store/defaultActions.test.js +112 -0
- package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +223 -0
- package/src/utils/filters.js +56 -0
|
@@ -0,0 +1,1897 @@
|
|
|
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';
|
|
25
|
+
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';
|
|
45
|
+
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);
|
|
215
|
+
}
|
|
216
|
+
|
|
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;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return getColumnKey(column);
|
|
227
|
+
};
|
|
228
|
+
|
|
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
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return normalizeText(value);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const flattenSummaryEntries = ({
|
|
251
|
+
path = [],
|
|
252
|
+
summaryLabels = {},
|
|
253
|
+
usedPaths,
|
|
254
|
+
value,
|
|
255
|
+
}) => {
|
|
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,
|
|
434
|
+
storeName,
|
|
435
|
+
}));
|
|
436
|
+
|
|
437
|
+
const normalizedNumber = Number(
|
|
438
|
+
String(resolvedValue).replace(/[^0-9,.-]/g, '').replace(',', '.'),
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
if (Number.isFinite(normalizedNumber) && String(resolvedValue).match(/[0-9]/)) {
|
|
442
|
+
return normalizedNumber;
|
|
443
|
+
}
|
|
444
|
+
|
|
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];
|
|
453
|
+
}
|
|
454
|
+
return styles.cell;
|
|
455
|
+
};
|
|
456
|
+
|
|
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
|
+
};
|
|
465
|
+
|
|
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(
|
|
541
|
+
() =>
|
|
542
|
+
resolveStoredTableViewModePreference(
|
|
543
|
+
visibleColumnsPreferenceKey,
|
|
544
|
+
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],
|
|
918
|
+
);
|
|
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
|
+
|
|
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
|
+
});
|
|
984
|
+
|
|
985
|
+
const genericEntries = flattenSummaryEntries({
|
|
986
|
+
summaryLabels: labels,
|
|
987
|
+
usedPaths,
|
|
988
|
+
value: resolvedSummary,
|
|
989
|
+
});
|
|
990
|
+
|
|
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;
|
|
1000
|
+
|
|
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);
|
|
1006
|
+
|
|
1007
|
+
if (!sortField || !sortColumn) return items;
|
|
1008
|
+
|
|
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
|
+
});
|
|
1022
|
+
|
|
1023
|
+
const comparison =
|
|
1024
|
+
typeof leftValue === 'number' && typeof rightValue === 'number'
|
|
1025
|
+
? leftValue - rightValue
|
|
1026
|
+
: normalizeSortText(leftValue).localeCompare(normalizeSortText(rightValue));
|
|
1027
|
+
|
|
1028
|
+
return sortDirection === 'desc' ? comparison * -1 : comparison;
|
|
1029
|
+
});
|
|
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
|
+
};
|
|
1058
|
+
|
|
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);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
const currentValue = resolveEditValue(column, row);
|
|
1097
|
+
const normalizedNextValue = nextValue && typeof nextValue === 'object'
|
|
1098
|
+
? normalizeOptionKey(nextValue)
|
|
1099
|
+
: normalizeText(nextValue);
|
|
1100
|
+
|
|
1101
|
+
if (normalizeText(currentValue) === normalizeText(normalizedNextValue)) {
|
|
1102
|
+
clearEdit();
|
|
1103
|
+
return Promise.resolve(null);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
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);
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
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;
|
|
1258
|
+
});
|
|
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
|
+
|
|
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
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
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>
|
|
1752
|
+
|
|
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}
|
|
1890
|
+
|
|
1891
|
+
{renderColumnMenuModal()}
|
|
1892
|
+
{renderEditModal()}
|
|
1893
|
+
</View>
|
|
1894
|
+
);
|
|
1895
|
+
};
|
|
1896
|
+
|
|
1897
|
+
export default DefaultTable;
|