@controleonline/ui-default 1.0.264 → 1.0.266

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 (46) hide show
  1. package/AGENTS.md +3 -2
  2. package/package.json +21 -18
  3. package/src/react/components/errors/DefaultErrors.js +64 -82
  4. package/src/react/components/filters/CompactFilterSelector.js +58 -23
  5. package/src/react/components/filters/CompactFilterSelector.styles.js +17 -5
  6. package/src/react/components/filters/DefaultColumnFilter.js +177 -45
  7. package/src/react/components/filters/DefaultExternalFilters.js +201 -143
  8. package/src/react/components/filters/DefaultSearch.js +27 -25
  9. package/src/react/components/form/DefaultForm.js +72 -53
  10. package/src/react/components/form/DefaultForm.styles.js +71 -66
  11. package/src/react/components/inputs/DefaultDateInput.js +150 -0
  12. package/src/react/components/inputs/DefaultInput.js +44 -11
  13. package/src/react/components/inputs/DefaultInput.styles.js +6 -0
  14. package/src/react/components/inputs/DefaultSelect.js +200 -43
  15. package/src/react/components/inputs/defaultInputUtils.js +40 -19
  16. package/src/react/components/table/DefaultColumnMenu.js +108 -0
  17. package/src/react/components/table/DefaultDebug.js +234 -0
  18. package/src/react/components/table/DefaultFiltersModal.js +142 -0
  19. package/src/react/components/table/DefaultModalButton.js +18 -0
  20. package/src/react/components/table/DefaultSearchModal.js +49 -0
  21. package/src/react/components/table/DefaultTable.js +322 -1964
  22. package/src/react/components/table/DefaultTable.styles.js +234 -102
  23. package/src/react/components/table/DefaultTable.utils.js +502 -0
  24. package/src/react/components/table/DefaultTableBody.js +26 -0
  25. package/src/react/components/table/DefaultTableCards.js +171 -0
  26. package/src/react/components/table/DefaultTableControls.js +205 -0
  27. package/src/react/components/table/DefaultTableEmptyState.js +22 -0
  28. package/src/react/components/table/DefaultTableFooter.js +172 -0
  29. package/src/react/components/table/DefaultTableInput.js +145 -0
  30. package/src/react/components/table/DefaultTableRows.js +344 -0
  31. package/src/react/components/table/DefaultTableSearch.js +100 -0
  32. package/src/react/components/table/DefaultTableToolbar.js +98 -0
  33. package/src/react/components/table/DefaultToolbarAction.js +56 -0
  34. package/src/react/components/table/useDefaultTablePagination.js +370 -0
  35. package/src/react/components/table/useDefaultTableSorting.js +126 -0
  36. package/src/react/components/table/useDefaultTableTheme.js +52 -0
  37. package/src/react/utils/tableVisibleColumnsPreferences.js +239 -211
  38. package/src/store/default/actions.js +51 -13
  39. package/src/store/default/getters.js +10 -8
  40. package/src/store/default/mutation_types.js +4 -2
  41. package/src/store/default/mutations.js +10 -1
  42. package/src/tests/react/components/form/DefaultForm.test.js +81 -0
  43. package/src/tests/react/components/inputs/DefaultSelect.test.js +106 -14
  44. package/src/tests/react/components/table/DefaultTable.test.js +1318 -334
  45. package/src/tests/react/store/defaultActions.test.js +59 -0
  46. package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +247 -168
package/AGENTS.md CHANGED
@@ -10,8 +10,9 @@
10
10
  - A tela deve passar preferencialmente apenas `storeName` e contexto minimo, como `requestParams`, `onRowPress`, `renderCard`, `onAdd`, `onEdit` ou componentes extras realmente especificos.
11
11
  - O componente pode receber componentes de composicao para variacoes visuais, como `renderCard`, add personalizado, toolbar customizada, acoes auxiliares e modais de apoio; isso nao muda o contrato de que a listagem continua sendo responsabilidade do `DefaultTable`.
