@controleonline/ui-default 1.0.272 → 1.0.273

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.github/workflows/publish-npm.yml +91 -0
  2. package/AGENTS.md +1 -1
  3. package/docs/technical/DefaultTable.md +70 -1
  4. package/package.json +22 -22
  5. package/src/react/components/address/DefaultAddress.js +20 -9
  6. package/src/react/components/address/DefaultAddressCoordFields.js +59 -0
  7. package/src/react/components/address/LatLonReadonlyFields.js +2 -2
  8. package/src/react/components/address/defaultAddressStyles.js +13 -1
  9. package/src/react/components/files/DefaultFile.js +2 -2
  10. package/src/react/components/files/DefaultFileColumn.js +1 -1
  11. package/src/react/components/filters/DefaultColumnFilter.js +2 -7
  12. package/src/react/components/filters/DefaultExternalFilters.js +4 -11
  13. package/src/react/components/filters/filterValue.js +65 -0
  14. package/src/react/components/help/DefaultTooltip.js +73 -63
  15. package/src/react/components/help/DefaultTooltip.styles.js +23 -23
  16. package/src/react/components/inputs/DefaultInput.js +301 -210
  17. package/src/react/components/inputs/defaultInputUtils.js +193 -170
  18. package/src/react/components/table/DefaultColumnMenu.js +127 -109
  19. package/src/react/components/table/DefaultTable.js +4 -4
  20. package/src/react/components/table/DefaultTable.styles.js +10 -4
  21. package/src/react/components/table/DefaultTable.utils.js +38 -4
  22. package/src/react/components/table/DefaultTableRows.js +324 -153
  23. package/src/react/components/upload/DefaultUpload.js +6 -36
  24. package/src/react/components/upload/DefaultUpload.styles.js +5 -5
  25. package/src/react/components/upload/DefaultUploadAttachmentsList.js +11 -3
  26. package/src/react/hooks/usePostalCodeLookup.js +15 -1
  27. package/src/react/services/addressFormUtils.js +104 -38
  28. package/src/react/services/addressGeo.js +6 -3
  29. package/src/react/utils/tableVisibleColumnsPreferences.js +84 -3
  30. package/src/tests/react/components/files/DefaultFile.test.js +16 -0
  31. package/src/tests/react/components/files/DefaultFileColumn.company.test.js +24 -0
  32. package/src/tests/react/components/help/DefaultTooltip.test.js +93 -0
  33. package/src/tests/react/components/table/DefaultTable.utils.test.js +10 -0
  34. package/src/tests/react/components/table/DefaultTableRowActions.test.js +23 -0
  35. package/src/tests/react/components/table/DefaultTableRows.rowActions.test.js +152 -0
  36. package/src/tests/react/components/upload/defaultUploadLibrary.test.js +31 -9
  37. package/src/tests/react/filters/filterValue.test.js +46 -0
  38. package/src/tests/react/hooks/usePostalCodeLookup.test.js +67 -0
  39. package/src/tests/react/inputs/defaultInputUtils.test.js +9 -1
  40. package/src/tests/react/services/addressFormUtils.test.js +6 -3
  41. package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +12 -1
@@ -2,31 +2,31 @@ import { getAllStores } from '@store';
2
2
  import Formatter from '@controleonline/ui-common/src/utils/formatter.js';
3
3
  import { formatStoreColumnValue } from '@controleonline/ui-common/src/react/utils/storeColumns';
4
4
  import FeatherGlyphMap from 'react-native-vector-icons/glyphmaps/Feather.json';
5
-
5
+
6
6
  export const normalizeText = value => String(value ?? '').trim();
7
7
  export const isValidFeatherIcon = icon => Boolean(FeatherGlyphMap[normalizeText(icon)]);
8
8
  const formatHumanLabel = value =>
