@controleonline/ui-default 1.0.263 → 1.0.265

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/AGENTS.md +47 -45
  2. package/package.json +20 -20
  3. package/src/react/components/errors/DefaultErrors.js +307 -325
  4. package/src/react/components/files/DefaultFile.js +46 -46
  5. package/src/react/components/filters/CompactFilterSelector.js +78 -24
  6. package/src/react/components/filters/CompactFilterSelector.styles.js +17 -5
  7. package/src/react/components/filters/DefaultColumnFilter.js +191 -57
  8. package/src/react/components/filters/DefaultExternalFilters.js +245 -182
  9. package/src/react/components/filters/DefaultSearch.js +131 -92
  10. package/src/react/components/filters/DefaultSearch.styles.js +65 -70
  11. package/src/react/components/form/DefaultForm.js +172 -151
  12. package/src/react/components/form/DefaultForm.styles.js +71 -66
  13. package/src/react/components/help/DefaultTooltip.js +87 -87
  14. package/src/react/components/help/DefaultTooltip.styles.js +61 -61
  15. package/src/react/components/inputs/DefaultDateInput.js +150 -0
  16. package/src/react/components/inputs/DefaultInput.js +162 -127
  17. package/src/react/components/inputs/DefaultInput.styles.js +6 -0
  18. package/src/react/components/inputs/DefaultSelect.js +320 -152
  19. package/src/react/components/inputs/defaultInputUtils.js +40 -19
  20. package/src/react/components/map/DefaultGoogleMap.styles.js +115 -9
  21. package/src/react/components/map/DefaultGoogleMap.web.js +560 -446
  22. package/src/react/components/map/DefaultMap.native.js +73 -71
  23. package/src/react/components/map/DefaultMap.styles.js +16 -16
  24. package/src/react/components/map/DefaultMap.web.js +68 -66
  25. package/src/react/components/map/DefaultNativeMap.native.js +307 -183
  26. package/src/react/components/map/DefaultNativeMap.shared.js +439 -340
  27. package/src/react/components/map/DefaultNativeMap.styles.js +156 -122
  28. package/src/react/components/table/DefaultColumnMenu.js +108 -0
  29. package/src/react/components/table/DefaultDebug.js +233 -0
  30. package/src/react/components/table/DefaultFiltersModal.js +142 -0
  31. package/src/react/components/table/DefaultModalButton.js +18 -0
  32. package/src/react/components/table/DefaultSearchModal.js +49 -0
  33. package/src/react/components/table/DefaultTable.js +345 -1846
  34. package/src/react/components/table/DefaultTable.styles.js +247 -136
  35. package/src/react/components/table/DefaultTable.utils.js +502 -0
  36. package/src/react/components/table/DefaultTableBody.js +26 -0
  37. package/src/react/components/table/DefaultTableCards.js +171 -0
  38. package/src/react/components/table/DefaultTableControls.js +208 -0
  39. package/src/react/components/table/DefaultTableEmptyState.js +22 -0
  40. package/src/react/components/table/DefaultTableFooter.js +172 -0
  41. package/src/react/components/table/DefaultTableInput.js +145 -0
  42. package/src/react/components/table/DefaultTableRows.js +340 -0
  43. package/src/react/components/table/DefaultTableSearch.js +100 -0
  44. package/src/react/components/table/DefaultTableToolbar.js +98 -0
  45. package/src/react/components/table/DefaultToolbarAction.js +56 -0
  46. package/src/react/components/table/useDefaultTablePagination.js +370 -0
  47. package/src/react/components/table/useDefaultTableSorting.js +126 -0
  48. package/src/react/components/table/useDefaultTableTheme.js +58 -0
  49. package/src/react/index.js +10 -10
  50. package/src/react/utils/tableVisibleColumnsPreferences.js +239 -211
  51. package/src/store/default/actions.js +51 -13
  52. package/src/store/default/getters.js +10 -8
  53. package/src/store/default/mutation_types.js +4 -2
  54. package/src/store/default/mutations.js +10 -1
  55. package/src/tests/react/components/errors/DefaultErrors.test.js +162 -162
  56. package/src/tests/react/components/files/DefaultFile.test.js +80 -80
  57. package/src/tests/react/components/form/DefaultForm.test.js +81 -0
  58. package/src/tests/react/components/inputs/DefaultSelect.test.js +185 -0
  59. package/src/tests/react/components/table/DefaultTable.test.js +1183 -0
  60. package/src/tests/react/store/defaultActions.test.js +59 -0
  61. package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +247 -168
