@controleonline/ui-default 1.0.264 → 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.
- package/AGENTS.md +3 -2
- package/package.json +21 -18
- package/src/react/components/errors/DefaultErrors.js +64 -82
- package/src/react/components/filters/CompactFilterSelector.js +58 -23
- package/src/react/components/filters/CompactFilterSelector.styles.js +17 -5
- package/src/react/components/filters/DefaultColumnFilter.js +177 -45
- package/src/react/components/filters/DefaultExternalFilters.js +201 -143
- package/src/react/components/filters/DefaultSearch.js +27 -25
- package/src/react/components/form/DefaultForm.js +72 -53
- package/src/react/components/form/DefaultForm.styles.js +71 -66
- package/src/react/components/inputs/DefaultDateInput.js +150 -0
- package/src/react/components/inputs/DefaultInput.js +44 -11
- package/src/react/components/inputs/DefaultInput.styles.js +6 -0
- package/src/react/components/inputs/DefaultSelect.js +200 -43
- package/src/react/components/inputs/defaultInputUtils.js +40 -19
- package/src/react/components/table/DefaultColumnMenu.js +108 -0
- package/src/react/components/table/DefaultDebug.js +233 -0
- package/src/react/components/table/DefaultFiltersModal.js +142 -0
- package/src/react/components/table/DefaultModalButton.js +18 -0
- package/src/react/components/table/DefaultSearchModal.js +49 -0
- package/src/react/components/table/DefaultTable.js +350 -2025
- package/src/react/components/table/DefaultTable.styles.js +217 -97
- package/src/react/components/table/DefaultTable.utils.js +502 -0
- package/src/react/components/table/DefaultTableBody.js +26 -0
- package/src/react/components/table/DefaultTableCards.js +171 -0
- package/src/react/components/table/DefaultTableControls.js +208 -0
- package/src/react/components/table/DefaultTableEmptyState.js +22 -0
- package/src/react/components/table/DefaultTableFooter.js +172 -0
- package/src/react/components/table/DefaultTableInput.js +145 -0
- package/src/react/components/table/DefaultTableRows.js +340 -0
- package/src/react/components/table/DefaultTableSearch.js +100 -0
- package/src/react/components/table/DefaultTableToolbar.js +98 -0
- package/src/react/components/table/DefaultToolbarAction.js +56 -0
- package/src/react/components/table/useDefaultTablePagination.js +370 -0
- package/src/react/components/table/useDefaultTableSorting.js +126 -0
- package/src/react/components/table/useDefaultTableTheme.js +58 -0
- package/src/react/utils/tableVisibleColumnsPreferences.js +239 -211
- package/src/store/default/actions.js +51 -13
- package/src/store/default/getters.js +10 -8
- package/src/store/default/mutation_types.js +4 -2
- package/src/store/default/mutations.js +10 -1
- package/src/tests/react/components/form/DefaultForm.test.js +81 -0
- package/src/tests/react/components/inputs/DefaultSelect.test.js +106 -14
- package/src/tests/react/components/table/DefaultTable.test.js +945 -96
- package/src/tests/react/store/defaultActions.test.js +59 -0
- package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +247 -168
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
Modal,
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Modal,
|
|
4
4
|
ScrollView,
|
|
5
5
|
Text,
|
|
6
6
|
TextInput,
|
|
@@ -8,59 +8,85 @@ import {
|
|
|
8
8
|
TouchableWithoutFeedback,
|
|
9
9
|
View,
|
|
10
10
|
useWindowDimensions,
|
|
11
|
-
} from 'react-native';
|
|
12
|
-
import Icon from 'react-native-vector-icons/Feather';
|
|
13
|
-
import { useStore } from '@store';
|
|
14
|
-
import { resolveThemePalette, withOpacity } from '@controleonline/../../src/styles/branding';
|
|
15
|
-
import { colors } from '@controleonline/../../src/styles/colors';
|
|
11
|
+
} from 'react-native';
|
|
12
|
+
import Icon from 'react-native-vector-icons/Feather';
|
|
13
|
+
import { getAllStores, useStore } from '@store';
|
|
14
|
+
import { resolveThemePalette, withOpacity } from '@controleonline/../../src/styles/branding';
|
|
15
|
+
import { colors } from '@controleonline/../../src/styles/colors';
|
|
16
|
+
import {
|
|
17
|
+
buildOptionsFromColumn,
|
|
18
|
+
getColumnKey,
|
|
19
|
+
isDateLikeColumn,
|
|
20
|
+
resolveStoreNameFromList,
|
|
21
|
+
} from '../inputs/defaultInputUtils';
|
|
16
22
|
import CompactFilterSelector from './CompactFilterSelector';
|
|
17
23
|
import DateShortcutFilter from './DateShortcutFilter';
|
|
18
24
|
import { resolveNextDateFilterValue } from './dateFilterSelection';
|
|
19
25
|
import styles from './DefaultExternalFilters.styles';
|
|
20
26
|
|
|
21
|
-
const DEFAULT_COMPACT_BREAKPOINT = 768;
|
|
22
|
-
const noop = () => {};
|
|
23
|
-
|
|
24
|
-
const normalizeText = value => String(value || '').trim();
|
|
25
|
-
const
|
|
26
|
-
'
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
'
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
27
|
+
const DEFAULT_COMPACT_BREAKPOINT = 768;
|
|
28
|
+
const noop = () => {};
|
|
29
|
+
|
|
30
|
+
const normalizeText = value => String(value || '').trim();
|
|
31
|
+
const resolveListActionName = list =>
|
|
32
|
+
normalizeText(list).split('/')[1] || 'getItems';
|
|
33
|
+
|
|
34
|
+
const COMPANY_SCOPED_LIST_STORES = new Set([
|
|
35
|
+
'categories',
|
|
36
|
+
'paymentType',
|
|
37
|
+
'wallet',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
const resolveListLoadParams = ({currentCompanyId, listStoreName}) => {
|
|
41
|
+
if (!currentCompanyId || !COMPANY_SCOPED_LIST_STORES.has(listStoreName)) {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return listStoreName === 'categories'
|
|
46
|
+
? {company: currentCompanyId}
|
|
47
|
+
: {people: currentCompanyId};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const resolveColumnListLoadParams = ({
|
|
51
|
+
column,
|
|
52
|
+
currentCompanyId,
|
|
53
|
+
requestParams = {},
|
|
54
|
+
searchValue = '',
|
|
55
|
+
}) => {
|
|
56
|
+
const listStoreName = resolveStoreNameFromList(column?.list);
|
|
57
|
+
const resolvedCustomParams = typeof column?.listRequestParams === 'function'
|
|
58
|
+
? column.listRequestParams({currentCompanyId, requestParams})
|
|
59
|
+
: column?.listRequestParams;
|
|
60
|
+
const customParams =
|
|
61
|
+
resolvedCustomParams &&
|
|
62
|
+
typeof resolvedCustomParams === 'object' &&
|
|
63
|
+
!Array.isArray(resolvedCustomParams)
|
|
64
|
+
? resolvedCustomParams
|
|
65
|
+
: {};
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
...resolveListLoadParams({currentCompanyId, listStoreName}),
|
|
69
|
+
...customParams,
|
|
70
|
+
...(normalizeText(searchValue)
|
|
71
|
+
? {[column?.listSearchParam || column?.searchParam || 'search']: normalizeText(searchValue)}
|
|
72
|
+
: {}),
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const stableSerialize = value => {
|
|
77
|
+
if (value === null || value === undefined) return 'null';
|
|
78
|
+
if (Array.isArray(value)) return `[${value.map(item => stableSerialize(item)).join(',')}]`;
|
|
79
|
+
if (value instanceof Date) return JSON.stringify(value.toISOString());
|
|
80
|
+
if (typeof value === 'object') {
|
|
81
|
+
return `{${Object.keys(value)
|
|
82
|
+
.sort()
|
|
83
|
+
.map(key => `${JSON.stringify(key)}:${stableSerialize(value[key])}`)
|
|
84
|
+
.join(',')}}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return JSON.stringify(value);
|
|
88
|
+
};
|
|
89
|
+
|
|
64
90
|
const shouldIncludeColumn = column =>
|
|
65
91
|
Boolean(getColumnKey(column)) &&
|
|
66
92
|
column?.show !== false &&
|
|
@@ -69,13 +95,13 @@ const shouldIncludeColumn = column =>
|
|
|
69
95
|
column?.filter !== false &&
|
|
70
96
|
column?.filters !== false;
|
|
71
97
|
|
|
72
|
-
const normalizeFilterValue = value => {
|
|
73
|
-
if (value && typeof value === 'object') {
|
|
74
|
-
return normalizeFilterValue(value.value ?? value.id ?? value['@id'] ?? '');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return normalizeText(value);
|
|
78
|
-
};
|
|
98
|
+
const normalizeFilterValue = value => {
|
|
99
|
+
if (value && typeof value === 'object') {
|
|
100
|
+
return normalizeFilterValue(value.value ?? value.id ?? value['@id'] ?? value.key ?? '');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return normalizeText(value);
|
|
104
|
+
};
|
|
79
105
|
|
|
80
106
|
const isFilledFilterValue = value => {
|
|
81
107
|
if (Array.isArray(value)) return value.length > 0;
|
|
@@ -86,52 +112,6 @@ const isFilledFilterValue = value => {
|
|
|
86
112
|
return normalizeText(value) !== '';
|
|
87
113
|
};
|
|
88
114
|
|
|
89
|
-
const resolveOptionLabel = (column, option, storeName = '') => {
|
|
90
|
-
if (!option) return '';
|
|
91
|
-
|
|
92
|
-
const translateOptionLabel = rawLabel => {
|
|
93
|
-
const normalizedLabel = normalizeText(rawLabel);
|
|
94
|
-
if (!shouldTranslateOptionLabel(column, storeName, normalizedLabel)) {
|
|
95
|
-
return normalizedLabel;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return global.t?.t(storeName, 'label', normalizedLabel) || normalizedLabel;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
if (typeof column?.formatList === 'function') {
|
|
102
|
-
const formatted = column.formatList(option, null, column);
|
|
103
|
-
if (formatted && typeof formatted === 'object') {
|
|
104
|
-
return translateOptionLabel(formatted.label ?? formatted.value);
|
|
105
|
-
}
|
|
106
|
-
if (formatted) return translateOptionLabel(formatted);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const rawLabel = normalizeText(
|
|
110
|
-
option.label ??
|
|
111
|
-
option[column?.searchParam] ??
|
|
112
|
-
option[column?.name] ??
|
|
113
|
-
option.name ??
|
|
114
|
-
option.status ??
|
|
115
|
-
option.wallet ??
|
|
116
|
-
option.paymentType ??
|
|
117
|
-
option.alias ??
|
|
118
|
-
option.id,
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
return translateOptionLabel(rawLabel);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const buildColumnOptions = (column, options = [], storeName = '') => [
|
|
125
|
-
{
|
|
126
|
-
key: '',
|
|
127
|
-
label: global.t?.t(storeName || 'invoice', 'label', 'select'),
|
|
128
|
-
},
|
|
129
|
-
...(Array.isArray(options) ? options : []).map(option => ({
|
|
130
|
-
key: normalizeFilterValue(option),
|
|
131
|
-
label: resolveOptionLabel(column, option, storeName) || '-',
|
|
132
|
-
})),
|
|
133
|
-
];
|
|
134
|
-
|
|
135
115
|
const resolveDateState = filterValue => {
|
|
136
116
|
if (!filterValue || typeof filterValue !== 'object') {
|
|
137
117
|
return {
|
|
@@ -167,25 +147,27 @@ const resolveIcon = column => {
|
|
|
167
147
|
const key = getColumnKey(column);
|
|
168
148
|
if (key === 'status') return 'check-circle';
|
|
169
149
|
if (key === 'category') return 'tag';
|
|
170
|
-
if (column
|
|
150
|
+
if (isDateLikeColumn(column)) return 'calendar';
|
|
171
151
|
return 'sliders';
|
|
172
152
|
};
|
|
173
153
|
|
|
174
154
|
const DefaultExternalFilters = ({
|
|
175
|
-
accentColor = null,
|
|
176
|
-
compactBreakpoint = DEFAULT_COMPACT_BREAKPOINT,
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
storeName = '',
|
|
184
|
-
}) => {
|
|
185
|
-
const { width } = useWindowDimensions();
|
|
155
|
+
accentColor = null,
|
|
156
|
+
compactBreakpoint = DEFAULT_COMPACT_BREAKPOINT,
|
|
157
|
+
dateOptionKeys = ['all', 'today', 'yesterday', '7d', '30d', 'custom'],
|
|
158
|
+
filters = {},
|
|
159
|
+
getOptionsForColumn = null,
|
|
160
|
+
onActiveCountChange = null,
|
|
161
|
+
onChangeFilters = null,
|
|
162
|
+
requestParams = {},
|
|
163
|
+
storeName = '',
|
|
164
|
+
}) => {
|
|
165
|
+
const { width } = useWindowDimensions();
|
|
166
|
+
const externalFilterStore = useStore(storeName);
|
|
186
167
|
const peopleStore = useStore('people');
|
|
187
168
|
const themeStore = useStore('theme');
|
|
188
169
|
const [isFiltersModalOpen, setIsFiltersModalOpen] = useState(false);
|
|
170
|
+
const loadedListStoresRef = useRef(new Set());
|
|
189
171
|
const { currentCompany } = peopleStore.getters || {};
|
|
190
172
|
const { colors: themeColors } = themeStore.getters;
|
|
191
173
|
const themeTokens = useMemo(
|
|
@@ -208,11 +190,15 @@ const DefaultExternalFilters = ({
|
|
|
208
190
|
const tableFilterBorderColor = themeColors.tableFilterBorder;
|
|
209
191
|
const tableFilterTextColor = themeColors.tableFilterText;
|
|
210
192
|
const onAccentColor = palette.secondary || palette.text;
|
|
211
|
-
const isCompactView = width > 0 && width <= compactBreakpoint;
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
[
|
|
215
|
-
);
|
|
193
|
+
const isCompactView = width > 0 && width <= compactBreakpoint;
|
|
194
|
+
const resolvedColumns = useMemo(() => {
|
|
195
|
+
const storeColumns = externalFilterStore?.getters?.columns;
|
|
196
|
+
return Array.isArray(storeColumns) ? storeColumns : [];
|
|
197
|
+
}, [externalFilterStore?.getters?.columns]);
|
|
198
|
+
const filterColumns = useMemo(
|
|
199
|
+
() => resolvedColumns.filter(shouldIncludeColumn),
|
|
200
|
+
[resolvedColumns],
|
|
201
|
+
);
|
|
216
202
|
const activeCount = useMemo(
|
|
217
203
|
() => filterColumns.filter(column => isFilledFilterValue(filters[getColumnKey(column)])).length,
|
|
218
204
|
[filterColumns, filters],
|
|
@@ -236,23 +222,80 @@ const DefaultExternalFilters = ({
|
|
|
236
222
|
onChangeFilters?.({});
|
|
237
223
|
}, [onChangeFilters]);
|
|
238
224
|
|
|
239
|
-
useEffect(() => {
|
|
240
|
-
if (!isCompactView) setIsFiltersModalOpen(false);
|
|
241
|
-
}, [isCompactView]);
|
|
242
|
-
|
|
243
|
-
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
if (!isCompactView) setIsFiltersModalOpen(false);
|
|
227
|
+
}, [isCompactView]);
|
|
228
|
+
|
|
229
|
+
useEffect(() => {
|
|
230
|
+
loadedListStoresRef.current.clear();
|
|
231
|
+
}, [currentCompany?.id]);
|
|
232
|
+
|
|
233
|
+
const loadListOptionsForColumn = useCallback((column, searchValue = '') => {
|
|
234
|
+
const explicitOptions = getOptionsForColumn?.(column);
|
|
235
|
+
if (!normalizeText(searchValue) && Array.isArray(explicitOptions) && explicitOptions.length > 0) {
|
|
236
|
+
return Promise.resolve([]);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const listStoreName = resolveStoreNameFromList(column?.list);
|
|
240
|
+
const actionName = resolveListActionName(column?.list);
|
|
241
|
+
const stores = getAllStores?.() || {};
|
|
242
|
+
const listStore = stores?.[listStoreName];
|
|
243
|
+
const listAction = listStore?.actions?.[actionName];
|
|
244
|
+
|
|
245
|
+
if (!listStoreName || typeof listAction !== 'function') {
|
|
246
|
+
return Promise.resolve([]);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const listLoadParams = resolveColumnListLoadParams({
|
|
250
|
+
column,
|
|
251
|
+
currentCompanyId: currentCompany?.id,
|
|
252
|
+
requestParams,
|
|
253
|
+
searchValue,
|
|
254
|
+
});
|
|
255
|
+
const hasScopedParams = Object.keys(listLoadParams).length > 0;
|
|
256
|
+
|
|
257
|
+
if (
|
|
258
|
+
!normalizeText(searchValue) &&
|
|
259
|
+
!hasScopedParams &&
|
|
260
|
+
Array.isArray(listStore?.getters?.items) &&
|
|
261
|
+
listStore.getters.items.length > 0
|
|
262
|
+
) {
|
|
263
|
+
return Promise.resolve(listStore.getters.items);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const loadKey = `${getColumnKey(column)}:${listStoreName}:${actionName}:${stableSerialize(listLoadParams)}`;
|
|
267
|
+
if (loadedListStoresRef.current.has(loadKey)) {
|
|
268
|
+
return Promise.resolve(listStore?.getters?.items || []);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
loadedListStoresRef.current.add(loadKey);
|
|
272
|
+
return Promise.resolve(
|
|
273
|
+
listAction({
|
|
274
|
+
...listLoadParams,
|
|
275
|
+
__storeMeta: {
|
|
276
|
+
dedupeKey: `default-external-filter-list-options:${loadKey}`,
|
|
277
|
+
skipSystemError: true,
|
|
278
|
+
},
|
|
279
|
+
}),
|
|
280
|
+
).catch(error => {
|
|
281
|
+
loadedListStoresRef.current.delete(loadKey);
|
|
282
|
+
throw error;
|
|
283
|
+
});
|
|
284
|
+
}, [currentCompany?.id, getOptionsForColumn, requestParams]);
|
|
285
|
+
|
|
286
|
+
if (filterColumns.length === 0) return null;
|
|
244
287
|
|
|
245
288
|
const renderFilterField = (column, compact = false) => {
|
|
246
289
|
const key = getColumnKey(column);
|
|
247
290
|
const fieldStyle = compact ? styles.modalField : styles.field;
|
|
248
291
|
|
|
249
|
-
if (column
|
|
292
|
+
if (isDateLikeColumn(column)) {
|
|
250
293
|
const dateState = resolveDateState(filters[key]);
|
|
251
294
|
|
|
252
295
|
return (
|
|
253
296
|
<View key={key} style={fieldStyle}>
|
|
254
|
-
<DateShortcutFilter
|
|
255
|
-
value={dateState.value}
|
|
297
|
+
<DateShortcutFilter
|
|
298
|
+
value={dateState.value}
|
|
256
299
|
onChange={optionKey => {
|
|
257
300
|
updateFilter(
|
|
258
301
|
key,
|
|
@@ -268,9 +311,10 @@ const DefaultExternalFilters = ({
|
|
|
268
311
|
});
|
|
269
312
|
}}
|
|
270
313
|
dense
|
|
271
|
-
store={storeName}
|
|
272
|
-
field={key}
|
|
273
|
-
|
|
314
|
+
store={storeName}
|
|
315
|
+
field={key}
|
|
316
|
+
labelCaption={global.t?.t(storeName, 'label', column.label || key)}
|
|
317
|
+
optionKeys={dateOptionKeys}
|
|
274
318
|
colors={{
|
|
275
319
|
accent: resolvedAccentColor,
|
|
276
320
|
appBg: 'transparent',
|
|
@@ -288,11 +332,22 @@ const DefaultExternalFilters = ({
|
|
|
288
332
|
/>
|
|
289
333
|
</View>
|
|
290
334
|
);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (column.list) {
|
|
294
|
-
const
|
|
295
|
-
const
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (column.list) {
|
|
338
|
+
const listStoreName = resolveStoreNameFromList(column?.list);
|
|
339
|
+
const options = [
|
|
340
|
+
{
|
|
341
|
+
key: '',
|
|
342
|
+
label:
|
|
343
|
+
(column?.emptyOptionLabel
|
|
344
|
+
? global.t?.t(storeName || 'invoice', 'label', column.emptyOptionLabel) || column.emptyOptionLabel
|
|
345
|
+
: null) ||
|
|
346
|
+
global.t?.t(storeName || 'invoice', 'label', 'select'),
|
|
347
|
+
},
|
|
348
|
+
...buildOptionsFromColumn(column, getOptionsForColumn, storeName),
|
|
349
|
+
];
|
|
350
|
+
const selectedKey = normalizeFilterValue(filters[key]);
|
|
296
351
|
const selectedLabel =
|
|
297
352
|
options.find(option => option.key === selectedKey)?.label ||
|
|
298
353
|
options[0]?.label ||
|
|
@@ -305,11 +360,14 @@ const DefaultExternalFilters = ({
|
|
|
305
360
|
label={selectedLabel}
|
|
306
361
|
accentColor={resolvedAccentColor}
|
|
307
362
|
active={Boolean(selectedKey)}
|
|
308
|
-
dense
|
|
309
|
-
store={storeName}
|
|
310
|
-
field={key}
|
|
311
|
-
|
|
312
|
-
|
|
363
|
+
dense
|
|
364
|
+
store={storeName}
|
|
365
|
+
field={key}
|
|
366
|
+
onBeforeOpen={listStoreName ? () => loadListOptionsForColumn(column) : null}
|
|
367
|
+
onSearchChange={listStoreName ? value => loadListOptionsForColumn(column, value) : null}
|
|
368
|
+
options={options}
|
|
369
|
+
searchable
|
|
370
|
+
selectedKey={selectedKey}
|
|
313
371
|
onSelect={optionKey => {
|
|
314
372
|
updateFilter(key, optionKey);
|
|
315
373
|
return true;
|
|
@@ -337,7 +395,7 @@ const DefaultExternalFilters = ({
|
|
|
337
395
|
const filterTitle = global.t?.t(storeName, 'label', 'filters');
|
|
338
396
|
const filterFields = compact => filterColumns.map(column => renderFilterField(column, compact));
|
|
339
397
|
|
|
340
|
-
if (isCompactView) {
|
|
398
|
+
if (isCompactView) {
|
|
341
399
|
return (
|
|
342
400
|
<View style={styles.mobileWrap}>
|
|
343
401
|
<TouchableOpacity
|
|
@@ -18,10 +18,13 @@ const DefaultSearch = ({
|
|
|
18
18
|
storeName = '',
|
|
19
19
|
style = null,
|
|
20
20
|
value,
|
|
21
|
-
}) => {
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
21
|
+
}) => {
|
|
22
|
+
const store = useStore(storeName);
|
|
23
|
+
const themeStore = useStore('theme');
|
|
24
|
+
const themeColors = themeStore.getters.colors;
|
|
25
|
+
const resolvedFilters = filters || store?.getters?.filters || {};
|
|
26
|
+
const resolvedChangeFilters = onChangeFilters || store?.actions?.setFilters || null;
|
|
27
|
+
const palette = useMemo(
|
|
25
28
|
() => ({
|
|
26
29
|
buttonBackgroundSecondary: themeColors.buttonBackgroundSecondary,
|
|
27
30
|
buttonBorder: themeColors.buttonBorder,
|
|
@@ -34,11 +37,11 @@ const DefaultSearch = ({
|
|
|
34
37
|
inputText: themeColors.inputText,
|
|
35
38
|
}),
|
|
36
39
|
[themeColors],
|
|
37
|
-
);
|
|
38
|
-
const resolvedValue = useMemo(
|
|
39
|
-
() => normalizeText(value ??
|
|
40
|
-
[
|
|
41
|
-
);
|
|
40
|
+
);
|
|
41
|
+
const resolvedValue = useMemo(
|
|
42
|
+
() => normalizeText(value ?? resolvedFilters?.[searchKey]),
|
|
43
|
+
[resolvedFilters, searchKey, value],
|
|
44
|
+
);
|
|
42
45
|
const [draftValue, setDraftValue] = useState(resolvedValue);
|
|
43
46
|
|
|
44
47
|
useEffect(() => {
|
|
@@ -48,12 +51,12 @@ const DefaultSearch = ({
|
|
|
48
51
|
const commitSearch = nextValue => {
|
|
49
52
|
const searchValue = normalizeText(nextValue ?? draftValue);
|
|
50
53
|
|
|
51
|
-
if (typeof
|
|
52
|
-
const nextFilters = { ...(
|
|
53
|
-
if (searchValue) nextFilters[searchKey] = searchValue;
|
|
54
|
-
else delete nextFilters[searchKey];
|
|
55
|
-
|
|
56
|
-
}
|
|
54
|
+
if (typeof resolvedChangeFilters === 'function') {
|
|
55
|
+
const nextFilters = { ...(resolvedFilters || {}) };
|
|
56
|
+
if (searchValue) nextFilters[searchKey] = searchValue;
|
|
57
|
+
else delete nextFilters[searchKey];
|
|
58
|
+
resolvedChangeFilters(nextFilters);
|
|
59
|
+
}
|
|
57
60
|
|
|
58
61
|
onSearch?.(searchValue);
|
|
59
62
|
};
|
|
@@ -61,21 +64,20 @@ const DefaultSearch = ({
|
|
|
61
64
|
const clearSearch = () => {
|
|
62
65
|
setDraftValue('');
|
|
63
66
|
|
|
64
|
-
if (typeof
|
|
65
|
-
const nextFilters = { ...(
|
|
66
|
-
delete nextFilters[searchKey];
|
|
67
|
-
|
|
68
|
-
}
|
|
67
|
+
if (typeof resolvedChangeFilters === 'function') {
|
|
68
|
+
const nextFilters = { ...(resolvedFilters || {}) };
|
|
69
|
+
delete nextFilters[searchKey];
|
|
70
|
+
resolvedChangeFilters(nextFilters);
|
|
71
|
+
}
|
|
69
72
|
|
|
70
73
|
onClear?.();
|
|
71
74
|
onSearch?.('');
|
|
72
75
|
};
|
|
73
76
|
|
|
74
|
-
const resolvedPlaceholder =
|
|
75
|
-
placeholder ||
|
|
76
|
-
global.t?.t(storeName, 'input', searchKey) ||
|
|
77
|
-
global.t?.t(storeName, 'placeholder', searchKey)
|
|
78
|
-
'Buscar...';
|
|
77
|
+
const resolvedPlaceholder =
|
|
78
|
+
placeholder ||
|
|
79
|
+
global.t?.t(storeName, 'input', searchKey) ||
|
|
80
|
+
global.t?.t(storeName, 'placeholder', searchKey);
|
|
79
81
|
|
|
80
82
|
return (
|
|
81
83
|
<View
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
|
|
3
|
+
import { useStore } from '@store';
|
|
4
|
+
import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
|
|
5
|
+
import DefaultInput from '../inputs/DefaultInput';
|
|
6
|
+
import {
|
|
7
|
+
formatSaveValue,
|
|
7
8
|
getColumnKey,
|
|
8
9
|
isEditableColumn,
|
|
9
10
|
normalizeId,
|
|
10
|
-
normalizeText,
|
|
11
|
-
resolveEditValue,
|
|
12
|
-
} from '../inputs/defaultInputUtils';
|
|
13
|
-
import
|
|
11
|
+
normalizeText,
|
|
12
|
+
resolveEditValue,
|
|
13
|
+
} from '../inputs/defaultInputUtils';
|
|
14
|
+
import { createStyles } from './DefaultForm.styles';
|
|
14
15
|
|
|
15
16
|
const hasValue = value => {
|
|
16
17
|
if (value === null || value === undefined) return false;
|
|
@@ -45,29 +46,47 @@ const shouldIncludeColumn = (column, row, mode) => {
|
|
|
45
46
|
return column?.visibleForm !== false;
|
|
46
47
|
};
|
|
47
48
|
|
|
48
|
-
const buildDraft = (columns, row) =>
|
|
49
|
-
columns.reduce((draft, column) => {
|
|
50
|
-
const fieldName = getColumnKey(column);
|
|
51
|
-
const defaultValue = resolveDefaultValue(column, row);
|
|
52
|
-
draft[fieldName] = resolveEditValue(column, row, defaultValue);
|
|
53
|
-
return draft;
|
|
54
|
-
}, {});
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
const buildDraft = (columns, row) =>
|
|
50
|
+
columns.reduce((draft, column) => {
|
|
51
|
+
const fieldName = getColumnKey(column);
|
|
52
|
+
const defaultValue = resolveDefaultValue(column, row);
|
|
53
|
+
draft[fieldName] = resolveEditValue(column, row, defaultValue);
|
|
54
|
+
return draft;
|
|
55
|
+
}, {});
|
|
56
|
+
|
|
57
|
+
const EMPTY_ROW = {};
|
|
58
|
+
|
|
59
|
+
const DefaultForm = ({
|
|
60
|
+
actions = {},
|
|
61
|
+
columns = [],
|
|
62
|
+
getOptionsForColumn = null,
|
|
61
63
|
mode = 'edit',
|
|
62
64
|
onBeforeOpen = null,
|
|
63
|
-
onCancel = null,
|
|
64
|
-
onSaved = null,
|
|
65
|
-
row =
|
|
66
|
-
storeName = '',
|
|
67
|
-
}) => {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
onCancel = null,
|
|
66
|
+
onSaved = null,
|
|
67
|
+
row = EMPTY_ROW,
|
|
68
|
+
storeName = '',
|
|
69
|
+
}) => {
|
|
70
|
+
const themeStore = useStore('theme');
|
|
71
|
+
const { colors: themeColors } = themeStore.getters;
|
|
72
|
+
const palette = useMemo(
|
|
73
|
+
() => ({
|
|
74
|
+
buttonBackground: themeColors.buttonBackground,
|
|
75
|
+
buttonBackgroundSecondary: themeColors.buttonBackgroundSecondary,
|
|
76
|
+
buttonBorderSecondary: themeColors.buttonBorderSecondary,
|
|
77
|
+
buttonDisabledBackground: themeColors.buttonDisabledBackground,
|
|
78
|
+
buttonDisabledText: themeColors.buttonDisabledText,
|
|
79
|
+
buttonText: themeColors.buttonText,
|
|
80
|
+
buttonTextSecondary: themeColors.buttonTextSecondary,
|
|
81
|
+
dividerBorder: themeColors.dividerBorder,
|
|
82
|
+
textSecondary: themeColors.textSecondary,
|
|
83
|
+
}),
|
|
84
|
+
[themeColors],
|
|
85
|
+
);
|
|
86
|
+
const styles = useMemo(() => createStyles(palette), [palette]);
|
|
87
|
+
const formColumns = useMemo(
|
|
88
|
+
() => columns.filter(column => shouldIncludeColumn(column, row, mode)),
|
|
89
|
+
[columns, mode, row],
|
|
71
90
|
);
|
|
72
91
|
const [draft, setDraft] = useState(() => buildDraft(formColumns, row));
|
|
73
92
|
const [isSaving, setIsSaving] = useState(false);
|
|
@@ -142,11 +161,11 @@ const DefaultForm = ({
|
|
|
142
161
|
|
|
143
162
|
return (
|
|
144
163
|
<View key={fieldName} style={styles.formField}>
|
|
145
|
-
<DefaultInput
|
|
146
|
-
accentColor={
|
|
147
|
-
autoFocus={false}
|
|
148
|
-
autoSave={false}
|
|
149
|
-
column={column}
|
|
164
|
+
<DefaultInput
|
|
165
|
+
accentColor={themeColors.buttonBackground}
|
|
166
|
+
autoFocus={false}
|
|
167
|
+
autoSave={false}
|
|
168
|
+
column={column}
|
|
150
169
|
columns={columns}
|
|
151
170
|
editing
|
|
152
171
|
getOptionsForColumn={getOptionsForColumn}
|
|
@@ -172,26 +191,26 @@ const DefaultForm = ({
|
|
|
172
191
|
)}
|
|
173
192
|
</ScrollView>
|
|
174
193
|
|
|
175
|
-
<View style={styles.actions}>
|
|
176
|
-
<TouchableOpacity style={styles.secondaryButton} onPress={onCancel}>
|
|
177
|
-
<Text style={styles.secondaryButtonText}>
|
|
194
|
+
<View style={styles.actions}>
|
|
195
|
+
<TouchableOpacity style={styles.secondaryButton} onPress={onCancel}>
|
|
196
|
+
<Text style={styles.secondaryButtonText}>
|
|
178
197
|
{global.t?.t(storeName, 'button', 'cancel') || 'Cancelar'}
|
|
179
198
|
</Text>
|
|
180
199
|
</TouchableOpacity>
|
|
181
|
-
<TouchableOpacity
|
|
182
|
-
style={[
|
|
183
|
-
styles.primaryButton,
|
|
184
|
-
{ backgroundColor:
|
|
185
|
-
isSaving ? styles.primaryButtonDisabled : null,
|
|
186
|
-
]}
|
|
187
|
-
activeOpacity={0.86}
|
|
188
|
-
disabled={isSaving}
|
|
189
|
-
onPress={save}
|
|
190
|
-
>
|
|
191
|
-
<Text style={styles.primaryButtonText}>
|
|
192
|
-
{isSaving ? 'Salvando' : global.t?.t(storeName, 'button', 'save') || 'Salvar'}
|
|
193
|
-
</Text>
|
|
194
|
-
</TouchableOpacity>
|
|
200
|
+
<TouchableOpacity
|
|
201
|
+
style={[
|
|
202
|
+
styles.primaryButton,
|
|
203
|
+
{ backgroundColor: palette.buttonBackground },
|
|
204
|
+
isSaving ? styles.primaryButtonDisabled : null,
|
|
205
|
+
]}
|
|
206
|
+
activeOpacity={0.86}
|
|
207
|
+
disabled={isSaving}
|
|
208
|
+
onPress={save}
|
|
209
|
+
>
|
|
210
|
+
<Text style={[styles.primaryButtonText, isSaving ? styles.primaryButtonTextDisabled : null]}>
|
|
211
|
+
{isSaving ? 'Salvando' : global.t?.t(storeName, 'button', 'save') || 'Salvar'}
|
|
212
|
+
</Text>
|
|
213
|
+
</TouchableOpacity>
|
|
195
214
|
</View>
|
|
196
215
|
</>
|
|
197
216
|
);
|