9
- normalizeText(value)
10
- .replace(/([a-z])([A-Z])/g, '$1 $2')
11
- .replace(/[_-]+/g, ' ')
12
- .replace(/^\w/, letter => letter.toUpperCase());
13
- const DEFAULT_TRANSLATABLE_FIELDS = new Set([
14
- 'active',
15
- 'app',
16
- 'channel',
17
- 'displaytype',
18
- 'featured',
19
- 'frequency',
20
- 'invoicetype',
21
- 'installments',
22
- 'ordertype',
23
- 'peopletype',
24
- 'pricecalculation',
25
- 'productcondition',
26
- 'realstatus',
27
- 'status',
28
- ]);
29
-
9
+ normalizeText(value)
10
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
11
+ .replace(/[_-]+/g, ' ')
12
+ .replace(/^\w/, letter => letter.toUpperCase());
13
+ const DEFAULT_TRANSLATABLE_FIELDS = new Set([
14
+ 'active',
15
+ 'app',
16
+ 'channel',
17
+ 'displaytype',
18
+ 'featured',
19
+ 'frequency',
20
+ 'invoicetype',
21
+ 'installments',
22
+ 'ordertype',
23
+ 'peopletype',
24
+ 'pricecalculation',
25
+ 'productcondition',
26
+ 'realstatus',
27
+ 'status',
28
+ ]);
29
+
30
30
  export const getColumnKey = column => column?.key || column?.name || '';
31
31
 
32
32
  export const isDateColumn = column =>
@@ -54,6 +54,13 @@ export const isColorColumn = column => {
54
54
  );
55
55
  };
56
56
 
