@controleonline/ui-default 1.0.275 → 1.0.277
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/package.json +1 -1
- package/src/react/components/table/DefaultTable.dataUtils.js +323 -0
- package/src/react/components/table/DefaultTable.js +2 -2
- package/src/react/components/table/DefaultTable.layoutUtils.js +253 -0
- package/src/react/components/table/DefaultTable.shadow.js +11 -0
- package/src/react/components/table/DefaultTable.styleGroup1.js +335 -0
- package/src/react/components/table/DefaultTable.styleGroup2.js +347 -0
- package/src/react/components/table/DefaultTable.styleGroup3.js +108 -0
- package/src/react/components/table/DefaultTable.styles.js +5 -796
- package/src/react/components/table/DefaultTable.utils.js +2 -566
- package/src/react/components/table/DefaultTableCards.js +1 -0
- package/src/react/components/table/DefaultTableRowActions.js +1 -1
- package/src/react/components/table/useDefaultTablePagination.js +3 -2
- package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +39 -3
package/package.json
CHANGED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import Formatter from '@controleonline/ui-common/src/utils/formatter.js';
|
|
2
|
+
import { getDateRange } from '@controleonline/ui-common/src/react/utils/dateRangeFilter';
|
|
3
|
+
import {
|
|
4
|
+
getColumnKey,
|
|
5
|
+
isDateLikeColumn,
|
|
6
|
+
normalizeText,
|
|
7
|
+
resolveCellText,
|
|
8
|
+
resolveStoreNameFromList,
|
|
9
|
+
} from '../inputs/defaultInputUtils';
|
|
10
|
+
import styles from './DefaultTable.styles';
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_CELL_MIN_WIDTH = 118;
|
|
13
|
+
export const DEFAULT_COMPACT_BREAKPOINT = 768;
|
|
14
|
+
export const COLLAPSED_SEARCH_MAX_VIEWPORT_WIDTH = 400;
|
|
15
|
+
export const COLLAPSED_SEARCH_MAX_CONTAINER_WIDTH = 350;
|
|
16
|
+
export const END_REACHED_THRESHOLD = 0.75;
|
|
17
|
+
export const IDENTITY_CELL_MIN_WIDTH = 76;
|
|
18
|
+
export const MONEY_CELL_MIN_WIDTH = 132;
|
|
19
|
+
export const ACTIONS_CELL_WIDTH = 60;
|
|
20
|
+
|
|
21
|
+
const SUMMARY_OPERATIONS = ['sum', 'count', 'avg', 'min', 'max'];
|
|
22
|
+
const COMPANY_SCOPED_LIST_STORES = new Set([
|
|
23
|
+
'categories',
|
|
24
|
+
'paymentType',
|
|
25
|
+
'wallet',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
export const shouldIncludeColumn = column =>
|
|
29
|
+
Boolean(getColumnKey(column)) &&
|
|
30
|
+
column?.show !== false &&
|
|
31
|
+
column?.visible !== false &&
|
|
32
|
+
column?.table !== false;
|
|
33
|
+
|
|
34
|
+
export const resolveListActionName = list =>
|
|
35
|
+
normalizeText(list).split('/')[1] || 'getItems';
|
|
36
|
+
|
|
37
|
+
const resolveListLoadParams = ({ currentCompanyId, listStoreName }) => {
|
|
38
|
+
if (!currentCompanyId || !COMPANY_SCOPED_LIST_STORES.has(listStoreName)) {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return listStoreName === 'categories'
|
|
43
|
+
? { company: currentCompanyId }
|
|
44
|
+
: { people: currentCompanyId };
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const resolveColumnListLoadParams = ({
|
|
48
|
+
column,
|
|
49
|
+
currentCompanyId,
|
|
50
|
+
requestParams = {},
|
|
51
|
+
searchValue = '',
|
|
52
|
+
}) => {
|
|
53
|
+
const listStoreName = resolveStoreNameFromList(column?.list);
|
|
54
|
+
const companyScopedParams = resolveListLoadParams({
|
|
55
|
+
currentCompanyId,
|
|
56
|
+
listStoreName,
|
|
57
|
+
});
|
|
58
|
+
const resolvedCustomParams = typeof column?.listRequestParams === 'function'
|
|
59
|
+
? column.listRequestParams({ currentCompanyId, requestParams })
|
|
60
|
+
: column?.listRequestParams;
|
|
61
|
+
const customParams =
|
|
62
|
+
resolvedCustomParams &&
|
|
63
|
+
typeof resolvedCustomParams === 'object' &&
|
|
64
|
+
!Array.isArray(resolvedCustomParams)
|
|
65
|
+
? resolvedCustomParams
|
|
66
|
+
: {};
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
...companyScopedParams,
|
|
70
|
+
...customParams,
|
|
71
|
+
...(normalizeText(searchValue)
|
|
72
|
+
? { [column?.listSearchParam || column?.searchParam || 'search']: normalizeText(searchValue) }
|
|
73
|
+
: {}),
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const isObject = value =>
|
|
78
|
+
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
79
|
+
|
|
80
|
+
export const getRowKey = (row, index = 0) =>
|
|
81
|
+
String(row?.['@id'] || row?.id || index);
|
|
82
|
+
|
|
83
|
+
export const shouldTriggerEndReachedFromScroll = event => {
|
|
84
|
+
const nativeEvent = event?.nativeEvent || {};
|
|
85
|
+
const viewportHeight = Number(nativeEvent.layoutMeasurement?.height);
|
|
86
|
+
const contentOffsetY = Number(nativeEvent.contentOffset?.y);
|
|
87
|
+
const contentHeight = Number(nativeEvent.contentSize?.height);
|
|
88
|
+
|
|
89
|
+
if (
|
|
90
|
+
!Number.isFinite(viewportHeight) ||
|
|
91
|
+
!Number.isFinite(contentOffsetY) ||
|
|
92
|
+
!Number.isFinite(contentHeight) ||
|
|
93
|
+
viewportHeight <= 0 ||
|
|
94
|
+
contentHeight <= viewportHeight
|
|
95
|
+
) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const distanceFromEnd = contentHeight - (contentOffsetY + viewportHeight);
|
|
100
|
+
|
|
101
|
+
return distanceFromEnd <= viewportHeight * END_REACHED_THRESHOLD;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const normalizeRowIdentity = row => {
|
|
105
|
+
const rawId = row?.['@id'] || row?.id;
|
|
106
|
+
if (rawId === null || rawId === undefined || rawId === '') {
|
|
107
|
+
return '';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return normalizeText(rawId).replace(/\D+/g, '');
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const mergeSortedDataWithLiveItems = ({ liveItems = [], sortedData = [] }) => {
|
|
114
|
+
const resolvedSortedData = Array.isArray(sortedData) ? sortedData : [];
|
|
115
|
+
const resolvedLiveItems = Array.isArray(liveItems) ? liveItems : [];
|
|
116
|
+
|
|
117
|
+
if (resolvedLiveItems.length === 0) {
|
|
118
|
+
return resolvedSortedData;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (resolvedSortedData.length === 0) {
|
|
122
|
+
return resolvedLiveItems;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const liveItemsById = new Map();
|
|
126
|
+
resolvedLiveItems.forEach(item => {
|
|
127
|
+
const itemId = normalizeRowIdentity(item);
|
|
128
|
+
if (itemId) {
|
|
129
|
+
liveItemsById.set(itemId, item);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (liveItemsById.size === 0) {
|
|
134
|
+
return resolvedSortedData;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return resolvedSortedData.map(item => {
|
|
138
|
+
const itemId = normalizeRowIdentity(item);
|
|
139
|
+
const liveItem = itemId ? liveItemsById.get(itemId) : null;
|
|
140
|
+
|
|
141
|
+
if (!liveItem) {
|
|
142
|
+
return item;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return liveItem;
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const stableSerialize = value => {
|
|
150
|
+
if (value === null || value === undefined) {
|
|
151
|
+
return 'null';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (Array.isArray(value)) {
|
|
155
|
+
return `[${value.map(item => stableSerialize(item)).join(',')}]`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (value instanceof Date) {
|
|
159
|
+
return JSON.stringify(value.toISOString());
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (isObject(value)) {
|
|
163
|
+
return `{${Object.keys(value)
|
|
164
|
+
.sort()
|
|
165
|
+
.map(key => `${JSON.stringify(key)}:${stableSerialize(value[key])}`)
|
|
166
|
+
.join(',')}}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return JSON.stringify(value);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export const resolveDateRangeQuery = value => {
|
|
173
|
+
if (!value || typeof value !== 'object') {
|
|
174
|
+
return {};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const shortcut = value.shortcut || value.value || 'all';
|
|
178
|
+
const customRange = value.customRange || { from: '', to: '' };
|
|
179
|
+
const dateRange = getDateRange(shortcut, customRange, {
|
|
180
|
+
relativeMode: 'rolling',
|
|
181
|
+
useCurrentMoment: true,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
after: dateRange?.after || '',
|
|
186
|
+
before: dateRange?.before || '',
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export const resolveFilterQueryValue = value => {
|
|
191
|
+
if (Array.isArray(value)) {
|
|
192
|
+
return value
|
|
193
|
+
.map(item => resolveFilterQueryValue(item))
|
|
194
|
+
.filter(item => item !== '' && item !== null && item !== undefined);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (value && typeof value === 'object') {
|
|
198
|
+
return resolveFilterQueryValue(
|
|
199
|
+
value.value ?? value.id ?? value['@id'] ?? value.key ?? '',
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return normalizeText(value);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export const normalizeCollectionItems = response => {
|
|
207
|
+
if (Array.isArray(response)) return response;
|
|
208
|
+
if (Array.isArray(response?.member)) return response.member;
|
|
209
|
+
if (Array.isArray(response?.['hydra:member'])) return response['hydra:member'];
|
|
210
|
+
|
|
211
|
+
return [];
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export const resolveHasMore = ({ hasMore, dataLength, totalItems }) => {
|
|
215
|
+
if (hasMore !== null && hasMore !== undefined) {
|
|
216
|
+
return hasMore;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const resolvedTotalItems = Number(totalItems);
|
|
220
|
+
if (!Number.isFinite(resolvedTotalItems)) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return dataLength < resolvedTotalItems;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const humanizeSummaryLabel = value =>
|
|
228
|
+
normalizeText(value)
|
|
229
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
230
|
+
.replace(/[._-]+/g, ' ')
|
|
231
|
+
.replace(/\s+/g, ' ')
|
|
232
|
+
.trim();
|
|
233
|
+
|
|
234
|
+
const isMoneySummaryPath = path =>
|
|
235
|
+
/(amount|price|total|value|paid|open|receivable|payable|pending)/i.test(
|
|
236
|
+
Array.isArray(path) ? path.join('.') : normalizeText(path),
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
const normalizeSummaryMoneyValue = value => {
|
|
240
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : 0;
|
|
241
|
+
|
|
242
|
+
const rawValue = normalizeText(value).replace(/[^0-9,.-]/g, '');
|
|
243
|
+
if (!rawValue) return 0;
|
|
244
|
+
|
|
245
|
+
const normalizedValue = rawValue.includes(',')
|
|
246
|
+
? rawValue.replace(/\./g, '').replace(',', '.')
|
|
247
|
+
: rawValue;
|
|
248
|
+
const parsedValue = Number(normalizedValue);
|
|
249
|
+
|
|
250
|
+
return Number.isFinite(parsedValue) ? parsedValue : 0;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export const getSummaryOperations = column => {
|
|
254
|
+
if (typeof column?.summary === 'string') return [column.summary];
|
|
255
|
+
if (Array.isArray(column?.summary)) return column.summary;
|
|
256
|
+
if (isObject(column?.summary)) {
|
|
257
|
+
return Object.entries(column.summary)
|
|
258
|
+
.filter(([, value]) => value)
|
|
259
|
+
.map(([operation]) => operation);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return SUMMARY_OPERATIONS.filter(operation => column?.[operation] === true);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export const getSummaryField = (column, operation) => {
|
|
266
|
+
if (isObject(column?.summary)) {
|
|
267
|
+
const summaryField = column.summary[operation];
|
|
268
|
+
if (typeof summaryField === 'string') return summaryField;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return getColumnKey(column);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export const formatSummaryValue = ({ column, columns, path, storeName, value }) => {
|
|
275
|
+
const columnKey = column ? getColumnKey(column) : '';
|
|
276
|
+
const shouldFormatMoney = isMoneySummaryPath(path) || isMoneySummaryPath(columnKey);
|
|
277
|
+
|
|
278
|
+
if (shouldFormatMoney) {
|
|
279
|
+
return Formatter.formatMoney(normalizeSummaryMoneyValue(value));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (column) {
|
|
283
|
+
return resolveCellText({
|
|
284
|
+
column,
|
|
285
|
+
columns,
|
|
286
|
+
row: { [columnKey]: value },
|
|
287
|
+
storeName,
|
|
288
|
+
value,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return normalizeText(value);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
export const flattenSummaryEntries = ({
|
|
296
|
+
path = [],
|
|
297
|
+
summaryLabels = {},
|
|
298
|
+
usedPaths,
|
|
299
|
+
value,
|
|
300
|
+
}) => {
|
|
301
|
+
if (!isObject(value)) {
|
|
302
|
+
const pathKey = path.join('.');
|
|
303
|
+
if (!pathKey || usedPaths.has(pathKey)) return [];
|
|
304
|
+
|
|
305
|
+
const fallbackLabel = humanizeSummaryLabel(path[path.length - 1] || pathKey);
|
|
306
|
+
|
|
307
|
+
return [{
|
|
308
|
+
key: pathKey,
|
|
309
|
+
label: summaryLabels[pathKey] || summaryLabels[path[path.length - 1]] || fallbackLabel,
|
|
310
|
+
path,
|
|
311
|
+
value,
|
|
312
|
+
}];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return Object.entries(value).flatMap(([key, childValue]) =>
|
|
316
|
+
flattenSummaryEntries({
|
|
317
|
+
path: [...path, key],
|
|
318
|
+
summaryLabels,
|
|
319
|
+
usedPaths,
|
|
320
|
+
value: childValue,
|
|
321
|
+
}),
|
|
322
|
+
);
|
|
323
|
+
};
|
|
@@ -404,7 +404,7 @@ const DefaultTable = ({
|
|
|
404
404
|
showTotalItemsInFooter,
|
|
405
405
|
storedViewMode,
|
|
406
406
|
tableFiltersVisible,
|
|
407
|
-
|
|
407
|
+
sortedData: stableSerialize(sortedData),
|
|
408
408
|
toolbarActionsLength: Array.isArray(toolbarActions) ? toolbarActions.length : 0,
|
|
409
409
|
}),
|
|
410
410
|
[
|
|
@@ -442,7 +442,7 @@ const DefaultTable = ({
|
|
|
442
442
|
showTotalItemsInFooter,
|
|
443
443
|
storedViewMode,
|
|
444
444
|
tableFiltersVisible,
|
|
445
|
-
sortedData
|
|
445
|
+
sortedData,
|
|
446
446
|
toolbarActions,
|
|
447
447
|
],
|
|
448
448
|
);
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import Formatter from '@controleonline/ui-common/src/utils/formatter.js';
|
|
2
|
+
import { getDateRange } from '@controleonline/ui-common/src/react/utils/dateRangeFilter';
|
|
3
|
+
import {
|
|
4
|
+
getColumnKey,
|
|
5
|
+
isDateLikeColumn,
|
|
6
|
+
normalizeText,
|
|
7
|
+
resolveCellText,
|
|
8
|
+
resolveStoreNameFromList,
|
|
9
|
+
} from '../inputs/defaultInputUtils';
|
|
10
|
+
import styles from './DefaultTable.styles';
|
|
11
|
+
import {DEFAULT_CELL_MIN_WIDTH, IDENTITY_CELL_MIN_WIDTH, MONEY_CELL_MIN_WIDTH} from './DefaultTable.dataUtils';
|
|
12
|
+
export const isSortableColumn = column => column?.sortable === true;
|
|
13
|
+
|
|
14
|
+
export const getSortField = column => column?.sortField || getColumnKey(column);
|
|
15
|
+
|
|
16
|
+
export const resolveDefaultSort = columns => {
|
|
17
|
+
if (!Array.isArray(columns)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (const column of columns) {
|
|
22
|
+
if (!column || column?.defaultSort === undefined || column?.defaultSort === null || column?.defaultSort === false) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const field = getSortField(column);
|
|
27
|
+
const defaultSort = column.defaultSort;
|
|
28
|
+
|
|
29
|
+
if (defaultSort && typeof defaultSort === 'object' && !Array.isArray(defaultSort)) {
|
|
30
|
+
const resolvedDirection = normalizeText(defaultSort.direction || defaultSort.order || 'desc').toLowerCase();
|
|
31
|
+
const resolvedField = normalizeText(
|
|
32
|
+
defaultSort.field || defaultSort.sortField || defaultSort.key || defaultSort.name || field,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
direction: resolvedDirection === 'asc' ? 'asc' : 'desc',
|
|
37
|
+
field: resolvedField || field,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof defaultSort === 'string') {
|
|
42
|
+
const normalizedSort = normalizeText(defaultSort).toLowerCase();
|
|
43
|
+
if (normalizedSort === 'asc' || normalizedSort === 'desc') {
|
|
44
|
+
return {
|
|
45
|
+
direction: normalizedSort,
|
|
46
|
+
field,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
direction: 'desc',
|
|
52
|
+
field: normalizeText(defaultSort) || field,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (defaultSort === true) {
|
|
57
|
+
return {
|
|
58
|
+
direction: 'asc',
|
|
59
|
+
field,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return null;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const sanitizeStoredSortPreference = ({
|
|
68
|
+
columns = [],
|
|
69
|
+
fallbackSort = null,
|
|
70
|
+
sort = null,
|
|
71
|
+
}) => {
|
|
72
|
+
const normalizedFallback =
|
|
73
|
+
fallbackSort &&
|
|
74
|
+
typeof fallbackSort === 'object' &&
|
|
75
|
+
typeof fallbackSort.field === 'string' &&
|
|
76
|
+
fallbackSort.field.trim()
|
|
77
|
+
? {
|
|
78
|
+
direction: fallbackSort.direction === 'asc' ? 'asc' : 'desc',
|
|
79
|
+
field: fallbackSort.field.trim(),
|
|
80
|
+
}
|
|
81
|
+
: null;
|
|
82
|
+
|
|
83
|
+
if (
|
|
84
|
+
!sort ||
|
|
85
|
+
typeof sort !== 'object' ||
|
|
86
|
+
typeof sort.field !== 'string' ||
|
|
87
|
+
!sort.field.trim()
|
|
88
|
+
) {
|
|
89
|
+
return normalizedFallback;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const normalizedDirection = sort.direction === 'asc' ? 'asc' : 'desc';
|
|
93
|
+
const normalizedField = sort.field.trim();
|
|
94
|
+
const sortableFields = new Set(
|
|
95
|
+
(Array.isArray(columns) ? columns : [])
|
|
96
|
+
.filter(isSortableColumn)
|
|
97
|
+
.map(column => getSortField(column))
|
|
98
|
+
.filter(Boolean),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (!sortableFields.has(normalizedField)) {
|
|
102
|
+
return normalizedFallback;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
direction: normalizedDirection,
|
|
107
|
+
field: normalizedField,
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const readValueByPath = (object, path) => {
|
|
112
|
+
if (!object || !path) return object;
|
|
113
|
+
|
|
114
|
+
return String(path)
|
|
115
|
+
.split('.')
|
|
116
|
+
.reduce((currentValue, key) => {
|
|
117
|
+
if (currentValue === null || currentValue === undefined) return currentValue;
|
|
118
|
+
return currentValue?.[key];
|
|
119
|
+
}, object);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const normalizeSortText = value =>
|
|
123
|
+
normalizeText(value)
|
|
124
|
+
.normalize('NFD')
|
|
125
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
126
|
+
.toLowerCase();
|
|
127
|
+
|
|
128
|
+
export const parseSortNumber = value => {
|
|
129
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
130
|
+
if (value == null) return NaN;
|
|
131
|
+
|
|
132
|
+
const raw = String(value).trim();
|
|
133
|
+
if (!raw || !/[0-9]/.test(raw) || raw.includes('/')) return NaN;
|
|
134
|
+
|
|
135
|
+
const compact = raw.replace(/[^0-9,.-]/g, '');
|
|
136
|
+
if (!compact) return NaN;
|
|
137
|
+
|
|
138
|
+
const hasComma = compact.includes(',');
|
|
139
|
+
const hasDot = compact.includes('.');
|
|
140
|
+
let normalized = compact;
|
|
141
|
+
|
|
142
|
+
if (hasComma && hasDot) {
|
|
143
|
+
normalized = compact.lastIndexOf(',') > compact.lastIndexOf('.')
|
|
144
|
+
? compact.replace(/\./g, '').replace(',', '.')
|
|
145
|
+
: compact.replace(/,/g, '');
|
|
146
|
+
} else if (hasComma) {
|
|
147
|
+
const parts = compact.split(',');
|
|
148
|
+
normalized = parts.length === 2 && parts[1].length <= 2
|
|
149
|
+
? `${parts[0].replace(/\./g, '')}.${parts[1]}`
|
|
150
|
+
: compact.replace(/,/g, '');
|
|
151
|
+
} else if (hasDot) {
|
|
152
|
+
const parts = compact.split('.');
|
|
153
|
+
if (parts.length > 2) {
|
|
154
|
+
normalized = compact.replace(/\./g, '');
|
|
155
|
+
} else if (parts.length === 2 && parts[1].length === 3 && parts[0].length <= 3) {
|
|
156
|
+
normalized = compact.replace(/\./g, '');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const parsed = Number(normalized);
|
|
161
|
+
return Number.isFinite(parsed) ? parsed : NaN;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const resolveSortComparable = ({ column, row, storeName, columns }) => {
|
|
165
|
+
const fieldName = getColumnKey(column);
|
|
166
|
+
const sortField = getSortField(column);
|
|
167
|
+
const rawValue = sortField === fieldName ? row?.[fieldName] : readValueByPath(row, sortField);
|
|
168
|
+
|
|
169
|
+
if (isDateLikeColumn(column)) {
|
|
170
|
+
const dateValue =
|
|
171
|
+
rawValue && typeof rawValue === 'object'
|
|
172
|
+
? rawValue?.value ??
|
|
173
|
+
rawValue?.date ??
|
|
174
|
+
rawValue?.createdAt ??
|
|
175
|
+
rawValue?.updatedAt ??
|
|
176
|
+
rawValue?.['@id'] ??
|
|
177
|
+
rawValue?.[fieldName] ??
|
|
178
|
+
rawValue
|
|
179
|
+
: rawValue;
|
|
180
|
+
const parsedDate = Date.parse(dateValue);
|
|
181
|
+
return Number.isFinite(parsedDate) ? parsedDate : Number.NEGATIVE_INFINITY;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const resolvedValue = sortField === fieldName
|
|
185
|
+
? resolveCellText({
|
|
186
|
+
column,
|
|
187
|
+
columns,
|
|
188
|
+
row,
|
|
189
|
+
storeName,
|
|
190
|
+
})
|
|
191
|
+
: normalizeText(rawValue ?? resolveCellText({
|
|
192
|
+
column,
|
|
193
|
+
columns,
|
|
194
|
+
row,
|
|
195
|
+
storeName,
|
|
196
|
+
}));
|
|
197
|
+
|
|
198
|
+
const normalizedNumber = parseSortNumber(resolvedValue);
|
|
199
|
+
|
|
200
|
+
if (Number.isFinite(normalizedNumber)) {
|
|
201
|
+
return normalizedNumber;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return normalizeSortText(resolvedValue);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export const getColumnStyle = column => {
|
|
208
|
+
const key = getColumnKey(column);
|
|
209
|
+
const customSize = {};
|
|
210
|
+
const columnWidth = Number(column?.width);
|
|
211
|
+
const columnMinWidth = Number(column?.minWidth);
|
|
212
|
+
const columnFlexBasis = Number(column?.flexBasis);
|
|
213
|
+
|
|
214
|
+
if (Number.isFinite(columnWidth) && columnWidth > 0) {
|
|
215
|
+
customSize.width = columnWidth;
|
|
216
|
+
customSize.flexBasis = columnWidth;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (Number.isFinite(columnMinWidth) && columnMinWidth > 0) {
|
|
220
|
+
customSize.minWidth = columnMinWidth;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (Number.isFinite(columnFlexBasis) && columnFlexBasis > 0) {
|
|
224
|
+
customSize.flexBasis = columnFlexBasis;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const columnSizeStyle = Object.keys(customSize).length ? customSize : null;
|
|
228
|
+
|
|
229
|
+
if (column?.isIdentity) return [styles.cell, styles.identityCell, columnSizeStyle];
|
|
230
|
+
if (['price', 'total', 'amount', 'value'].includes(key)) {
|
|
231
|
+
return [styles.cell, styles.moneyCell, columnSizeStyle];
|
|
232
|
+
}
|
|
233
|
+
return [styles.cell, columnSizeStyle];
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export const getColumnMinWidth = column => {
|
|
237
|
+
const columnMinWidth = Number(column?.minWidth);
|
|
238
|
+
if (Number.isFinite(columnMinWidth) && columnMinWidth > 0) {
|
|
239
|
+
return columnMinWidth;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const columnWidth = Number(column?.width);
|
|
243
|
+
if (Number.isFinite(columnWidth) && columnWidth > 0) {
|
|
244
|
+
return columnWidth;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const key = getColumnKey(column);
|
|
248
|
+
if (column?.isIdentity) return IDENTITY_CELL_MIN_WIDTH;
|
|
249
|
+
if (['price', 'total', 'amount', 'value'].includes(key)) {
|
|
250
|
+
return MONEY_CELL_MIN_WIDTH;
|
|
251
|
+
}
|
|
252
|
+
return DEFAULT_CELL_MIN_WIDTH;
|
|
253
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {Platform} from 'react-native';
|
|
2
|
+
export const shadow = Platform.select({
|
|
3
|
+
ios: {
|
|
4
|
+
shadowColor: '#0F172A',
|
|
5
|
+
shadowOffset: { width: 0, height: 8 },
|
|
6
|
+
shadowOpacity: 0.14,
|
|
7
|
+
shadowRadius: 20,
|
|
8
|
+
},
|
|
9
|
+
android: { elevation: 6 },
|
|
10
|
+
web: { boxShadow: '0 14px 36px rgba(15,23,42,0.16)' },
|
|
11
|
+
});
|