12
12
  - Quando `data` nao e informada e existe `storeName`, o `DefaultTable` trabalha em modo automatico e carrega os itens pelas actions do store.
13
- - O componente e responsavel por busca, ordenacao, filtros, paginação, infinite scroll, loading, erro, resumo, visibilidade de colunas, modo desktop e modo card no mobile.
14
- - O store nao e fallback: ele e a regra. Se `columns` define um comportamento, o `DefaultTable` deve obedecer sem recriar uma segunda regra na tela.
13
+ - O componente e responsavel por busca, ordenacao, filtros, paginação, infinite scroll, loading, erro, resumo, visibilidade de colunas, modo desktop e modo card no mobile.
14
+ - Preferencias de usuario do `DefaultTable`, incluindo filtros, busca, ordenacao, modo de exibicao e visibilidade de colunas, devem ser salvas em `localStorage` na chave `default-table`, sempre no formato `[store][rota] = valores`. Nunca usar chave apenas por store ou apenas por rota.
15
+ - O store nao e fallback: ele e a regra. Se `columns` define um comportamento, o `DefaultTable` deve obedecer sem recriar uma segunda regra na tela.
15
16
  - `columns` e a fonte de verdade para label, visibilidade, ordem, `editable`, `sortable`, `inputType`, `list`, `format`, `formatList`, `formatFilter`, `saveFormat`, `searchParam`, `externalFilter`, `summary` e `defaultSort`. `show:false` e um alias aceito para ocultar coluna, junto com `visible:false` e `table:false`.
16
17
  - Se uma coluna vier como `editable: false`, ela continua nao editavel mesmo que seja ordenavel. Se vier como `sortable: true`, a ordenacao precisa continuar funcionando.
17
18
  - `inputType: 'date-range'` exige filtro de intervalo. `externalFilter: true` habilita o filtro externo quando a listagem suporta esse contrato.
package/package.json CHANGED
@@ -1,18 +1,21 @@
1
- {
2
- "name": "@controleonline/ui-default",
3
- "version": "1.0.264",
4
- "author": "ControleOnline",
5
- "description": "ControleOnline Default Quasar UI Component",
6
- "license": "MIT",
7
- "main": "src/react/index.js",
8
- "scripts": {},
9
- "repository": {
10
- "type": "git",
11
- "url": "https://github.com/ControleOnline/conline-quasar-ui"
12
- },
13
- "bugs": "",
14
- "homepage": "https://www.controleonline.com/devs/plugins",
15
- "browserslist": [
16
- "last 1 version, not dead, ie >= 11"
17
- ]
18
- }
1
+ {
2
+ "name": "@controleonline/ui-default",
3
+ "version": "1.0.266",
4
+ "author": "ControleOnline",
5
+ "description": "ControleOnline Default Quasar UI Component",
6
+ "license": "MIT",
7
+ "main": "src/react/index.js",
8
+ "scripts": {},
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/ControleOnline/conline-quasar-ui"
12
+ },
13
+ "bugs": "",
14
+ "homepage": "https://www.controleonline.com/devs/plugins",
15
+ "browserslist": [
16
+ "last 1 version, not dead, ie >= 11"
17
+ ],
18
+ "dependencies": {
19
+ "@controleonline/ui-common": "*"
20
+ }
21
+ }
@@ -7,28 +7,16 @@ const AUTO_CLOSE_MS = 5000;
7
7
 
8
8
  const resolveText = value => String(value ?? '').trim();
9
9
 