57
+ export const isIconColumn = column => {
58
+ const fieldName = normalizeColumnKey(getColumnKey(column));
59
+ const inputType = normalizeColumnKey(column?.inputType || column?.type);
60
+
61
+ return inputType === 'icon' || fieldName === 'icon';
62
+ };
63
+
57
64
  export const isFileColumn = column => {
58
65
  const inputType = normalizeColumnKey(column?.inputType || column?.type);
59
66
 
@@ -73,114 +80,130 @@ export const isExtraDataColumn = column =>
73
80
 
74
81
  const normalizeColumnKey = value =>
75
82
  normalizeText(value)
76
- .replace(/[^a-zA-Z0-9]/g, '')
77
- .toLowerCase();
78
-
79
- const shouldTranslateOptionLabel = (column, storeName, rawLabel) => {
80
- if (column?.translate === false || !normalizeText(rawLabel) || !normalizeText(storeName)) {
81
- return false;
82
- }
83
-
84
- if (column?.translate === true || Array.isArray(column?.list)) {
85
- return true;
86
- }
87
-
88
- return [column?.key, column?.name, column?.label]
89
- .map(normalizeColumnKey)
90
- .filter(Boolean)
91
- .some(candidate => DEFAULT_TRANSLATABLE_FIELDS.has(candidate));
92
- };
93
-
94
- export const normalizeId = value => {
95
- if (!value) return '';
96
- if (typeof value === 'number') return String(value);
97
- if (typeof value === 'string') {
98
- const match = value.match(/\d+/g);
99
- return match ? match[match.length - 1] : value;
100
- }
101
- return normalizeId(value?.id || value?.['@id'] || '');
102
- };
103
-
83
+ .replace(/[^a-zA-Z0-9]/g, '')
84
+ .toLowerCase();
85
+
86
+ const shouldTranslateOptionLabel = (column, storeName, rawLabel) => {
87
+ if (column?.translate === false || !normalizeText(rawLabel) || !normalizeText(storeName)) {
88
+ return false;
89
+ }
90
+
91
+ if (column?.translate === true || Array.isArray(column?.list)) {
92
+ return true;
93
+ }
94
+
95
+ return [column?.key, column?.name, column?.label]
96
+ .map(normalizeColumnKey)
97
+ .filter(Boolean)
98
+ .some(candidate => DEFAULT_TRANSLATABLE_FIELDS.has(candidate));
99
+ };
100
+
101
+ export const normalizeId = value => {
102
+ if (!value) return '';
103
+ if (typeof value === 'number') return String(value);
104
+ if (typeof value === 'string') {
105
+ const match = value.match(/\d+/g);
106
+ return match ? match[match.length - 1] : value;
107
+ }
108
+ return normalizeId(value?.id || value?.['@id'] || '');
109
+ };
110
+
104
111
  export const isEditableColumn = column =>
105
112
  column?.editable !== false &&
106
113
  !column?.isIdentity &&
107
114
  column?.inputType !== 'increase' &&
108
115
  !isFileColumn(column) &&
109
116
  !String(getColumnKey(column)).includes('.');
110
-
117
+
111
118
  export const normalizeOptionKey = option => {
112
- if (!option) return '';
113
- if (typeof option !== 'object') return normalizeId(option) || normalizeText(option);
114
- return normalizeText(
115
- option.value ??
116
- option.id ??
117
- normalizeId(option['@id']) ??
118
- option.key ??
119
- '',
120
- );
121
- };
122
-
123
- export const resolveOptionLabel = (column, option, storeName = '') => {
124
- if (!option) return '';
125
-
126
- const translateOptionLabel = rawLabel => {
127
- const normalizedLabel = normalizeText(rawLabel);
128
- if (!shouldTranslateOptionLabel(column, storeName, normalizedLabel)) {
129
- return normalizedLabel;
130
- }
131
-
132
- const translated = global.t?.t(storeName, 'label', normalizedLabel) || normalizedLabel;
133
- return normalizeText(translated) === formatHumanLabel(normalizedLabel)
134
- ? normalizedLabel
135
- : translated;
136
- };
137
-
138
- if (typeof column?.formatList === 'function') {
139
- const formatted = column.formatList(option, null, column);
140
- if (formatted && typeof formatted === 'object') {
141
- return translateOptionLabel(formatted.label ?? formatted.value);
142
- }
143
- if (formatted) return translateOptionLabel(formatted);
144
- }
145
-
146
- const rawLabel = normalizeText(
147
- option.label ??
148
- option[column?.searchParam] ??
149
- option[column?.name] ??
150
- option.name ??
151
- option.status ??
152
- option.wallet ??
153
- option.paymentType ??
154
- option.alias ??
155
- option.id,
156
- );
157
-
158
- return translateOptionLabel(rawLabel);
159
- };
160
-
119
+ if (!option) return '';
120
+ if (typeof option !== 'object') return normalizeId(option) || normalizeText(option);
121
+ return normalizeText(
122
+ option.value ??
123
+ option.id ??
124
+ normalizeId(option['@id']) ??
125
+ option.key ??
126
+ '',
127
+ );
128
+ };
129
+
130
+ export const resolveOptionLabel = (column, option, storeName = '') => {
131
+ if (!option) return '';
132
+
133
+ const STATUS_DISPLAY_PT = {
134
+ paid: 'Pago',
135
+ 'waiting payment': 'Aguardando pagamento',
136
+ waiting_payment: 'Aguardando pagamento',
137
+ };
138
+
139
+ const translateOptionLabel = rawLabel => {
140
+ const normalizedLabel = normalizeText(rawLabel);
141
+ const statusFallback = STATUS_DISPLAY_PT[normalizedLabel.toLowerCase()] || '';
142
+ if (!shouldTranslateOptionLabel(column, storeName, normalizedLabel)) {
143
+ return statusFallback || normalizedLabel;
144
+ }
145
+
146
+ const translated = global.t?.t(storeName, 'label', normalizedLabel) || normalizedLabel;
147
+ const translatedText = normalizeText(translated);
148
+ if (
149
+ statusFallback &&
150
+ (!translatedText ||
151
+ translatedText.toLowerCase() === normalizedLabel.toLowerCase() ||
152
+ translatedText === formatHumanLabel(normalizedLabel))
153
+ ) {
154
+ return statusFallback;
155
+ }
156
+ return translatedText === formatHumanLabel(normalizedLabel)
157
+ ? normalizedLabel
158
+ : translated;
159
+ };
160
+
161
+ if (typeof column?.formatList === 'function') {
162
+ const formatted = column.formatList(option, null, column);
163
+ if (formatted && typeof formatted === 'object') {
164
+ return translateOptionLabel(formatted.label ?? formatted.value);
165
+ }
166
+ if (formatted) return translateOptionLabel(formatted);
167
+ }
168
+
169
+ const rawLabel = normalizeText(
170
+ option.label ??
171
+ option[column?.searchParam] ??
172
+ option[column?.name] ??
173
+ option.name ??
174
+ option.status ??
175
+ option.wallet ??
176
+ option.paymentType ??
177
+ option.alias ??
178
+ option.id,
179
+ );
180
+
181
+ return translateOptionLabel(rawLabel);
182
+ };
183
+
161
184
  export const resolveStoreNameFromList = list =>
162
185
  typeof list === 'string' ? normalizeText(list).split('/')[0] || '' : '';