@@ -1,192 +1,360 @@
1
- import React, { useEffect, useMemo, useState } from 'react';
2
- import {
3
- Modal,
4
- ScrollView,
5
- Text,
6
- TextInput,
7
- TouchableOpacity,
8
- View,
9
- } from 'react-native';
1
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ Modal,
4
+ ScrollView,
5
+ Text,
6
+ TextInput,
7
+ TouchableOpacity,
8
+ View,
9
+ } from 'react-native';
10
10
  import Icon from 'react-native-vector-icons/Feather';
11
+ import { getAllStores, useStore } from '@store';
11
12
  import {
12
13
  buildOptionsFromColumn,
13
14
  getColumnKey,
14
15
  isEditableColumn,
16
+ mapOptions,
15
17
  normalizeOptionKey,
16
18
  normalizeText,
17
19
  resolveCellText,
18
20
  resolveEditValue,
21
+ resolveStoreNameFromList,
19
22
  } from './defaultInputUtils';
20
23
  import inputStyles from './DefaultInput.styles';
21
24
  import styles from './DefaultSelect.styles';
22
25
 
23
- const DefaultSelect = ({
24
- accentColor = '#2563EB',
25
- autoSave = true,
26
+ const COMPANY_SCOPED_LIST_STORES = new Set([
27
+ 'categories',
28
+ 'paymentType',
29
+ 'wallet',
30
+ ]);
31
+
32
+ const normalizeCollectionItems = response => {
33
+ if (Array.isArray(response)) return response;
34
+ if (Array.isArray(response?.member)) return response.member;
35
+ if (Array.isArray(response?.['hydra:member'])) return response['hydra:member'];
36
+ return [];
37
+ };
38
+
39
+ const stableSerialize = value => {
40
+ if (value === null || value === undefined) return 'null';
41
+ if (Array.isArray(value)) return `[${value.map(item => stableSerialize(item)).join(',')}]`;
42
+ if (value instanceof Date) return JSON.stringify(value.toISOString());
43
+ if (value && typeof value === 'object') {
44
+ return `{${Object.keys(value)
45
+ .sort()
46
+ .map(key => `${JSON.stringify(key)}:${stableSerialize(value[key])}`)
47
+ .join(',')}}`;
48
+ }
49
+
50
+ return JSON.stringify(value);
51
+ };
52
+
53
+ const resolveListActionName = list =>
54
+ normalizeText(list).split('/')[1] || 'getItems';
55
+
56
+ const resolveCompanyScopedParams = ({ currentCompanyId, listStoreName }) => {
57
+ if (!currentCompanyId || !COMPANY_SCOPED_LIST_STORES.has(listStoreName)) {
58
+ return {};
59
+ }
60
+
61
+ return listStoreName === 'categories'
62
+ ? { company: currentCompanyId }
63
+ : { people: currentCompanyId };
64
+ };
65
+
66
+ const resolveColumnListLoadParams = ({
26
67
  column,
27
- columns = [],
28
- containerStyle = null,
29
- displayValue,
30
- editing = false,
31
- getOptionsForColumn = null,
32
- label = '',
33
- numberOfLines = 1,
68
+ currentCompanyId,
69
+ requestParams = {},
70
+ searchValue = '',
71
+ }) => {
72
+ const listStoreName = resolveStoreNameFromList(column?.list);
73
+ const resolvedCustomParams = typeof column?.listRequestParams === 'function'
74
+ ? column.listRequestParams({ currentCompanyId, requestParams })
75
+ : column?.listRequestParams;
76
+ const customParams =
77
+ resolvedCustomParams &&
78
+ typeof resolvedCustomParams === 'object' &&
79
+ !Array.isArray(resolvedCustomParams)
80
+ ? resolvedCustomParams
81
+ : {};
82
+
83
+ return {
84
+ ...resolveCompanyScopedParams({ currentCompanyId, listStoreName }),
85
+ ...customParams,
86
+ ...(normalizeText(searchValue)
87
+ ? { [column?.listSearchParam || column?.searchParam || 'search']: normalizeText(searchValue) }
88
+ : {}),
89
+ };
90
+ };
91
+
92
+ const DefaultSelect = ({
93
+ accentColor = '#2563EB',
94
+ autoSave = true,
95
+ column,
96
+ columns = [],
97
+ containerStyle = null,
98
+ displayValue,
99
+ editing = false,
100
+ getOptionsForColumn = null,
101
+ label = '',
102
+ numberOfLines = 1,
103
+ onBeforeOpen = null,
34
104
  onCancelEditing = null,
35
105
  onChangeValue = null,
36
106
  onSave = null,
107
+ onSearchChange = null,
37
108
  onStartEditing = null,
38
- readTextStyle = null,
39
- row = {},
40
- saving = false,
41
- showLabel = false,
42
- storeName = '',
43
- value,
44
- variant = 'cell',
109
+ readTextStyle = null,
110
+ row = {},
111
+ saving = false,
112
+ showLabel = false,
113
+ storeName = '',
114
+ value,
115
+ variant = 'cell',
45
116
  }) => {
117
+ const peopleStore = useStore('people');
118
+ const ownerStore = useStore(storeName);
119
+ const loadedListKeysRef = useRef(new Set());
46
120
  const [isOpen, setIsOpen] = useState(false);
121
+ const [loadedItems, setLoadedItems] = useState([]);
47
122
  const [searchText, setSearchText] = useState('');
123
+ const currentCompanyId = peopleStore?.getters?.currentCompany?.id;
124
+ const requestParams = ownerStore?.getters?.configs?.requestParams || {};
125
+
126
+ const fieldName = getColumnKey(column);
127
+ const selectedKey = normalizeOptionKey(value ?? resolveEditValue(column, row));
128
+ const options = useMemo(() => {
129
+ const localOptions = buildOptionsFromColumn(column, getOptionsForColumn, storeName);
130
+ const remoteOptions = mapOptions(column, loadedItems, storeName);
131
+
132
+ if (remoteOptions.length === 0) {
133
+ return localOptions;
134
+ }
48
135
 
49
- const fieldName = getColumnKey(column);
50
- const selectedKey = normalizeOptionKey(value ?? resolveEditValue(column, row));
51
- const options = useMemo(
52
- () => buildOptionsFromColumn(column, getOptionsForColumn, storeName),
53
- [column, getOptionsForColumn, storeName],
54
- );
55
- const selected = options.find(option => option.key === selectedKey);
56
- const resolvedLabel =
57
- displayValue ??
58
- selected?.label ??
59
- resolveCellText({ column, columns, row, storeName, value });
60
- const canEdit = isEditableColumn(column) && typeof onStartEditing === 'function';
61
- const isForm = variant === 'form';
136
+ const seenKeys = new Set(remoteOptions.map(option => option.key));
137
+ return [
138
+ ...remoteOptions,
139
+ ...localOptions.filter(option => !seenKeys.has(option.key)),
140
+ ];
141
+ }, [column, getOptionsForColumn, loadedItems, storeName]);
142
+ const selected = options.find(option => option.key === selectedKey);
143
+ const resolvedLabel =
144
+ displayValue ??
145
+ selected?.label ??
146
+ resolveCellText({ column, columns, row, storeName, value });
147
+ const canEdit = isEditableColumn(column) && typeof onStartEditing === 'function';
148
+ const isForm = variant === 'form';
62
149
  const filteredOptions = useMemo(() => {
63
150
  const query = normalizeText(searchText).toLowerCase();
64
- if (!query) return options;
151
+ if (!query || onSearchChange) return options;
65
152
  return options.filter(option =>
66
- normalizeText(option.label).toLowerCase().includes(query) ||
67
- normalizeText(option.key).toLowerCase().includes(query),
68
- );
153
+ normalizeText(option.label).toLowerCase().includes(query) ||
154
+ normalizeText(option.key).toLowerCase().includes(query),
155
+ );
69
156
  }, [options, searchText]);
70
157
 
71
- useEffect(() => {
72
- if (editing && autoSave) setIsOpen(true);
73
- }, [autoSave, editing]);
158
+ const applyLoadedItems = useCallback(value => {
159
+ const nextItems = normalizeCollectionItems(value);
160
+ if (nextItems.length > 0 || Array.isArray(value)) {
161
+ setLoadedItems(nextItems);
162
+ }
163
+ }, []);
74
164
 
75
- const close = () => {
76
- setIsOpen(false);
77
- setSearchText('');
78
- if (autoSave) onCancelEditing?.();
79
- };
165
+ const loadRemoteOptions = useCallback((searchValue = '') => {
166
+ const listStoreName = resolveStoreNameFromList(column?.list);
167
+ const actionName = resolveListActionName(column?.list);
168
+ const stores = getAllStores?.() || {};
169
+ const listStore = stores?.[listStoreName];
170
+ const listAction = listStore?.actions?.[actionName];
80
171
 
81
- const selectOption = option => {
82
- setIsOpen(false);
83
- setSearchText('');
172
+ if (!listStoreName || typeof listAction !== 'function') {
173
+ return Promise.resolve([]);
174
+ }
84
175
 
85
- if (autoSave) {
86
- onSave?.({
87
- value: option.key,
88
- label: option.label,
89
- object: option.raw,
176
+ const listLoadParams = resolveColumnListLoadParams({
177
+ column,
178
+ currentCompanyId,
179
+ requestParams,
180
+ searchValue,
181
+ });
182
+ const loadKey = `${getColumnKey(column)}:${listStoreName}:${actionName}:${stableSerialize(listLoadParams)}`;
183
+
184
+ if (
185
+ !normalizeText(searchValue) &&
186
+ loadedListKeysRef.current.has(loadKey) &&
187
+ loadedItems.length > 0
188
+ ) {
189
+ return Promise.resolve(loadedItems);
190
+ }
191
+
192
+ if (!normalizeText(searchValue)) {
193
+ loadedListKeysRef.current.add(loadKey);
194
+ }
195
+
196
+ return Promise.resolve(
197
+ listAction({
198
+ ...listLoadParams,
199
+ __storeMeta: {
200
+ dedupeKey: `default-select-list-options:${loadKey}`,
201
+ skipSystemError: true,
202
+ },
203
+ }),
204
+ )
205
+ .then(response => {
206
+ applyLoadedItems(response);
207
+ return response;
208
+ })
209
+ .catch(error => {
210
+ loadedListKeysRef.current.delete(loadKey);
211
+ throw error;
90
212
  });
213
+ }, [applyLoadedItems, column, currentCompanyId, loadedItems, requestParams]);
214
+
215
+ const handleSearchChange = useCallback(value => {
216
+ setSearchText(value);
217
+ const maybeLoad = onSearchChange ? onSearchChange(value) : loadRemoteOptions(value);
218
+ if (maybeLoad && typeof maybeLoad.then === 'function') {
219
+ maybeLoad.then(applyLoadedItems).catch(() => {});
91
220
  return;
92
221
  }
93
222
 
94
- onChangeValue?.(option.key, {
95
- value: option.key,
96
- label: option.label,
97
- object: option.raw,
98
- });
99
- };
100
-
101
- const open = () => {
102
- if (!canEdit && !isForm) return;
103
- if (!editing && !isForm) onStartEditing?.();
223
+ applyLoadedItems(maybeLoad);
224
+ }, [applyLoadedItems, loadRemoteOptions, onSearchChange]);
225
+
226
+ const close = () => {
227
+ setIsOpen(false);
228
+ setSearchText('');
229
+ if (autoSave) onCancelEditing?.();
230
+ };
231
+
232
+ const selectOption = option => {
233
+ setIsOpen(false);
234
+ setSearchText('');
235
+
236
+ if (autoSave) {
237
+ onSave?.({
238
+ value: option.key,
239
+ label: option.label,
240
+ object: option.raw,
241
+ });
242
+ return;
243
+ }
244
+
245
+ onChangeValue?.(option.key, {
246
+ value: option.key,
247
+ label: option.label,
248
+ object: option.raw,
249
+ });
250
+ };
251
+
252
+ const open = async () => {
253
+ if (!canEdit && !isForm) return;
254
+ try {
255
+ if (!editing && !isForm) {
256
+ const maybePromise = onStartEditing?.();
257
+ if (maybePromise && typeof maybePromise.then === 'function') {
258
+ await maybePromise;
259
+ }
260
+ }
261
+
262
+ const maybeLoad = onBeforeOpen?.();
263
+ if (maybeLoad && typeof maybeLoad.then === 'function') {
264
+ applyLoadedItems(await maybeLoad);
265
+ } else {
266
+ applyLoadedItems(maybeLoad);
267
+ }
268
+ } catch (error) {
269
+ return;
270
+ }
104
271
  setIsOpen(true);
272
+ loadRemoteOptions().catch(() => {});
105
273
  };
106
-
107
- return (
108
- <View style={[inputStyles.wrap, containerStyle]}>
109
- {showLabel ? <Text style={inputStyles.fieldLabel}>{label || column?.label || fieldName}</Text> : null}
110
-
111
- <TouchableOpacity
112
- style={[inputStyles.readButton, isForm ? inputStyles.readButtonForm : null]}
113
- activeOpacity={canEdit || isForm ? 0.78 : 1}
114
- disabled={!canEdit && !isForm}
115
- onPress={open}
116
- >
117
- <Text
118
- style={[
119
- inputStyles.readText,
120
- resolvedLabel === '-' ? inputStyles.mutedText : null,
121
- readTextStyle,
122
- ]}
123
- numberOfLines={numberOfLines}
124
- >
125
- {resolvedLabel || '-'}
126
- </Text>
127
- {saving ? (
128
- <Text style={[inputStyles.savingText, { color: accentColor }]}>Salvando</Text>
129
- ) : canEdit || isForm ? (
130
- <Icon style={inputStyles.editIcon} name="chevron-down" size={14} color="#64748B" />
131
- ) : null}
132
- </TouchableOpacity>
133
-
134
- <Modal visible={isOpen} transparent animationType="fade" onRequestClose={close}>
135
- <View style={styles.overlay}>
136
- <View style={styles.modalCard}>
137
- <View style={styles.modalHeader}>
138
- <Text style={styles.modalTitle} numberOfLines={1}>
139
- {label || column?.label || fieldName}
140
- </Text>
141
- <TouchableOpacity style={inputStyles.cancelButton} onPress={close}>
142
- <Icon name="x" size={16} color="#64748B" />
143
- </TouchableOpacity>
144
- </View>
145
-
146
- <View style={styles.searchBox}>
147
- <TextInput
274
+
275
+ return (
276
+ <View style={[inputStyles.wrap, containerStyle]}>
277
+ {showLabel ? <Text style={inputStyles.fieldLabel}>{label || column?.label || fieldName}</Text> : null}
278
+
279
+ <TouchableOpacity
280
+ style={[inputStyles.readButton, isForm ? inputStyles.readButtonForm : null]}
281
+ activeOpacity={canEdit || isForm ? 0.78 : 1}
282
+ disabled={!canEdit && !isForm}
283
+ onPress={open}
284
+ >
285
+ <Text
286
+ style={[
287
+ inputStyles.readText,
288
+ resolvedLabel === '-' ? inputStyles.mutedText : null,
289
+ readTextStyle,
290
+ ]}
291
+ numberOfLines={numberOfLines}
292
+ >
293
+ {resolvedLabel || '-'}
294
+ </Text>
295
+ {saving ? (
296
+ <Text style={[inputStyles.savingText, { color: accentColor }]}>Salvando</Text>
297
+ ) : canEdit || isForm ? (
298
+ <Icon style={inputStyles.editIcon} name="chevron-down" size={14} color="#64748B" />
299
+ ) : null}
300
+ </TouchableOpacity>
301
+
302
+ <Modal visible={isOpen} transparent animationType="fade" onRequestClose={close}>
303
+ <View style={styles.overlay}>
304
+ <View style={styles.modalCard}>
305
+ <View style={styles.modalHeader}>
306
+ <Text style={styles.modalTitle} numberOfLines={1}>
307
+ {label || column?.label || fieldName}
308
+ </Text>
309
+ <TouchableOpacity style={inputStyles.cancelButton} onPress={close}>
310
+ <Icon name="x" size={16} color="#64748B" />
311
+ </TouchableOpacity>
312
+ </View>
313
+
314
+ <View style={styles.searchBox}>
315
+ <TextInput
148
316
  style={[inputStyles.input, inputStyles.formInput]}
149
317
  value={searchText}
150
318
  placeholder={global.t?.t(storeName, 'input', column?.searchParam || 'search')}
151
- onChangeText={setSearchText}
319
+ onChangeText={handleSearchChange}
152
320
  />
153
- </View>
154
-
155
- <ScrollView keyboardShouldPersistTaps="handled">
156
- {filteredOptions.length > 0 ? (
157
- filteredOptions.map(option => {
158
- const isSelected = option.key === selectedKey;
159
- return (
160
- <TouchableOpacity
161
- key={`${option.key}_${option.label}`}
162
- style={styles.optionRow}
163
- activeOpacity={0.78}
164
- onPress={() => selectOption(option)}
165
- >
166
- <Icon
167
- name={isSelected ? 'check-circle' : 'circle'}
168
- size={15}
169
- color={isSelected ? accentColor : '#CBD5E1'}
170
- />
171
- <Text style={styles.optionText}>
172
- {option.label}
173
- </Text>
174
- </TouchableOpacity>
175
- );
176
- })
177
- ) : (
178
- <View style={styles.emptyBox}>
179
- <Text style={styles.emptyText}>
180
- Nenhum resultado encontrado
181
- </Text>
182
- </View>
183
- )}
184
- </ScrollView>
185
- </View>
186
- </View>
187
- </Modal>
188
- </View>
189
- );
190
- };
191
-
192
- export default DefaultSelect;
321
+ </View>
322
+
323
+ <ScrollView keyboardShouldPersistTaps="handled">
324
+ {filteredOptions.length > 0 ? (
325
+ filteredOptions.map(option => {
326
+ const isSelected = option.key === selectedKey;
327
+ return (
328
+ <TouchableOpacity
329
+ key={`${option.key}_${option.label}`}
330
+ style={styles.optionRow}
331
+ activeOpacity={0.78}
332
+ onPress={() => selectOption(option)}
333
+ >
334
+ <Icon
335
+ name={isSelected ? 'check-circle' : 'circle'}
336
+ size={15}
337
+ color={isSelected ? accentColor : '#CBD5E1'}
338
+ />
339
+ <Text style={styles.optionText}>
340
+ {option.label}
341
+ </Text>
342
+ </TouchableOpacity>
343
+ );
344
+ })
345
+ ) : (
346
+ <View style={styles.emptyBox}>
347
+ <Text style={styles.emptyText}>
348
+ Nenhum resultado encontrado
349
+ </Text>
350
+ </View>
351
+ )}
352
+ </ScrollView>
353
+ </View>
354
+ </View>
355
+ </Modal>
356
+ </View>
357
+ );
358
+ };
359
+
360
+ export default DefaultSelect;
@@ -25,7 +25,18 @@ const DEFAULT_TRANSLATABLE_FIELDS = new Set([
25
25
  'status',
26
26
  ]);
27
27
 
28
- export const getColumnKey = column => column?.key || column?.name || '';
28
+ export const getColumnKey = column => column?.key || column?.name || '';
29
+
30
+ export const isDateColumn = column =>
31
+ column?.inputType === 'date' ||
32
+ column?.type === 'date';
33
+
34
+ export const isDateRangeColumn = column =>
35
+ column?.inputType === 'date-range' ||
36
+ column?.type === 'range-date';
37
+
38
+ export const isDateLikeColumn = column =>
39
+ isDateColumn(column) || isDateRangeColumn(column);
29
40
 
30
41
  const normalizeColumnKey = value =>
31
42
  normalizeText(value)
@@ -114,7 +125,8 @@ export const resolveOptionLabel = (column, option, storeName = '') => {
114
125
  return translateOptionLabel(rawLabel);
115
126
  };
116
127
 
117
- export const resolveStoreNameFromList = list => normalizeText(list).split('/')[0] || '';
128
+ export const resolveStoreNameFromList = list =>
129
+ typeof list === 'string' ? normalizeText(list).split('/')[0] || '' : '';
118
130
 
119
131
  const filterItemsByListRequestParams = (items = [], column = null) => {
120
132
  const requestParams =
@@ -164,13 +176,17 @@ export const mapOptions = (column, items = [], storeName = '') => {
164
176
  return options;
165
177
  };
166
178
 
167
- export const buildOptionsFromColumn = (column, getOptionsForColumn = null, storeName = '') => {
168
- const explicitOptions = getOptionsForColumn?.(column);
169
- if (Array.isArray(explicitOptions) && explicitOptions.length > 0) {
170
- return mapOptions(column, explicitOptions, storeName);
171
- }
172
-
173
- const listStoreName = resolveStoreNameFromList(column?.list);
179
+ export const buildOptionsFromColumn = (column, getOptionsForColumn = null, storeName = '') => {
180
+ const explicitOptions = getOptionsForColumn?.(column);
181
+ if (Array.isArray(explicitOptions) && explicitOptions.length > 0) {
182
+ return mapOptions(column, explicitOptions, storeName);
183
+ }
184
+
185
+ if (Array.isArray(column?.list)) {
186
+ return mapOptions(column, column.list, storeName);
187
+ }
188
+
189
+ const listStoreName = resolveStoreNameFromList(column?.list);
174
190
  if (!listStoreName) return [];
175
191
 
176
192
  const listStore = getAllStores()?.[listStoreName];
@@ -209,9 +225,9 @@ export const resolveEditValue = (column, row, value) => {
209
225
  return normalizeText(column.editFormat(rawValue, column, row, true));
210
226
  }
211
227
 
212
- if ((column?.inputType === 'date' || column?.inputType === 'date-range') && rawValue) {
213
- return normalizeText(Formatter.formatDateYmdTodmY(rawValue));
214
- }
228
+ if (isDateLikeColumn(column) && rawValue) {
229
+ return normalizeText(Formatter.formatDateYmdTodmY(rawValue));
230
+ }
215
231
 
216
232
  return normalizeText(rawValue);
217
233
  };
@@ -221,10 +237,15 @@ export const formatSaveValue = (column, value, row) => {
221
237
  return column.saveFormat(value, column, row);
222
238
  }
223
239
 
224
- if (value && typeof value === 'object') {
225
- if (value.value) return Number.isNaN(Number(value.value)) ? value.value : Number(value.value);
226
- if (value['@id']) return value['@id'];
227
- }
228
-
229
- return Number.isNaN(Number(value)) || value === '' ? value : Number(value);
230
- };
240
+ if (value && typeof value === 'object') {
241
+ if (value.value) return Number.isNaN(Number(value.value)) ? value.value : Number(value.value);
242
+ if (value['@id']) return value['@id'];
243
+ }
244
+
245
+ if (isDateLikeColumn(column) && value) {
246
+ const normalizedDate = Formatter.buildAmericanDate(String(value));
247
+ return normalizedDate || value;
248
+ }
249
+
250
+ return Number.isNaN(Number(value)) || value === '' ? value : Number(value);
251
+ };