10
- const normalizeStoreNames = (store, stores) => {
11
- const resolvedStoreNames = [
12
- ...(Array.isArray(store) ? store : store ? [store] : []),
13
- ...(Array.isArray(stores) ? stores : []),
14
- ];
15
-
16
- return resolvedStoreNames.filter(name => typeof name === 'string' && name.trim()).map(name => name.trim());
17
- };
18
-
19
- const pickThemeColor = (colors, keys, fallback) => {
20
- for (const key of keys) {
21
- const value = colors?.[key];
22
-
23
- if (typeof value === 'string' && value.trim()) {
24
- return value.trim();
25
- }
26
- }
27
-
28
- return fallback;
29
- };
30
-
31
- const DefaultErrors = ({
10
+ const normalizeStoreNames = (store, stores) => {
11
+ const resolvedStoreNames = [
12
+ ...(Array.isArray(store) ? store : store ? [store] : []),
13
+ ...(Array.isArray(stores) ? stores : []),
14
+ ];
15
+
16
+ return resolvedStoreNames.filter(name => typeof name === 'string' && name.trim()).map(name => name.trim());
17
+ };
18
+
19
+ const DefaultErrors = ({
32
20
  store = null,
33
21
  stores = [],
34
22
  error = null,
@@ -44,10 +32,25 @@ const DefaultErrors = ({
44
32
  messageStyle = null,
45
33
  retryButtonStyle = null,
46
34
  retryTextStyle = null,
47
- }) => {
48
- const themeStore = useStore('theme');
49
- const themeColors = themeStore?.getters?.colors || {};
50
- const allStores = typeof useStores === 'function' ? useStores(state => state) : null;
35
+ }) => {
36
+ const themeStore = useStore('theme');
37
+ const themeColors = themeStore?.getters?.colors || {};
38
+ const palette = useMemo(
39
+ () => ({
40
+ buttonBackground: themeColors.buttonBackground,
41
+ buttonBorder: themeColors.buttonBorder,
42
+ buttonText: themeColors.buttonText,
43
+ inputErrorBorder: themeColors.inputErrorBorder,
44
+ inputErrorText: themeColors.inputErrorText,
45
+ modalBackground: themeColors.modalBackground,
46
+ modalBorder: themeColors.modalBorder,
47
+ modalCloseIcon: themeColors.modalCloseIcon,
48
+ modalOverlay: themeColors.modalOverlay,
49
+ modalText: themeColors.modalText,
50
+ }),
51
+ [themeColors],
52
+ );
53
+ const allStores = typeof useStores === 'function' ? useStores(state => state) : null;
51
54
  const storeNames = useMemo(() => normalizeStoreNames(store, stores), [store, stores]);
52
55
  const watchedStoreEntries = useMemo(() => {
53
56
  if (!allStores || storeNames.length === 0) {
@@ -90,40 +93,18 @@ const DefaultErrors = ({
90
93
  .join('|')}`
91
94
  : '';
92
95
  const activeDriverSignature = storeDriverSignature || explicitDriverSignature;
93
- const [dismissedSignatures, setDismissedSignatures] = useState(() => new Set());
94
- const timerRef = useRef(null);
95
- const textAlign = align === 'left' ? 'left' : 'center';
96
- const accentColor = pickThemeColor(
97
- themeColors,
98
- ['inputErrorBorder', 'textDanger', 'modalBorder', 'primary'],
99
- '#DC2626',
100
- );
101
- const backgroundColor = pickThemeColor(
102
- themeColors,
103
- ['inputErrorBackground', 'modalBackground', 'surface', 'background'],
104
- '#FFFFFF',
105
- );
106
- const overlayColor = pickThemeColor(
107
- themeColors,
108
- ['modalOverlay', 'overlayBackground'],
109
- 'rgba(15, 23, 42, 0.55)',
110
- );
111
- const titleColor = pickThemeColor(
112
- themeColors,
113
- ['inputErrorText', 'textDanger', 'modalHeaderText', 'text'],
114
- '#B91C1C',
115
- );
116
- const messageColor = pickThemeColor(
117
- themeColors,
118
- ['modalText', 'textSecondary', 'text'],
119
- '#334155',
120
- );
121
- const closeColor = pickThemeColor(
122
- themeColors,
123
- ['modalCloseIcon', 'textDanger', 'primary'],
124
- accentColor,
125
- );
126
- const retryAccentColor = pickThemeColor(themeColors, ['primary'], accentColor);
96
+ const [dismissedSignatures, setDismissedSignatures] = useState(() => new Set());
97
+ const timerRef = useRef(null);
98
+ const textAlign = align === 'left' ? 'left' : 'center';
99
+ const accentColor = palette.inputErrorBorder;
100
+ const backgroundColor = palette.modalBackground;
101
+ const overlayColor = palette.modalOverlay;
102
+ const titleColor = palette.inputErrorText;
103
+ const messageColor = palette.modalText;
104
+ const closeColor = palette.modalCloseIcon;
105
+ const retryBackgroundColor = palette.buttonBackground;
106
+ const retryBorderColor = palette.buttonBorder;
107
+ const retryTextColor = palette.buttonText;
127
108
 
128
109
  const clearStoreErrors = useCallback(() => {
129
110
  watchedStoreEntries.forEach(entry => {
@@ -291,27 +272,27 @@ const DefaultErrors = ({
291
272
  activeOpacity={0.85}
292
273
  onPress={onRetry}
293
274
  style={[
294
- {
295
- alignItems: 'center',
296
- alignSelf: textAlign === 'left' ? 'flex-start' : 'center',
297
- borderRadius: 999,
298
- borderWidth: 1,
299
- borderColor: retryAccentColor,
300
- backgroundColor: `${retryAccentColor}14`,
301
- marginTop: 4,
302
- paddingHorizontal: 12,
303
- paddingVertical: 8,
275
+ {
276
+ alignItems: 'center',
277
+ alignSelf: textAlign === 'left' ? 'flex-start' : 'center',
278
+ borderRadius: 999,
279
+ borderWidth: 1,
280
+ borderColor: retryBorderColor,
281
+ backgroundColor: retryBackgroundColor,
282
+ marginTop: 4,
283
+ paddingHorizontal: 12,
284
+ paddingVertical: 8,
304
285
  },
305
286
  retryButtonStyle,
306
287
  ]}
307
288
  >
308
289
  <Text
309
- style={[
310
- {
311
- color: retryAccentColor,
312
- fontSize: 12,
313
- fontWeight: '800',
314
- },
290
+ style={[
291
+ {
292
+ color: retryTextColor,
293
+ fontSize: 12,
294
+ fontWeight: '800',
295
+ },
315
296
  retryTextStyle,
316
297
  ]}
317
298
  >
@@ -328,12 +309,13 @@ const DefaultErrors = ({
328
309
  onPress={dismissPopup}
329
310
  testID="default-errors-close"
330
311
  style={[
331
- {
332
- alignItems: 'center',
333
- borderColor: accentColor,
334
- borderRadius: 999,
335
- borderWidth: 1,
336
- height: 28,
312
+ {
313
+ alignItems: 'center',
314
+ backgroundColor,
315
+ borderColor: palette.modalBorder,
316
+ borderRadius: 999,
317
+ borderWidth: 1,
318
+ height: 28,
337
319
  justifyContent: 'center',
338
320
  width: 28,
339
321
  },
@@ -1,10 +1,11 @@
1
1
  import React, { useCallback, useMemo, useState } from 'react';
2
2
  import {
3
- Modal,
4
- ScrollView,
5
- Text,
6
- TouchableOpacity,
7
- TouchableWithoutFeedback,
3
+ Modal,
4
+ ScrollView,
5
+ Text,
6
+ TextInput,
7
+ TouchableOpacity,
8
+ TouchableWithoutFeedback,
8
9
  useWindowDimensions,
9
10
  View,
10
11
  } from 'react-native';
@@ -88,17 +89,20 @@ const CompactFilterSelector = ({
88
89
  icon = 'sliders',
89
90
  label = '',
90
91
  labelCaption = '',
91
- onClose = null,
92
- onBeforeOpen = null,
93
- onSelect = null,
94
- options = [],
95
- selectedKey = '',
92
+ onClose = null,
93
+ onBeforeOpen = null,
94
+ onSearchChange = null,
95
+ onSelect = null,
96
+ options = [],
97
+ searchable = false,
98
+ selectedKey = '',
96
99
  field = '',
97
100
  store = '',
98
101
  themeColors = {},
99
102
  title = '',
100
103
  }) => {
101
- const [visible, setVisible] = useState(false);
104
+ const [visible, setVisible] = useState(false);
105
+ const [searchText, setSearchText] = useState('');
102
106
  const { width: windowWidth } = useWindowDimensions();
103
107
  const resolvedTheme = useMemo(
104
108
  () => buildTheme({ accentColor, themeColors }),
@@ -116,12 +120,13 @@ const CompactFilterSelector = ({
116
120
  const resolvedLabelCaption = labelCaption || storeFieldLabel;
117
121
  const resolvedTitle = title || storeFieldLabel;
118
122
 
119
- const closeModal = useCallback(() => {
120
- setVisible(false);
121
- onClose?.();
122
- }, [onClose]);
123
+ const closeModal = useCallback(() => {
124
+ setVisible(false);
125
+ setSearchText('');
126
+ onClose?.();
127
+ }, [onClose]);
123
128
 
124
- const openModal = useCallback(() => {
129
+ const openModal = useCallback(() => {
125
130
  if (disabled) {
126
131
  return;
127
132
  }
@@ -143,7 +148,28 @@ const CompactFilterSelector = ({
143
148
  }
144
149
 
145
150
  setVisible(true);
146
- }, [disabled, onBeforeOpen]);
151
+ }, [disabled, onBeforeOpen]);
152
+
153
+ /*
154
+ * @agents
155
+ * List filters must keep the column/store contract in the default component:
156
+ * open loads the initial page, typing delegates autocomplete to the caller
157
+ * that knows the column list store/action.
158
+ */
159
+ const handleSearchChange = useCallback(value => {
160
+ setSearchText(value);
161
+ onSearchChange?.(value);
162
+ }, [onSearchChange]);
163
+
164
+ const filteredOptions = useMemo(() => {
165
+ const normalizedSearch = normalizeText(searchText).toLowerCase();
166
+ if (!searchable || onSearchChange || !normalizedSearch) return options;
167
+
168
+ return options.filter(option =>
169
+ normalizeText(option?.label).toLowerCase().includes(normalizedSearch) ||
170
+ normalizeText(option?.key).toLowerCase().includes(normalizedSearch),
171
+ );
172
+ }, [onSearchChange, options, searchable, searchText]);
147
173
 
148
174
  const handleSelect = useCallback(optionKey => {
149
175
  const shouldClose = onSelect?.(optionKey, {
@@ -229,12 +255,21 @@ const CompactFilterSelector = ({
229
255
  </TouchableOpacity>
230
256
  </View>
231
257
 
232
- <ScrollView
233
- style={styles.modalScroll}
234
- contentContainerStyle={styles.modalContent}
235
- keyboardShouldPersistTaps="handled"
236
- >
237
- {options.map(option => {
258
+ <ScrollView
259
+ style={styles.modalScroll}
260
+ contentContainerStyle={styles.modalContent}
261
+ keyboardShouldPersistTaps="handled"
262
+ >
263
+ {searchable ? (
264
+ <TextInput
265
+ style={styles.searchInput}
266
+ value={searchText}
267
+ placeholder={global.t?.t(resolveStoreName(store), 'input', 'search')}
268
+ onChangeText={handleSearchChange}
269
+ />
270
+ ) : null}
271
+
272
+ {filteredOptions.map(option => {
238
273
  const isSelected = option.key === selectedKey;
239
274
 
240
275
  return (
@@ -88,11 +88,23 @@ const createStyles = (theme, dense = false, windowWidth = 0) => {
88
88
  modalScroll: {
89
89
  flexGrow: 0,
90
90
  },
91
- modalContent: {
92
- paddingBottom: 10,
93
- gap: 10,
94
- },
95
- modalOption: {
91
+ modalContent: {
92
+ paddingBottom: 10,
93
+ gap: 10,
94
+ },
95
+ searchInput: {
96
+ minHeight: 42,
97
+ borderWidth: 1,
98
+ borderColor: theme.optionBorderColor,
99
+ borderRadius: 14,
100
+ paddingHorizontal: 12,
101
+ paddingVertical: 8,
102
+ backgroundColor: theme.backgroundColor,
103
+ color: theme.textColor,
104
+ fontSize: 13,
105
+ fontWeight: '700',
106
+ },
107
+ modalOption: {
96
108
  minHeight: 50,
97
109
  borderWidth: 1,
98
110
  borderColor: theme.optionBorderColor,
@@ -1,62 +1,192 @@
1
- import React from 'react';
2
- import { TextInput, View } from 'react-native';
3
- import {
4
- buildOptionsFromColumn,
5
- getColumnKey,
6
- normalizeOptionKey,
7
- normalizeText,
8
- } from '../inputs/defaultInputUtils';
9
- import CompactFilterSelector from './CompactFilterSelector';
10
- import DateShortcutFilter from './DateShortcutFilter';
11
- import { resolveNextDateFilterValue } from './dateFilterSelection';
12
- import styles from './DefaultColumnFilter.styles';
1
+ import React, { useCallback, useState } from 'react';
2
+ import { TextInput, View } from 'react-native';
3
+ import { getAllStores, useStore } from '@store';
4
+ import {
5
+ buildOptionsFromColumn,
6
+ getColumnKey,
7
+ isDateLikeColumn,
8
+ mapOptions,
9
+ normalizeOptionKey,
10
+ normalizeText,
11
+ resolveStoreNameFromList,
12
+ } from '../inputs/defaultInputUtils';
13
+ import CompactFilterSelector from './CompactFilterSelector';
14
+ import DateShortcutFilter from './DateShortcutFilter';
15
+ import { resolveNextDateFilterValue } from './dateFilterSelection';
16
+ import {
17
+ normalizeCollectionItems,
18
+ resolveColumnListLoadParams,
19
+ resolveListActionName,
20
+ stableSerialize,
21
+ } from '../table/DefaultTable.utils';
22
+ import {
23
+ persistTableFiltersPreference,
24
+ resolveDefaultTablePreferenceScope,
25
+ sanitizeTableFiltersPreference,
26
+ } from '../../utils/tableVisibleColumnsPreferences';
27
+ import styles from './DefaultColumnFilter.styles';
13
28
 
14
29
  const DefaultColumnFilter = ({
15
30
  accentColor = '#2563EB',
16
31
  column,
17
32
  filters = {},
18
33
  onBeforeOpen = null,
34
+ onSearchChange = null,
19
35
  getOptionsForColumn = null,
20
36
  onChange = null,
21
37
  storeName = '',
22
38
  style = null,
23
- }) => {
24
- const fieldName = getColumnKey(column);
39
+ }) => {
40
+ const store = useStore(storeName);
41
+ const peopleStore = useStore('people');
42
+ const configs = store?.getters?.configs || {};
43
+ const [loadedItems, setLoadedItems] = useState([]);
44
+ const [loadedKey, setLoadedKey] = useState('');
45
+ const fieldName = getColumnKey(column);
46
+ const currentCompanyId = peopleStore?.getters?.currentCompany?.id;
47
+ const requestChange = useCallback(
48
+ (nextFieldName, value) => {
49
+ if (typeof onChange === 'function') {
50
+ onChange(nextFieldName, value);
51
+ return;
52
+ }
53
+
54
+ const currentFilters = store?.getters?.filters || {};
55
+ const nextFilters = { ...(currentFilters || {}) };
56
+ const isEmpty =
57
+ value === null ||
58
+ value === undefined ||
59
+ normalizeText(value) === '' ||
60
+ (Array.isArray(value) && value.length === 0);
61
+
62
+ if (isEmpty) delete nextFilters[nextFieldName];
63
+ else nextFilters[nextFieldName] = value;
64
+
65
+ const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
66
+ const tablePreferenceScope =
67
+ configs.tablePreferenceScope ||
68
+ resolveDefaultTablePreferenceScope({ storeName });
69
+ const persistedFilters = sanitizeTableFiltersPreference({
70
+ columns,
71
+ filters: nextFilters,
72
+ });
73
+
74
+ persistTableFiltersPreference(tablePreferenceScope, persistedFilters);
75
+
76
+ if (typeof store?.actions?.setFilters === 'function') {
77
+ store.actions.setFilters(nextFilters);
78
+ } else if (store?.getters) {
79
+ store.getters.filters = nextFilters;
80
+ }
81
+
82
+ configs.onFilterChange?.(nextFilters);
83
+ },
84
+ [configs, onChange, store, storeName],
85
+ );
86
+ const resolveOptions = useCallback(
87
+ targetColumn => {
88
+ const configOptions = configs.getOptionsForColumn?.(targetColumn);
89
+ const propOptions = getOptionsForColumn?.(targetColumn);
90
+
91
+ if (Array.isArray(configOptions)) return configOptions;
92
+ if (Array.isArray(propOptions)) return propOptions;
93
+ if (targetColumn === column && loadedItems.length > 0) return loadedItems;
94
+
95
+ return undefined;
96
+ },
97
+ [column, configs, getOptionsForColumn, loadedItems],
98
+ );
99
+ const loadListOptions = useCallback(
100
+ (searchValue = '') => {
101
+ const listStoreName = resolveStoreNameFromList(column?.list);
102
+ const actionName = resolveListActionName(column?.list);
103
+ const listStore = getAllStores?.()?.[listStoreName];
104
+ const listAction = listStore?.actions?.[actionName];
105
+
106
+ if (!listStoreName || typeof listAction !== 'function') {
107
+ return Promise.resolve([]);
108
+ }
109
+
110
+ const listLoadParams = resolveColumnListLoadParams({
111
+ column,
112
+ currentCompanyId,
113
+ requestParams: configs.requestParams || {},
114
+ searchValue,
115
+ });
116
+ const nextLoadedKey = stableSerialize({
117
+ actionName,
118
+ listLoadParams,
119
+ listStoreName,
120
+ });
121
+
122
+ if (!normalizeText(searchValue) && loadedKey === nextLoadedKey && loadedItems.length > 0) {
123
+ return Promise.resolve(loadedItems);
124
+ }
125
+
126
+ return Promise.resolve(
127
+ listAction({
128
+ ...listLoadParams,
129
+ __storeMeta: {
130
+ dedupeKey: `default-column-filter-list:${fieldName}:${nextLoadedKey}`,
131
+ skipSystemError: true,
132
+ },
133
+ }),
134
+ )
135
+ .then(response => {
136
+ const items = normalizeCollectionItems(response);
137
+ setLoadedItems(items);
138
+ setLoadedKey(nextLoadedKey);
139
+ return items;
140
+ })
141
+ .catch(() => []);
142
+ },
143
+ [
144
+ column,
145
+ configs.requestParams,
146
+ currentCompanyId,
147
+ fieldName,
148
+ loadedItems,
149
+ loadedKey,
150
+ ],
151
+ );
25
152
 
26
153
  if (column?.filter === false || column?.filters === false) {
27
154
  return <View style={style} />;
28
155
  }
29
156
 
30
- if (column?.inputType === 'date-range' || column?.type === 'range-date') {
157
+ if (isDateLikeColumn(column)) {
31
158
  const filterValue = filters?.[fieldName] || {};
32
159
  return (
33
160
  <View style={[style, styles.filterCell]}>
34
- <DateShortcutFilter
35
- dense
36
- store={storeName}
37
- field={fieldName}
38
- value={filterValue.shortcut || 'all'}
161
+ <DateShortcutFilter
162
+ dense
163
+ store={storeName}
164
+ field={fieldName}
165
+ labelCaption={global.t?.t(storeName, 'label', column?.label || fieldName)}
166
+ value={filterValue.shortcut || 'all'}
39
167
  customRange={filterValue.customRange || { from: '', to: '' }}
40
- onChange={optionKey =>
41
- onChange?.(
42
- fieldName,
43
- resolveNextDateFilterValue(filterValue, optionKey),
44
- )
45
- }
46
- onCustomRangeChange={range => onChange?.(fieldName, {
47
- ...(filterValue || {}),
48
- shortcut: 'custom',
49
- customRange: range,
168
+ onChange={optionKey =>
169
+ requestChange(
170
+ fieldName,
171
+ resolveNextDateFilterValue(filterValue, optionKey),
172
+ )
173
+ }
174
+ onCustomRangeChange={range => requestChange(fieldName, {
175
+ ...(filterValue || {}),
176
+ shortcut: 'custom',
177
+ customRange: range,
50
178
  })}
51
179
  />
52
180
  </View>
53
181
  );
54
182
  }
55
183
 
56
- if (column?.list) {
57
- const rawOptions = buildOptionsFromColumn(column, getOptionsForColumn, storeName);
184
+ if (column?.list) {
185
+ const rawOptions = loadedItems.length > 0
186
+ ? mapOptions(column, loadedItems, storeName)
187
+ : buildOptionsFromColumn(column, resolveOptions, storeName);
58
188
  const options = [
59
- { key: '', label: global.t?.t(storeName, 'label', 'select') || 'Selecionar' },
189
+ { key: '', label: global.t?.t(storeName, 'label', 'select') },
60
190
  ...rawOptions,
61
191
  ];
62
192
  const selectedKey = normalizeOptionKey(filters?.[fieldName]);
@@ -69,16 +199,18 @@ const DefaultColumnFilter = ({
69
199
  store={storeName}
70
200
  field={fieldName}
71
201
  icon="filter"
72
- onBeforeOpen={onBeforeOpen}
202
+ onBeforeOpen={onBeforeOpen || (() => loadListOptions())}
203
+ onSearchChange={onSearchChange || (value => loadListOptions(value))}
73
204
  accentColor={accentColor}
74
205
  active={Boolean(selectedKey)}
75
206
  label={selected?.label || options[0]?.label || ''}
76
- options={options}
77
- selectedKey={selectedKey}
78
- onSelect={optionKey => {
79
- onChange?.(fieldName, optionKey);
80
- return true;
81
- }}
207
+ options={options}
208
+ searchable
209
+ selectedKey={selectedKey}
210
+ onSelect={optionKey => {
211
+ requestChange(fieldName, optionKey);
212
+ return true;
213
+ }}
82
214
  />
83
215
  </View>
84
216
  );
@@ -87,11 +219,11 @@ const DefaultColumnFilter = ({
87
219
  return (
88
220
  <View style={[style, styles.filterCell]}>
89
221
  <TextInput
90
- style={styles.filterInput}
91
- value={normalizeText(filters?.[fieldName])}
92
- placeholder={global.t?.t(storeName, 'input', column?.label || fieldName)}
93
- onChangeText={value => onChange?.(fieldName, value)}
94
- />
222
+ style={styles.filterInput}
223
+ value={normalizeText(filters?.[fieldName])}
224
+ placeholder={global.t?.t(storeName, 'input', column?.label || fieldName)}
225
+ onChangeText={value => requestChange(fieldName, value)}
226
+ />
95
227
  </View>
96
228
  );
97
229
  };