163
-
186
+
164
187
  const filterItemsByListRequestParams = (items = [], column = null) => {
165
- const requestParams =
166
- column?.listRequestParams &&
167
- typeof column.listRequestParams === 'object' &&
168
- !Array.isArray(column.listRequestParams)
169
- ? column.listRequestParams
170
- : null;
171
-
172
- if (!requestParams || Object.keys(requestParams).length === 0) {
173
- return Array.isArray(items) ? items : [];
174
- }
175
-
176
- return (Array.isArray(items) ? items : []).filter(item =>
177
- Object.entries(requestParams).every(([key, expectedValue]) => {
178
- if (expectedValue === undefined || expectedValue === null || expectedValue === '') {
179
- return true;
180
- }
181
-
182
- return normalizeText(item?.[key]) === normalizeText(expectedValue);
183
- }),
188
+ const requestParams =
189
+ column?.listRequestParams &&
190
+ typeof column.listRequestParams === 'object' &&
191
+ !Array.isArray(column.listRequestParams)
192
+ ? column.listRequestParams
193
+ : null;
194
+
195
+ if (!requestParams || Object.keys(requestParams).length === 0) {
196
+ return Array.isArray(items) ? items : [];
197
+ }
198
+
199
+ return (Array.isArray(items) ? items : []).filter(item =>
200
+ Object.entries(requestParams).every(([key, expectedValue]) => {
201
+ if (expectedValue === undefined || expectedValue === null || expectedValue === '') {
202
+ return true;
203
+ }
204
+
205
+ return normalizeText(item?.[key]) === normalizeText(expectedValue);
206
+ }),
184
207
  );
185
208
  };
186
209
 
@@ -215,16 +238,16 @@ export const mapOptions = (column, items = [], storeName = '') => {
215
238
  const options = [];
216
239
 
217
240
  (Array.isArray(items) ? items : []).forEach(item => {
218
- const formatted =
219
- typeof column?.formatList === 'function'
220
- ? column.formatList(item, null, column)
221
- : item;
222
- const key = normalizeOptionKey(formatted) || normalizeOptionKey(item);
223
- const dedupeKey = key || normalizeText(resolveOptionLabel(column, item, storeName)).toLowerCase();
224
-
225
- if (dedupeKey && seenKeys.has(dedupeKey)) return;
226
- if (dedupeKey) seenKeys.add(dedupeKey);
227
-
241
+ const formatted =
242
+ typeof column?.formatList === 'function'
243
+ ? column.formatList(item, null, column)
244
+ : item;
245
+ const key = normalizeOptionKey(formatted) || normalizeOptionKey(item);
246
+ const dedupeKey = key || normalizeText(resolveOptionLabel(column, item, storeName)).toLowerCase();
247
+
248
+ if (dedupeKey && seenKeys.has(dedupeKey)) return;
249
+ if (dedupeKey) seenKeys.add(dedupeKey);
250
+
228
251
  const formattedMeta = normalizeObject(formatted);
229
252
  const itemMeta = normalizeObject(item);
230
253
  const color = formattedMeta.color ?? itemMeta.color;
@@ -240,10 +263,10 @@ export const mapOptions = (column, items = [], storeName = '') => {
240
263
  raw: item,
241
264
  });
242
265
  });
243
-
244
- return options;
245
- };
246
-
266
+
267
+ return options;
268
+ };
269
+
247
270
  export const buildOptionsFromColumn = (column, getOptionsForColumn = null, storeName = '') => {
248
271
  const explicitOptions = getOptionsForColumn?.(column);
249
272
  if (Array.isArray(explicitOptions) && explicitOptions.length > 0) {
@@ -255,26 +278,26 @@ export const buildOptionsFromColumn = (column, getOptionsForColumn = null, store
255
278
  }
256
279
 
257
280
  const listStoreName = resolveStoreNameFromList(column?.list);
258
- if (!listStoreName) return [];
259
-
260
- const listStore = getAllStores()?.[listStoreName];
261
- return mapOptions(
262
- column,
263
- filterItemsByListRequestParams(listStore?.getters?.items || [], column),
264
- storeName,
265
- );
266
- };
267
-
281
+ if (!listStoreName) return [];
282
+
283
+ const listStore = getAllStores()?.[listStoreName];
284
+ return mapOptions(
285
+ column,
286
+ filterItemsByListRequestParams(listStore?.getters?.items || [], column),
287
+ storeName,
288
+ );
289
+ };
290
+
268
291
  export const resolveCellText = ({ column, columns = [], row, storeName, value }) => {
269
292
  const fieldName = getColumnKey(column);
270
293
  const rawValue = value ?? row?.[fieldName];
271
294
  const formattedValue = formatStoreColumnValue({
272
- columns: columns.length ? columns : [column],
273
- fieldName,
274
- row,
275
- storeName,
276
- value: rawValue,
277
- });
295
+ columns: columns.length ? columns : [column],
296
+ fieldName,
297
+ row,
298
+ storeName,
299
+ value: rawValue,
300
+ });
278
301
 
279
302
  return normalizeText(formattedValue) || '-';
280
303
  };
@@ -408,29 +431,29 @@ export const buildReadPresentationStyles = presentation => {
408
431
  export const resolveEditValue = (column, row, value) => {
409
432
  const rawValue = value ?? row?.[getColumnKey(column)];
410
433
  if (column?.list) {
411
- return normalizeOptionKey(
412
- typeof column?.formatList === 'function'
413
- ? column.formatList(rawValue, row, column)
414
- : rawValue,
415
- );
416
- }
417
-
418
- if (typeof column?.editFormat === 'function') {
419
- return normalizeText(column.editFormat(rawValue, column, row, true));
420
- }
421
-
434
+ return normalizeOptionKey(
435
+ typeof column?.formatList === 'function'
436
+ ? column.formatList(rawValue, row, column)
437
+ : rawValue,
438
+ );
439
+ }
440
+
441
+ if (typeof column?.editFormat === 'function') {
442
+ return normalizeText(column.editFormat(rawValue, column, row, true));
443
+ }
444
+
422
445
  if (isDateLikeColumn(column) && rawValue) {
423
446
  return normalizeText(Formatter.formatDateYmdTodmY(rawValue));
424
447
  }
425
-
426
- return normalizeText(rawValue);
427
- };
428
-
429
- export const formatSaveValue = (column, value, row) => {
430
- if (typeof column?.saveFormat === 'function') {
431
- return column.saveFormat(value, column, row);
432
- }
433
-
448
+
449
+ return normalizeText(rawValue);
450
+ };
451
+
452
+ export const formatSaveValue = (column, value, row) => {
453
+ if (typeof column?.saveFormat === 'function') {
454
+ return column.saveFormat(value, column, row);
455
+ }
456
+
434
457
  if (value && typeof value === 'object') {
435
458
  if (value.value) return Number.isNaN(Number(value.value)) ? value.value : Number(value.value);
436
459
  if (value['@id']) return value['@id'];
@@ -1,109 +1,127 @@
1
- import React from 'react';
2
- import { Modal, ScrollView, Text, TouchableOpacity, View } from 'react-native';
3
- import Icon from 'react-native-vector-icons/Feather';
4
- import { useStore } from '@store';
5
- import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
6
- import { getColumnKey } from '../inputs/defaultInputUtils';
7
- import {
8
- persistVisibleColumnsPreference,
9
- resolveDefaultTablePreferenceScope,
10
- sanitizeVisibleColumnsPreference,
11
- } from '../../utils/tableVisibleColumnsPreferences';
12
- import styles from './DefaultTable.styles';
13
- import { shouldIncludeColumn } from './DefaultTable.utils';
14
- import useDefaultTableTheme from './useDefaultTableTheme';
15
-
16
- const DefaultColumnMenu = ({ storeName, visible = false, onClose }) => {
17
- const store = useStore(storeName);
18
- const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
19
- const visibleColumns = store?.getters?.visibleColumns || {};
20
- const configs = store?.getters?.configs || {};
21
- const tablePreferenceScope =
22
- configs.tablePreferenceScope ||
23
- resolveDefaultTablePreferenceScope({ storeName });
24
- const availableColumns = columns.filter(column => shouldIncludeColumn(column));
25
- const {
26
- checkboxBorderColor: resolvedCheckboxBorderColor,
27
- checkboxSelectedMarkColor: resolvedCheckboxSelectedMarkColor,
28
- modalColors,
29
- themeColors,
30
- } = useDefaultTableTheme();
31
- if (!visible) return null;
32
-
33
- const {
34
- backgroundColor,
35
- borderColor,
36
- closeIconColor,
37
- headerTextColor,
38
- overlayColor,
39
- textColor,
40
- } = modalColors;
41
-
42
- return (
43
- <Modal visible transparent animationType="fade" onRequestClose={onClose}>
44
- <View style={[styles.modalOverlay, { backgroundColor: overlayColor }]}>
45
- <View style={[styles.modalCard, styles.columnMenuModalCard, { borderColor, backgroundColor }]}>
46
- <View style={[styles.modalHeader, { borderBottomColor: borderColor }]}>
47
- <Text style={[styles.modalTitle, { color: headerTextColor }]} numberOfLines={1}>
48
- {global.t?.t(storeName, 'label', 'columns')}
49
- </Text>
50
- <TouchableOpacity
51
- style={[styles.modalCloseButton, { borderColor, backgroundColor }]}
52
- activeOpacity={0.82}
53
- onPress={onClose}
54
- >
55
- <Icon name="x" size={16} color={themeColors.iconWarning || closeIconColor} />
56
- </TouchableOpacity>
57
- </View>
58
- <ScrollView style={styles.columnMenuModalBody} contentContainerStyle={styles.columnMenuModalList}>
59
- {availableColumns.map(column => {
60
- const fieldName = getColumnKey(column);
61
- const label = formatStoreColumnLabel({
62
- columns,
63
- fieldName,
64
- fallbackLabel: column?.label || fieldName,
65
- storeName,
66
- });
67
- const checked = visibleColumns[fieldName] !== false;
68
- const toggleColumn = () => {
69
- const nextVisibleColumns = sanitizeVisibleColumnsPreference({
70
- columns,
71
- visibleColumns: {
72
- ...visibleColumns,
73
- [fieldName]: visibleColumns[fieldName] === false,
74
- },
75
- });
76
-
77
- persistVisibleColumnsPreference(tablePreferenceScope, nextVisibleColumns);
78
-
79
- if (typeof store?.actions?.setVisibleColumns === 'function') {
80
- store.actions.setVisibleColumns(nextVisibleColumns);
81
- } else if (store?.getters) {
82
- store.getters.visibleColumns = nextVisibleColumns;
83
- }
84
- };
85
-
86
- return (
87
- <TouchableOpacity
88
- key={fieldName}
89
- style={styles.columnMenuItem}
90
- activeOpacity={0.82}
91
- onPress={toggleColumn}
92
- >
93
- <Icon
94
- name={checked ? 'check-square' : 'square'}
95
- size={16}
96
- color={checked ? resolvedCheckboxSelectedMarkColor : resolvedCheckboxBorderColor}
97
- />
98
- <Text style={[styles.columnMenuText, { color: textColor }]} numberOfLines={1}>{label}</Text>
99
- </TouchableOpacity>
100
- );
101
- })}
102
- </ScrollView>
103
- </View>
104
- </View>
105
- </Modal>
106
- );
107
- };
108
-
109
- export default DefaultColumnMenu;
1
+ import React from 'react';
2
+ import { Modal, ScrollView, Text, TouchableOpacity, View } from 'react-native';
3
+ import Icon from 'react-native-vector-icons/Feather';
4
+ import { useStore } from '@store';
5
+ import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
6
+ import { getColumnKey } from '../inputs/defaultInputUtils';
7
+ import {
8
+ canHideVisibleColumn,
9
+ isRequiredVisibleColumn,
10
+ persistVisibleColumnsPreference,
11
+ resolveDefaultTablePreferenceScope,
12
+ sanitizeVisibleColumnsPreference,
13
+ } from '../../utils/tableVisibleColumnsPreferences';
14
+ import styles from './DefaultTable.styles';
15
+ import { shouldIncludeColumn } from './DefaultTable.utils';
16
+ import useDefaultTableTheme from './useDefaultTableTheme';
17
+
18
+ const DefaultColumnMenu = ({ storeName, visible = false, onClose }) => {
19
+ const store = useStore(storeName);
20
+ const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
21
+ const visibleColumns = store?.getters?.visibleColumns || {};
22
+ const configs = store?.getters?.configs || {};
23
+ const tablePreferenceScope =
24
+ configs.tablePreferenceScope ||
25
+ resolveDefaultTablePreferenceScope({ storeName });
26
+ const availableColumns = columns.filter(column => shouldIncludeColumn(column));
27
+ const {
28
+ checkboxBorderColor: resolvedCheckboxBorderColor,
29
+ checkboxSelectedMarkColor: resolvedCheckboxSelectedMarkColor,
30
+ modalColors,
31
+ themeColors,
32
+ } = useDefaultTableTheme();
33
+ if (!visible) return null;
34
+
35
+ const {
36
+ backgroundColor,
37
+ borderColor,
38
+ closeIconColor,
39
+ headerTextColor,
40
+ overlayColor,
41
+ textColor,
42
+ } = modalColors;
43
+
44
+ return (
45
+ <Modal visible transparent animationType="fade" onRequestClose={onClose}>
46
+ <View style={[styles.modalOverlay, { backgroundColor: overlayColor }]}>
47
+ <View style={[styles.modalCard, styles.columnMenuModalCard, { borderColor, backgroundColor }]}>
48
+ <View style={[styles.modalHeader, { borderBottomColor: borderColor }]}>
49
+ <Text style={[styles.modalTitle, { color: headerTextColor }]} numberOfLines={1}>
50
+ {global.t?.t(storeName, 'label', 'columns')}
51
+ </Text>
52
+ <TouchableOpacity
53
+ style={[styles.modalCloseButton, { borderColor, backgroundColor }]}
54
+ activeOpacity={0.82}
55
+ onPress={onClose}
56
+ >
57
+ <Icon name="x" size={16} color={themeColors.iconWarning || closeIconColor} />
58
+ </TouchableOpacity>
59
+ </View>
60
+ <ScrollView style={styles.columnMenuModalBody} contentContainerStyle={styles.columnMenuModalList}>
61
+ {availableColumns.map(column => {
62
+ const fieldName = getColumnKey(column);
63
+ const label = formatStoreColumnLabel({
64
+ columns,
65
+ fieldName,
66
+ fallbackLabel: column?.label || fieldName,
67
+ storeName,
68
+ });
69
+ const checked = visibleColumns[fieldName] !== false;
70
+ const required = isRequiredVisibleColumn(column);
71
+ const locked =
72
+ required ||
73
+ (checked &&
74
+ !canHideVisibleColumn({
75
+ columns,
76
+ fieldName,
77
+ visibleColumns,
78
+ }));
79
+ const toggleColumn = () => {
80
+ if (locked && checked) {
81
+ return;
82
+ }
83
+
84
+ const nextVisibleColumns = sanitizeVisibleColumnsPreference({
85
+ columns,
86
+ visibleColumns: {
87
+ ...visibleColumns,
88
+ [fieldName]: visibleColumns[fieldName] === false,
89
+ },
90
+ });
91
+
92
+ persistVisibleColumnsPreference(tablePreferenceScope, nextVisibleColumns);
93
+
94
+ if (typeof store?.actions?.setVisibleColumns === 'function') {
95
+ store.actions.setVisibleColumns(nextVisibleColumns);
96
+ } else if (store?.getters) {
97
+ store.getters.visibleColumns = nextVisibleColumns;
98
+ }
99
+ };
100
+
101
+ return (
102
+ <TouchableOpacity
103
+ key={fieldName}
104
+ style={styles.columnMenuItem}
105
+ activeOpacity={locked ? 1 : 0.82}
106
+ disabled={locked && checked}
107
+ onPress={toggleColumn}
108
+ >
109
+ <Icon
110
+ name={checked ? 'check-square' : 'square'}
111
+ size={16}
112
+ color={checked ? resolvedCheckboxSelectedMarkColor : resolvedCheckboxBorderColor}
113
+ />
114
+ <Text style={[styles.columnMenuText, { color: textColor }]} numberOfLines={1}>
115
+ {required ? `${label} *` : label}
116
+ </Text>
117
+ </TouchableOpacity>
118
+ );
119
+ })}
120
+ </ScrollView>
121
+ </View>
122
+ </View>
123
+ </Modal>
124
+ );
125
+ };
126
+
127
+ export default DefaultColumnMenu;