@controleonline/ui-default 1.0.276 → 1.0.278
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/docs/compact-order-history-1047.md +25 -0
- package/package.json +1 -1
- package/src/react/components/filters/CompactFilterSelector.js +194 -185
- package/src/react/components/filters/DefaultColumnFilter.js +8 -6
- package/src/react/components/filters/DefaultCompactFilterPicker.js +35 -0
- package/src/react/components/table/DefaultColumnMenu.js +36 -2
- package/src/react/components/table/DefaultCompactDialog.js +36 -0
- package/src/react/components/table/DefaultDebug.js +199 -197
- package/src/react/components/table/DefaultFiltersModal.js +44 -12
- package/src/react/components/table/DefaultTable.js +29 -29
- package/src/react/components/table/DefaultTableCards.js +18 -7
- package/src/react/components/table/DefaultTableCompact.helpers.js +26 -0
- package/src/react/components/table/DefaultTableCompact.styles.js +28 -0
- package/src/react/components/table/DefaultTableCompactToolbar.js +112 -0
- package/src/react/components/table/DefaultTableInput.js +30 -11
- package/src/react/components/table/DefaultTablePaginationBar.js +38 -0
- package/src/react/components/table/DefaultTableRows.js +17 -9
- package/src/react/components/table/DefaultTableToolbar.js +7 -0
- package/src/react/components/table/DefaultTableView.js +5 -2
- package/src/react/components/table/useDefaultTableCreateForm.js +29 -0
- package/src/react/components/table/useDefaultTablePagination.js +15 -1
- package/src/react/components/table/useDefaultTableRowInteraction.js +19 -0
- package/src/react/components/table/useDefaultTableStoreSync.js +30 -21
- package/src/react/components/table/useDefaultTableTheme.js +44 -41
- package/src/tests/react/components/table/DefaultTable.group1.test.js +441 -0
- package/src/tests/react/components/table/DefaultTable.group2.test.js +453 -0
- package/src/tests/react/components/table/DefaultTable.group3.test.js +289 -0
- package/src/tests/react/components/table/DefaultTable.test.js +1 -1381
- package/src/tests/react/components/table/DefaultTableToolbar.waiter.test.js +1 -0
- package/src/tests/react/components/table/compactDialogs.test.js +53 -0
- package/src/tests/react/components/table/compactFilters.test.js +47 -0
- package/src/tests/react/components/table/compactPagination.test.js +66 -0
- package/src/tests/react/components/table/compactTheme.test.js +34 -0
- package/src/tests/react/components/table/compactToolbar.test.js +41 -0
- package/src/tests/react/components/table/defaultTableStoreHarness.cjs +35 -0
- package/src/tests/react/components/table/defaultTableTestSetup.cjs +207 -0
- package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +76 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, {useEffect, useState} from 'react';
|
|
2
2
|
import { Modal, ScrollView, Text, TouchableOpacity, View } from 'react-native';
|
|
3
3
|
import Icon from 'react-native-vector-icons/Feather';
|
|
4
4
|
import { useStore } from '@store';
|
|
@@ -6,6 +6,7 @@ import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/util
|
|
|
6
6
|
import DefaultColumnFilter from '../filters/DefaultColumnFilter';
|
|
7
7
|
import { getColumnKey, normalizeText } from '../inputs/defaultInputUtils';
|
|
8
8
|
import styles from './DefaultTable.styles';
|
|
9
|
+
import {getCompactColumnLabel} from './DefaultTableCompact.helpers';
|
|
9
10
|
import { shouldIncludeColumn } from './DefaultTable.utils';
|
|
10
11
|
import {
|
|
11
12
|
persistTableFiltersPreference,
|
|
@@ -13,8 +14,9 @@ import {
|
|
|
13
14
|
sanitizeTableFiltersPreference,
|
|
14
15
|
} from '../../utils/tableVisibleColumnsPreferences';
|
|
15
16
|
import useDefaultTableTheme from './useDefaultTableTheme';
|
|
17
|
+
import DefaultCompactDialog from './DefaultCompactDialog';
|
|
16
18
|
|
|
17
|
-
const DefaultFiltersModal = ({ storeName, visible = false, onClose }) => {
|
|
19
|
+
const DefaultFiltersModal = ({ storeName, visible = false, deferred = false, onClose }) => {
|
|
18
20
|
const store = useStore(storeName);
|
|
19
21
|
const columns = (Array.isArray(store?.getters?.columns) ? store.getters.columns : []).filter(
|
|
20
22
|
column =>
|
|
@@ -24,6 +26,9 @@ const DefaultFiltersModal = ({ storeName, visible = false, onClose }) => {
|
|
|
24
26
|
);
|
|
25
27
|
const filters = store?.getters?.filters || {};
|
|
26
28
|
const configs = store?.getters?.configs || {};
|
|
29
|
+
const [draft, setDraft] = useState(filters);
|
|
30
|
+
useEffect(() => {if (visible) setDraft({...filters});}, [visible]);
|
|
31
|
+
const activeFilters = deferred ? draft : filters;
|
|
27
32
|
const tablePreferenceScope =
|
|
28
33
|
configs.tablePreferenceScope ||
|
|
29
34
|
resolveDefaultTablePreferenceScope({ storeName });
|
|
@@ -35,6 +40,10 @@ const DefaultFiltersModal = ({ storeName, visible = false, onClose }) => {
|
|
|
35
40
|
|
|
36
41
|
persistTableFiltersPreference(tablePreferenceScope, persistedFilters);
|
|
37
42
|
|
|
43
|
+
if (deferred && typeof configs.onFilterChange === 'function') {
|
|
44
|
+
configs.onFilterChange(nextFilters);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
38
47
|
if (typeof store?.actions?.setFilters === 'function') {
|
|
39
48
|
store.actions.setFilters(nextFilters);
|
|
40
49
|
} else if (store?.getters) {
|
|
@@ -44,7 +53,7 @@ const DefaultFiltersModal = ({ storeName, visible = false, onClose }) => {
|
|
|
44
53
|
configs.onFilterChange?.(nextFilters);
|
|
45
54
|
};
|
|
46
55
|
const updateFilter = (fieldName, value) => {
|
|
47
|
-
const nextFilters = { ...(
|
|
56
|
+
const nextFilters = { ...(activeFilters || {}) };
|
|
48
57
|
const isEmpty =
|
|
49
58
|
value === null ||
|
|
50
59
|
value === undefined ||
|
|
@@ -54,12 +63,13 @@ const DefaultFiltersModal = ({ storeName, visible = false, onClose }) => {
|
|
|
54
63
|
if (isEmpty) delete nextFilters[fieldName];
|
|
55
64
|
else nextFilters[fieldName] = value;
|
|
56
65
|
|
|
57
|
-
|
|
66
|
+
if (deferred) setDraft(nextFilters);
|
|
67
|
+
else applyFilters(nextFilters);
|
|
58
68
|
};
|
|
59
|
-
const applyLabel = global.t?.t(storeName, 'button', 'apply');
|
|
60
|
-
const clearLabel = global.t?.t(storeName, 'button', 'clear');
|
|
61
|
-
const title = global.t?.t(storeName, 'label', 'filters');
|
|
62
|
-
const { modalColors, resolvedAccentColor } = useDefaultTableTheme();
|
|
69
|
+
const applyLabel = deferred ? 'Aplicar filtros' : global.t?.t(storeName, 'button', 'apply');
|
|
70
|
+
const clearLabel = deferred ? 'Limpar' : global.t?.t(storeName, 'button', 'clear');
|
|
71
|
+
const title = deferred ? 'Filtros' : global.t?.t(storeName, 'label', 'filters');
|
|
72
|
+
const { modalColors, resolvedAccentColor, themeColors } = useDefaultTableTheme();
|
|
63
73
|
if (!visible) return null;
|
|
64
74
|
|
|
65
75
|
const {
|
|
@@ -71,6 +81,21 @@ const DefaultFiltersModal = ({ storeName, visible = false, onClose }) => {
|
|
|
71
81
|
textColor,
|
|
72
82
|
} = modalColors;
|
|
73
83
|
|
|
84
|
+
if (configs.appearance === 'compact') {
|
|
85
|
+
const fields = columns.filter(column => column.compactFilter !== false);
|
|
86
|
+
const action = (label, onPress, primary = false) => <TouchableOpacity accessibilityRole="button" accessibilityLabel={label} onPress={onPress}
|
|
87
|
+
style={{minHeight: 44, justifyContent: 'center', paddingHorizontal: 18, borderRadius: 9, borderWidth: 1, borderColor, backgroundColor: primary ? themeColors.buttonBackground : themeColors.modalBackground}}>
|
|
88
|
+
<Text style={{color: primary ? themeColors.buttonText : textColor, fontSize: 14, fontWeight: '600'}}>{label}</Text>
|
|
89
|
+
</TouchableOpacity>;
|
|
90
|
+
return <DefaultCompactDialog visible title="Filtros" description="Combine os filtros e aplique para atualizar os resultados." onClose={onClose}
|
|
91
|
+
bodyHeight={fields.length * 86 + 40} footer={<>{action(clearLabel, () => setDraft({}))}{action(applyLabel, () => {applyFilters(draft); onClose?.();}, true)}</>}>
|
|
92
|
+
{fields.map(column => <View key={getColumnKey(column)} style={{gap: 8}}>
|
|
93
|
+
<Text style={{fontSize: 14, fontWeight: '600', color: textColor}}>{getCompactColumnLabel(column, formatStoreColumnLabel({columns, fieldName: getColumnKey(column), fallbackLabel: column.label || getColumnKey(column), storeName}))}</Text>
|
|
94
|
+
<DefaultColumnFilter column={column} filters={activeFilters} onChange={updateFilter} storeName={storeName} />
|
|
95
|
+
</View>)}
|
|
96
|
+
</DefaultCompactDialog>;
|
|
97
|
+
}
|
|
98
|
+
|
|
74
99
|
return (
|
|
75
100
|
<Modal visible transparent animationType="fade" onRequestClose={onClose}>
|
|
76
101
|
<View style={[styles.modalOverlay, { backgroundColor: overlayColor }]}>
|
|
@@ -80,6 +105,8 @@ const DefaultFiltersModal = ({ storeName, visible = false, onClose }) => {
|
|
|
80
105
|
{title}
|
|
81
106
|
</Text>
|
|
82
107
|
<TouchableOpacity
|
|
108
|
+
accessibilityRole="button"
|
|
109
|
+
accessibilityLabel="Fechar filtros"
|
|
83
110
|
style={[styles.modalCloseButton, { borderColor, backgroundColor }]}
|
|
84
111
|
activeOpacity={0.82}
|
|
85
112
|
onPress={onClose}
|
|
@@ -90,12 +117,13 @@ const DefaultFiltersModal = ({ storeName, visible = false, onClose }) => {
|
|
|
90
117
|
<ScrollView style={styles.filtersModalBody} contentContainerStyle={styles.filtersModalList}>
|
|
91
118
|
{columns.map(column => {
|
|
92
119
|
const fieldName = getColumnKey(column);
|
|
93
|
-
const
|
|
120
|
+
const defaultLabel = formatStoreColumnLabel({
|
|
94
121
|
columns,
|
|
95
122
|
fieldName,
|
|
96
123
|
fallbackLabel: column?.label || fieldName,
|
|
97
124
|
storeName,
|
|
98
125
|
});
|
|
126
|
+
const label = configs.appearance === 'compact' ? getCompactColumnLabel(column, defaultLabel) : defaultLabel;
|
|
99
127
|
|
|
100
128
|
return (
|
|
101
129
|
<View key={fieldName} style={styles.filtersModalField}>
|
|
@@ -104,7 +132,7 @@ const DefaultFiltersModal = ({ storeName, visible = false, onClose }) => {
|
|
|
104
132
|
</Text>
|
|
105
133
|
<DefaultColumnFilter
|
|
106
134
|
column={column}
|
|
107
|
-
filters={
|
|
135
|
+
filters={activeFilters}
|
|
108
136
|
onChange={updateFilter}
|
|
109
137
|
storeName={storeName}
|
|
110
138
|
style={styles.filtersModalInput}
|
|
@@ -115,18 +143,22 @@ const DefaultFiltersModal = ({ storeName, visible = false, onClose }) => {
|
|
|
115
143
|
</ScrollView>
|
|
116
144
|
<View style={[styles.modalActions, { borderTopColor: borderColor }]}>
|
|
117
145
|
<TouchableOpacity
|
|
146
|
+
accessibilityRole="button"
|
|
147
|
+
accessibilityLabel={clearLabel}
|
|
118
148
|
style={[styles.secondaryButton, { borderColor }]}
|
|
119
149
|
activeOpacity={0.82}
|
|
120
|
-
onPress={() => applyFilters({})}
|
|
150
|
+
onPress={() => deferred ? setDraft({}) : applyFilters({})}
|
|
121
151
|
>
|
|
122
152
|
<Text style={[styles.secondaryButtonText, { color: textColor }]}>
|
|
123
153
|
{clearLabel}
|
|
124
154
|
</Text>
|
|
125
155
|
</TouchableOpacity>
|
|
126
156
|
<TouchableOpacity
|
|
157
|
+
accessibilityRole="button"
|
|
158
|
+
accessibilityLabel={applyLabel}
|
|
127
159
|
style={[styles.primaryButton, { backgroundColor: resolvedAccentColor }]}
|
|
128
160
|
activeOpacity={0.82}
|
|
129
|
-
onPress={onClose}
|
|
161
|
+
onPress={() => {if (deferred) applyFilters(draft); onClose?.();}}
|
|
130
162
|
>
|
|
131
163
|
<Text style={styles.primaryButtonText}>
|
|
132
164
|
{applyLabel}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useEffect, useMemo, useRef } from 'react';
|
|
2
2
|
import { useWindowDimensions } from 'react-native';
|
|
3
3
|
import { NavigationRouteContext, useIsFocused } from '@react-navigation/native';
|
|
4
4
|
import { useStore } from '@store';
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from './useDefaultTableSorting';
|
|
17
17
|
import useDefaultTableTheme from './useDefaultTableTheme';
|
|
18
18
|
import DefaultTableView from './DefaultTableView';
|
|
19
|
+
import useDefaultTableCreateForm from './useDefaultTableCreateForm';
|
|
19
20
|
|
|
20
21
|
import {
|
|
21
22
|
assignGetterValue,
|
|
@@ -63,6 +64,8 @@ const DefaultTable = ({
|
|
|
63
64
|
onSelectionChange = null,
|
|
64
65
|
onSortChange = null,
|
|
65
66
|
pageSize = null,
|
|
67
|
+
appearance = 'default',
|
|
68
|
+
paginationMode = 'infinite',
|
|
66
69
|
pinRowActions = true,
|
|
67
70
|
renderCard = null,
|
|
68
71
|
requestParams = {},
|
|
@@ -86,7 +89,6 @@ const DefaultTable = ({
|
|
|
86
89
|
toolbarActions = [],
|
|
87
90
|
visibleColumnsPreferenceKey = '',
|
|
88
91
|
}) => {
|
|
89
|
-
const [isCreateFormOpen, setIsCreateFormOpen] = useState(false);
|
|
90
92
|
const { width } = useWindowDimensions();
|
|
91
93
|
const route = React.useContext(NavigationRouteContext);
|
|
92
94
|
const store = useStore(storeName);
|
|
@@ -94,7 +96,7 @@ const DefaultTable = ({
|
|
|
94
96
|
const isFocused = useIsFocused();
|
|
95
97
|
const storeDeclaredConfigsRef = useRef(null);
|
|
96
98
|
const { showError } = useMessage() || {};
|
|
97
|
-
const { tableBorderColors, themeColors } = useDefaultTableTheme(accentColor);
|
|
99
|
+
const { tableBorderColors, themeColors } = useDefaultTableTheme(accentColor, appearance);
|
|
98
100
|
const tablePanelBorderColor = tableBorderColors.containerBorderColor;
|
|
99
101
|
const floatingAddBackgroundColor =
|
|
100
102
|
themeColors.buttonBackground || themeColors.primary || accentColor;
|
|
@@ -165,6 +167,9 @@ const DefaultTable = ({
|
|
|
165
167
|
const {
|
|
166
168
|
buildRequestQuery,
|
|
167
169
|
currentPage,
|
|
170
|
+
pageSizeNumber,
|
|
171
|
+
goToPage,
|
|
172
|
+
resolvedIsLoading,
|
|
168
173
|
handleEndReached,
|
|
169
174
|
handleRefresh,
|
|
170
175
|
resolvedData,
|
|
@@ -180,6 +185,7 @@ const DefaultTable = ({
|
|
|
180
185
|
onEndReached,
|
|
181
186
|
onRefresh,
|
|
182
187
|
pageSize,
|
|
188
|
+
paginationMode,
|
|
183
189
|
requestParams: requestParamsSeed,
|
|
184
190
|
resolvedActions,
|
|
185
191
|
resolvedSort,
|
|
@@ -194,23 +200,10 @@ const DefaultTable = ({
|
|
|
194
200
|
storeName,
|
|
195
201
|
tableColumns: columnsForTable,
|
|
196
202
|
});
|
|
197
|
-
const closeCreateForm
|
|
198
|
-
|
|
199
|
-
const handleAdd = useCallback(() => {
|
|
200
|
-
if (typeof onAdd === 'function') {
|
|
201
|
-
return onAdd();
|
|
202
|
-
}
|
|
203
|
-
if (!canUseDefaultCreateForm) {
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
setIsCreateFormOpen(true);
|
|
207
|
-
return null;
|
|
208
|
-
}, [canUseDefaultCreateForm, onAdd]);
|
|
209
|
-
const resolvedOnAdd = typeof onAdd === 'function' || canUseDefaultCreateForm
|
|
210
|
-
? handleAdd
|
|
211
|
-
: null;
|
|
203
|
+
const {isCreateFormOpen, closeCreateForm, resolvedOnAdd, handleDefaultCreateSaved} =
|
|
204
|
+
useDefaultTableCreateForm({onAdd, onSaved, resolvedActions, handleRefresh});
|
|
212
205
|
const hasAddAction =
|
|
213
|
-
(addConfig === true || add === true) &&
|
|
206
|
+
add !== false && (addConfig === true || add === true) &&
|
|
214
207
|
typeof resolvedOnAdd === 'function';
|
|
215
208
|
const shouldRenderFloatingAddButton =
|
|
216
209
|
hasAddAction &&
|
|
@@ -219,14 +212,6 @@ const DefaultTable = ({
|
|
|
219
212
|
hasAddAction &&
|
|
220
213
|
showToolbar !== false &&
|
|
221
214
|
normalizedAddButtonPlacement === 'bottom';
|
|
222
|
-
const handleDefaultCreateSaved = useCallback(
|
|
223
|
-
savedItem => {
|
|
224
|
-
closeCreateForm();
|
|
225
|
-
onSaved?.(savedItem, null);
|
|
226
|
-
handleRefresh?.();
|
|
227
|
-
},
|
|
228
|
-
[closeCreateForm, handleRefresh, onSaved],
|
|
229
|
-
);
|
|
230
215
|
const debugFallbackParameters = useMemo(() => {
|
|
231
216
|
if (autoMode) {
|
|
232
217
|
return buildRequestQuery(currentPage || 1, false);
|
|
@@ -237,7 +222,11 @@ const DefaultTable = ({
|
|
|
237
222
|
sort: resolvedSort || null,
|
|
238
223
|
};
|
|
239
224
|
}, [autoMode, buildRequestQuery, currentPage, requestParamsSeed, resolvedSort, storeFilters]);
|
|
225
|
+
const previousCompactRef = useRef(isCompactView);
|
|
226
|
+
const enteringCompact = isCompactView && !previousCompactRef.current;
|
|
227
|
+
useEffect(() => {previousCompactRef.current = isCompactView;}, [isCompactView]);
|
|
240
228
|
const effectiveViewMode =
|
|
229
|
+
enteringCompact && forceCardsOnCompact !== false ? 'cards' :
|
|
241
230
|
currentConfigs.viewMode ||
|
|
242
231
|
storedViewMode ||
|
|
243
232
|
(isCompactView && forceCardsOnCompact !== false ? 'cards' : initialViewMode);
|
|
@@ -245,6 +234,12 @@ const DefaultTable = ({
|
|
|
245
234
|
const defaultTableConfigs = useMemo(
|
|
246
235
|
() => ({
|
|
247
236
|
...storeDeclaredConfigs,
|
|
237
|
+
appearance,
|
|
238
|
+
paginationMode,
|
|
239
|
+
currentPage,
|
|
240
|
+
pageSizeNumber,
|
|
241
|
+
onPageChange: goToPage,
|
|
242
|
+
paginationLoading: resolvedIsLoading,
|
|
248
243
|
add,
|
|
249
244
|
addButtonPlacement: normalizedAddButtonPlacement,
|
|
250
245
|
addLabel: resolvedAddLabel,
|
|
@@ -311,6 +306,7 @@ const DefaultTable = ({
|
|
|
311
306
|
viewMode: effectiveViewMode,
|
|
312
307
|
}),
|
|
313
308
|
[
|
|
309
|
+
appearance, paginationMode, currentPage, pageSizeNumber, goToPage, resolvedIsLoading,
|
|
314
310
|
add,
|
|
315
311
|
normalizedAddButtonPlacement,
|
|
316
312
|
resolvedAddLabel,
|
|
@@ -373,6 +369,7 @@ const DefaultTable = ({
|
|
|
373
369
|
const defaultTableConfigsSignature = useMemo(
|
|
374
370
|
() =>
|
|
375
371
|
stableSerialize({
|
|
372
|
+
appearance, paginationMode, currentPage, pageSizeNumber, resolvedIsLoading,
|
|
376
373
|
configuredImport: storeDeclaredConfigs.import || null,
|
|
377
374
|
add,
|
|
378
375
|
addButtonPlacement: normalizedAddButtonPlacement,
|
|
@@ -404,10 +401,11 @@ const DefaultTable = ({
|
|
|
404
401
|
showTotalItemsInFooter,
|
|
405
402
|
storedViewMode,
|
|
406
403
|
tableFiltersVisible,
|
|
407
|
-
|
|
404
|
+
sortedData: stableSerialize(sortedData),
|
|
408
405
|
toolbarActionsLength: Array.isArray(toolbarActions) ? toolbarActions.length : 0,
|
|
409
406
|
}),
|
|
410
407
|
[
|
|
408
|
+
appearance, paginationMode, currentPage, pageSizeNumber, goToPage, resolvedIsLoading,
|
|
411
409
|
add,
|
|
412
410
|
normalizedAddButtonPlacement,
|
|
413
411
|
resolvedAddLabel,
|
|
@@ -442,7 +440,7 @@ const DefaultTable = ({
|
|
|
442
440
|
showTotalItemsInFooter,
|
|
443
441
|
storedViewMode,
|
|
444
442
|
tableFiltersVisible,
|
|
445
|
-
sortedData
|
|
443
|
+
sortedData,
|
|
446
444
|
toolbarActions,
|
|
447
445
|
],
|
|
448
446
|
);
|
|
@@ -452,6 +450,8 @@ const DefaultTable = ({
|
|
|
452
450
|
// opening Adicionar on DefaultTable (categories). Sync only in the hook
|
|
453
451
|
// when defaultTableConfigsSignature changes.
|
|
454
452
|
useDefaultTableStoreSync({
|
|
453
|
+
filters,
|
|
454
|
+
onFilterChange,
|
|
455
455
|
columns,
|
|
456
456
|
columnsForTable,
|
|
457
457
|
data,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, {useState} from 'react';
|
|
2
2
|
import { FlatList, Text, View } from 'react-native';
|
|
3
3
|
import { useStore } from '@store';
|
|
4
4
|
import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
|
|
@@ -16,12 +16,16 @@ import {
|
|
|
16
16
|
shouldIncludeColumn,
|
|
17
17
|
} from './DefaultTable.utils';
|
|
18
18
|
import styles from './DefaultTable.styles';
|
|
19
|
+
import compactStyles from './DefaultTableCompact.styles';
|
|
20
|
+
import useDefaultTableRowInteraction from './useDefaultTableRowInteraction';
|
|
19
21
|
import useDefaultTableTheme from './useDefaultTableTheme';
|
|
20
22
|
|
|
21
23
|
// Vertical cards must keep their intrinsic height; zero basis is only for grid rows.
|
|
22
24
|
const intrinsicHeight = {flexBasis: 'auto', flexGrow: 0, flexShrink: 0};
|
|
23
25
|
|
|
24
26
|
const DefaultTableCards = ({ storeName }) => {
|
|
27
|
+
const [listWidth, setListWidth] = useState(0);
|
|
28
|
+
const {canOpenRow, setRowInteraction} = useDefaultTableRowInteraction();
|
|
25
29
|
const store = useStore(storeName);
|
|
26
30
|
const configs = store?.getters?.configs || {};
|
|
27
31
|
const { palette } = useDefaultTableTheme();
|
|
@@ -38,6 +42,9 @@ const DefaultTableCards = ({ storeName }) => {
|
|
|
38
42
|
configs.cardListProps && typeof configs.cardListProps === 'object'
|
|
39
43
|
? configs.cardListProps
|
|
40
44
|
: {};
|
|
45
|
+
const compact = configs.appearance === 'compact';
|
|
46
|
+
const gridColumns = cardListProps.numColumns || 1;
|
|
47
|
+
const compactCardWidth = listWidth ? Math.max(0, (listWidth - 24 - 12 * (gridColumns - 1)) / gridColumns) : undefined;
|
|
41
48
|
const {
|
|
42
49
|
contentContainerStyle,
|
|
43
50
|
key: cardListKey,
|
|
@@ -67,7 +74,7 @@ const DefaultTableCards = ({ storeName }) => {
|
|
|
67
74
|
const renderField = (fieldName, options = {}) => (
|
|
68
75
|
<DefaultTableInput
|
|
69
76
|
fieldName={fieldName}
|
|
70
|
-
options={options}
|
|
77
|
+
options={{...options, onInteractionChange: blocked => {options.onInteractionChange?.(blocked); setRowInteraction(row, {key: fieldName}, blocked);}}}
|
|
71
78
|
row={row}
|
|
72
79
|
storeName={storeName}
|
|
73
80
|
variant="card"
|
|
@@ -89,10 +96,10 @@ const DefaultTableCards = ({ storeName }) => {
|
|
|
89
96
|
component={RowActionsComponent}
|
|
90
97
|
helpers={{
|
|
91
98
|
openEdit: () => configs.onEditRow?.(row),
|
|
92
|
-
openRow: hasRowPress ? () => configs.onRowPress(row) : null,
|
|
99
|
+
openRow: hasRowPress ? () => {if (canOpenRow(row)) configs.onRowPress(row);} : null,
|
|
93
100
|
}}
|
|
94
101
|
openEdit={() => configs.onEditRow?.(row)}
|
|
95
|
-
openRow={hasRowPress ? () => configs.onRowPress(row) : null}
|
|
102
|
+
openRow={hasRowPress ? () => {if (canOpenRow(row)) configs.onRowPress(row);} : null}
|
|
96
103
|
row={row}
|
|
97
104
|
storeName={storeName}
|
|
98
105
|
/>
|
|
@@ -112,20 +119,21 @@ const DefaultTableCards = ({ storeName }) => {
|
|
|
112
119
|
(flatListProps.numColumns || 1) === 1 ? intrinsicHeight : null,
|
|
113
120
|
hasRowActions ? styles.cardItemWithActions : null,
|
|
114
121
|
rowStyleValue,
|
|
122
|
+
compact ? [compactStyles.cardItem, {flex: 0, flexGrow: 0, flexShrink: 0, flexBasis: 'auto', width: compactCardWidth, maxWidth: compactCardWidth, backgroundColor: palette.cardBackground || palette.panelBackground, borderColor: palette.cardBorder || palette.inputBorder}] : null,
|
|
115
123
|
]}
|
|
116
124
|
>
|
|
117
|
-
<View style={[styles.cardContent, hasRowActions ? null : intrinsicHeight]}>
|
|
125
|
+
<View style={[styles.cardContent, hasRowActions ? null : intrinsicHeight, compact ? {flex: 0, flexBasis: 'auto', flexGrow: 0} : null]}>
|
|
118
126
|
{configs.renderCard({
|
|
119
127
|
item: row,
|
|
120
128
|
openEdit: () => configs.onEditRow?.(row),
|
|
121
|
-
openRow: hasRowPress ? () => configs.onRowPress(row) : null,
|
|
129
|
+
openRow: hasRowPress ? () => {if (canOpenRow(row)) configs.onRowPress(row);} : null,
|
|
122
130
|
renderField,
|
|
123
131
|
renderValue,
|
|
124
132
|
row,
|
|
125
133
|
})}
|
|
126
134
|
</View>
|
|
127
135
|
{hasRowActions ? (
|
|
128
|
-
<View style={styles.cardActions}>
|
|
136
|
+
<View style={compact ? [compactStyles.cardActions, {backgroundColor: palette.tableFooterBackground, borderTopColor: palette.border}] : styles.cardActions}>
|
|
129
137
|
{customRowActions}
|
|
130
138
|
{editButton}
|
|
131
139
|
</View>
|
|
@@ -177,6 +185,8 @@ const DefaultTableCards = ({ storeName }) => {
|
|
|
177
185
|
<FlatList
|
|
178
186
|
key={listKey || cardListKey || `cards-${flatListProps.numColumns || 1}`}
|
|
179
187
|
{...flatListProps}
|
|
188
|
+
onLayout={event => {flatListProps.onLayout?.(event); setListWidth(event.nativeEvent.layout.width);}}
|
|
189
|
+
columnWrapperStyle={compact && gridColumns > 1 ? {gap: 12} : flatListProps.columnWrapperStyle}
|
|
180
190
|
data={sortedData}
|
|
181
191
|
keyExtractor={getRowKey}
|
|
182
192
|
renderItem={({ item, index }) => renderCardItem(item, index)}
|
|
@@ -184,6 +194,7 @@ const DefaultTableCards = ({ storeName }) => {
|
|
|
184
194
|
contentContainerStyle={[
|
|
185
195
|
styles.cardsGrid,
|
|
186
196
|
contentContainerStyle,
|
|
197
|
+
compact ? {padding: 12, gap: 12} : null,
|
|
187
198
|
hasBottomAddButton ? styles.cardsGridWithBottomAdd : null,
|
|
188
199
|
]}
|
|
189
200
|
ListEmptyComponent={(
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const isActiveTableFilter = value => {
|
|
2
|
+
if (value === null || value === undefined || value === '') return false;
|
|
3
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
4
|
+
if (typeof value === 'object') {
|
|
5
|
+
if (value.shortcut) return value.shortcut !== 'all';
|
|
6
|
+
if ('value' in value) return isActiveTableFilter(value.value);
|
|
7
|
+
return Object.values(value).some(isActiveTableFilter);
|
|
8
|
+
}
|
|
9
|
+
return true;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const getCompactFilterLabel = value => {
|
|
13
|
+
if (Array.isArray(value)) return value.map(getCompactFilterLabel).join(', ');
|
|
14
|
+
if (value && typeof value === 'object') {
|
|
15
|
+
if (value.shortcut) return ({today: 'Hoje', yesterday: 'Ontem', all: 'Todos', custom: 'Personalizado'})[value.shortcut] || value.shortcut;
|
|
16
|
+
return String(value.label || value.value || 'Selecionado');
|
|
17
|
+
}
|
|
18
|
+
return String(value ?? '');
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const getVisiblePageNumbers = (page, totalPages) => {
|
|
22
|
+
const start = Math.max(1, Math.min(page - 1, totalPages - 2));
|
|
23
|
+
return Array.from({length: Math.min(3, totalPages)}, (_, index) => start + index);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const getCompactColumnLabel = (column, fallback) => column?.compactLabel || fallback;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {StyleSheet} from 'react-native';
|
|
2
|
+
|
|
3
|
+
export default StyleSheet.create({
|
|
4
|
+
toolbar: {padding: 12, gap: 8, borderBottomWidth: 1, flexShrink: 0},
|
|
5
|
+
row: {flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap', gap: 8},
|
|
6
|
+
search: {flex: 1, maxWidth: 620, minWidth: 210, flexDirection: 'row', alignItems: 'center', gap: 8, borderWidth: 1, borderRadius: 8, paddingHorizontal: 10},
|
|
7
|
+
searchMobile: {flexBasis: '100%', maxWidth: '100%', minWidth: 0},
|
|
8
|
+
input: {flex: 1, minWidth: 0, height: 42, fontSize: 14},
|
|
9
|
+
inputMobile: {fontSize: 16},
|
|
10
|
+
button: {minHeight: 44, minWidth: 44, paddingHorizontal: 10, borderWidth: 1, borderRadius: 8, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 6},
|
|
11
|
+
label: {fontSize: 12, fontWeight: '600'},
|
|
12
|
+
small: {fontSize: 12},
|
|
13
|
+
chips: {flexDirection: 'row', flexWrap: 'wrap', gap: 6, alignItems: 'center'},
|
|
14
|
+
chip: {minHeight: 30, maxWidth: '100%', borderWidth: 1, borderRadius: 18, paddingHorizontal: 9, flexDirection: 'row', alignItems: 'center', gap: 6},
|
|
15
|
+
secondary: {flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 8},
|
|
16
|
+
mode: {flexDirection: 'row', gap: 3},
|
|
17
|
+
backdrop: {flex: 1, alignItems: 'center', justifyContent: 'center', padding: 16},
|
|
18
|
+
sheet: {width: '100%', maxWidth: 480, maxHeight: '85%', borderWidth: 1, borderRadius: 14, padding: 16, gap: 12},
|
|
19
|
+
sheetHeader: {flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 10},
|
|
20
|
+
sheetTitle: {fontSize: 17, fontWeight: '600'},
|
|
21
|
+
option: {minHeight: 44, paddingVertical: 10, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 8},
|
|
22
|
+
footer: {padding: 10, flexShrink: 0, borderTopWidth: 1, flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'space-between', gap: 8},
|
|
23
|
+
pager: {flexDirection: 'row', flexWrap: 'wrap', gap: 4, alignItems: 'center'},
|
|
24
|
+
page: {minWidth: 44, minHeight: 44, borderWidth: 1, borderRadius: 7, justifyContent: 'center', alignItems: 'center'},
|
|
25
|
+
disabled: {opacity: 0.45},
|
|
26
|
+
cardItem: {flexDirection: 'column', alignItems: 'stretch', borderWidth: 1, borderRadius: 12, overflow: 'hidden', margin: 0},
|
|
27
|
+
cardActions: {alignSelf: 'stretch', flexDirection: 'row', justifyContent: 'flex-end', borderTopWidth: 1, paddingHorizontal: 12, paddingVertical: 8},
|
|
28
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import React, {useEffect, useState} from 'react';
|
|
2
|
+
import {Text, TextInput, TouchableOpacity, View, useWindowDimensions} 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 {getSortField, isSortableColumn} from './DefaultTable.utils';
|
|
8
|
+
import {persistTableFiltersPreference, persistTableViewModePreference} from '../../utils/tableVisibleColumnsPreferences';
|
|
9
|
+
import DefaultCompactDialog from './DefaultCompactDialog';
|
|
10
|
+
import DefaultFiltersModal from './DefaultFiltersModal';
|
|
11
|
+
import DefaultColumnMenu from './DefaultColumnMenu';
|
|
12
|
+
import DefaultDebug from './DefaultDebug';
|
|
13
|
+
import DefaultToolbarAction from './DefaultToolbarAction';
|
|
14
|
+
import useDefaultTableTheme from './useDefaultTableTheme';
|
|
15
|
+
import {getCompactColumnLabel, getCompactFilterLabel, isActiveTableFilter} from './DefaultTableCompact.helpers';
|
|
16
|
+
import s from './DefaultTableCompact.styles';
|
|
17
|
+
|
|
18
|
+
export default function DefaultTableCompactToolbar({storeName, actions = [], showControls = true}) {
|
|
19
|
+
const store = useStore(storeName);
|
|
20
|
+
const configs = store?.getters?.configs || {};
|
|
21
|
+
const filters = store?.getters?.filters || {};
|
|
22
|
+
const columns = store?.getters?.columns || [];
|
|
23
|
+
const {themeColors: c} = useDefaultTableTheme();
|
|
24
|
+
const {width} = useWindowDimensions();
|
|
25
|
+
const mobile = width <= (configs.compactBreakpoint || 768);
|
|
26
|
+
const [modal, setModal] = useState('');
|
|
27
|
+
const searchKey = configs.searchKey || 'search';
|
|
28
|
+
const [draft, setDraft] = useState(String(filters[searchKey] || ''));
|
|
29
|
+
useEffect(() => setDraft(String(filters[searchKey] || '')), [filters[searchKey]]);
|
|
30
|
+
const entries = Object.entries(filters).filter(([, value]) => isActiveTableFilter(value));
|
|
31
|
+
const text = c.textPrimary || c.text;
|
|
32
|
+
const muted = c.textSecondary || text;
|
|
33
|
+
const panel = c.panelBackground || c.cardBackground || c.appBackground;
|
|
34
|
+
const border = c.inputBorder || c.cardBorder || c.border;
|
|
35
|
+
const apply = next => {
|
|
36
|
+
persistTableFiltersPreference(configs.tablePreferenceScope, next);
|
|
37
|
+
if (configs.onFilterChange) configs.onFilterChange(next);
|
|
38
|
+
else store.actions?.setFilters?.(next);
|
|
39
|
+
};
|
|
40
|
+
const remove = key => {const next = {...filters}; delete next[key]; apply(next);};
|
|
41
|
+
const submit = () => {const next = {...filters}; if (draft.trim()) next[searchKey] = draft.trim(); else delete next[searchKey]; apply(next);};
|
|
42
|
+
const button = (label, icon, onPress, selected = false, primary = false) => (
|
|
43
|
+
<TouchableOpacity accessibilityRole="button" accessibilityLabel={label} accessibilityState={{selected}} onPress={onPress}
|
|
44
|
+
style={[s.button, {backgroundColor: primary ? c.buttonBackground : selected ? c.chipSelectedBackground || panel : panel, borderColor: border}, !mobile && icon === 'sliders' ? {marginLeft: 'auto'} : null]}>
|
|
45
|
+
<Icon name={icon} size={16} color={primary ? c.buttonIcon || c.buttonText : text} />
|
|
46
|
+
{label && (!mobile || width >= 360 && (label === 'Filtros' || primary)) ? <Text style={[s.label, {color: primary ? c.buttonText : text}]}>{primary && mobile ? 'Novo' : label}</Text> : null}
|
|
47
|
+
</TouchableOpacity>
|
|
48
|
+
);
|
|
49
|
+
const mode = configs.effectiveViewMode || 'table';
|
|
50
|
+
const setMode = value => {
|
|
51
|
+
persistTableViewModePreference(configs.tablePreferenceScope, value);
|
|
52
|
+
store.actions?.setConfigs?.({...configs, effectiveViewMode: value, viewMode: value});
|
|
53
|
+
};
|
|
54
|
+
const canAdd = configs.add !== false && (configs.add === true || store.getters?.add === true) && configs.onAdd;
|
|
55
|
+
const buttonLabel = configs.addLabel || 'Novo pedido';
|
|
56
|
+
const columnButton = () => button('Colunas', 'columns', () => setModal('columns'));
|
|
57
|
+
const addButton = () => button(buttonLabel, 'plus', configs.onAdd, false, true);
|
|
58
|
+
const sortColumn = columns.find(column => getSortField(column) === configs.resolvedSort?.field);
|
|
59
|
+
const labelFor = column => getCompactColumnLabel(column, formatStoreColumnLabel({columns, fieldName: getColumnKey(column), fallbackLabel: column?.label || getColumnKey(column), storeName}));
|
|
60
|
+
return (
|
|
61
|
+
<View style={[s.toolbar, {backgroundColor: c.toolbarBackground || panel, borderBottomColor: border}]}>
|
|
62
|
+
<View style={[s.row, {gap: mobile ? 6 : 8}]}>
|
|
63
|
+
{configs.showSearch !== false ? <View style={[s.search, mobile && s.searchMobile, {borderColor: border, backgroundColor: c.inputBackground || panel}]}>
|
|
64
|
+
<Icon name="search" size={16} color={c.inputIcon || muted} />
|
|
65
|
+
<TextInput accessibilityLabel={configs.searchPlaceholder || 'Buscar'} placeholder={configs.searchPlaceholder || 'Buscar'} placeholderTextColor={c.inputPlaceholderText || muted}
|
|
66
|
+
value={draft} onChangeText={setDraft} onSubmitEditing={submit} returnKeyType="search" style={[s.input, mobile && s.inputMobile, {color: c.inputText || text}]} />
|
|
67
|
+
<TouchableOpacity accessibilityRole="button" accessibilityLabel="Pesquisar" onPress={submit} style={{padding: 8}}><Icon name="arrow-right" size={16} color={text} /></TouchableOpacity>
|
|
68
|
+
</View> : null}
|
|
69
|
+
{showControls ? <>
|
|
70
|
+
{button('Filtros', 'sliders', () => setModal('filters'))}
|
|
71
|
+
<View style={s.mode}>{button('Tabela', 'list', () => setMode('table'), mode === 'table')}{button('Cards', 'grid', () => setMode('cards'), mode === 'cards')}</View>
|
|
72
|
+
{!mobile ? columnButton() : null}
|
|
73
|
+
{button('Mais opções', 'more-horizontal', () => setModal('more'))}
|
|
74
|
+
{canAdd ? addButton() : null}
|
|
75
|
+
</> : null}
|
|
76
|
+
</View>
|
|
77
|
+
{entries.length ? <View style={s.chips}>{entries.map(([key, value]) => {
|
|
78
|
+
const column = columns.find(item => getColumnKey(item) === key);
|
|
79
|
+
const options = column ? configs.getOptionsForColumn?.(column) || column.options || [] : [];
|
|
80
|
+
const valueLabel = Array.isArray(options) ? (Array.isArray(value) ? value : [value]).map(item => options.find(option => String(option.value ?? option.key) === String(item))?.label || getCompactFilterLabel(item)).join(', ') : getCompactFilterLabel(value);
|
|
81
|
+
const label = `${column ? labelFor(column) + ': ' : key === searchKey ? 'Busca: ' : key + ': '}${valueLabel}`;
|
|
82
|
+
return <TouchableOpacity key={key} accessibilityRole="button" accessibilityLabel={`Remover filtro ${label}`} onPress={() => remove(key)} style={[s.chip, {borderColor: border, backgroundColor: c.chipBackground || panel}]}>
|
|
83
|
+
<Text numberOfLines={1} style={[s.small, {color: c.chipText || text, flexShrink: 1}]}>{label}</Text><Icon name="x" size={12} color={text} />
|
|
84
|
+
</TouchableOpacity>;
|
|
85
|
+
})}<TouchableOpacity accessibilityRole="button" onPress={() => apply({})}><Text style={[s.small, {color: muted}]}>Limpar filtros</Text></TouchableOpacity></View> : null}
|
|
86
|
+
<View style={s.secondary}>
|
|
87
|
+
<Text style={[s.small, {color: muted}]}>{configs.showTotalItemsInFooter !== false ? `${store.getters?.totalItems ?? 0} ${Number(store.getters?.totalItems) === 1 ? 'resultado' : 'resultados'}` : 'Pedidos'}</Text>
|
|
88
|
+
{showControls ? <TouchableOpacity accessibilityRole="button" accessibilityLabel="Escolher ordenação" onPress={() => setModal('sort')} style={s.row}>
|
|
89
|
+
<Icon name="arrow-down" size={14} color={muted} /><Text style={[s.small, {color: text}]}>{sortColumn ? labelFor(sortColumn) : 'Ordenar'} · {configs.resolvedSort?.direction === 'asc' ? '↑' : '↓'}</Text>
|
|
90
|
+
</TouchableOpacity> : null}
|
|
91
|
+
</View>
|
|
92
|
+
<DefaultFiltersModal storeName={storeName} visible={modal === 'filters'} deferred onClose={() => setModal('')} />
|
|
93
|
+
<DefaultColumnMenu storeName={storeName} visible={modal === 'columns'} onClose={() => setModal('')} />
|
|
94
|
+
<DefaultCompactDialog visible={modal === 'more' || modal === 'sort'} title={modal === 'sort' ? 'Ordenação' : 'Mais opções'}
|
|
95
|
+
description={modal === 'sort' ? 'Escolha a coluna. Toque novamente para inverter a ordem.' : undefined}
|
|
96
|
+
onClose={() => setModal('')} bodyHeight={modal === 'sort' ? columns.filter(isSortableColumn).length * 54 + 32 : 180}>
|
|
97
|
+
{modal === 'sort' ? columns.filter(isSortableColumn).map(column => <TouchableOpacity key={getColumnKey(column)} accessibilityRole="button"
|
|
98
|
+
accessibilityLabel={`Ordenar por ${labelFor(column)}`} style={[s.option, {borderBottomWidth: 1, borderBottomColor: c.tableRowBorder}]}
|
|
99
|
+
onPress={() => {configs.requestSort?.(column); setModal('');}}>
|
|
100
|
+
<Text style={{color: text, fontSize: 14}}>{labelFor(column)}</Text><Text style={{color: muted}}>{getSortField(column) === configs.resolvedSort?.field ? configs.resolvedSort.direction === 'asc' ? '↑' : '↓' : '↕'}</Text>
|
|
101
|
+
</TouchableOpacity>) : <View style={{gap: 8}}>{mobile ? <DefaultToolbarAction action={{label: 'Colunas visíveis', icon: 'columns', onPress: () => setModal('columns'), color: text,
|
|
102
|
+
style: {width: '100%', minHeight: 48, justifyContent: 'flex-start', paddingHorizontal: 12, borderWidth: 0, backgroundColor: c.inputBackground}, labelStyle: {fontSize: 14}}} /> : null}
|
|
103
|
+
{actions.filter(action => !action.hidden).map(action => <DefaultToolbarAction key={action.key || action.icon}
|
|
104
|
+
action={{...action, label: action.label || action.accessibilityLabel, color: text,
|
|
105
|
+
style: {width: '100%', minHeight: 48, justifyContent: 'flex-start', paddingHorizontal: 12, borderWidth: 0, backgroundColor: c.inputBackground}, labelStyle: {fontSize: 14, fontWeight: '400'},
|
|
106
|
+
onPress: () => {setModal(''); action.onPress?.();}}} />)}
|
|
107
|
+
<DefaultDebug storeName={storeName} />
|
|
108
|
+
</View>}
|
|
109
|
+
</DefaultCompactDialog>
|
|
110
|
+
</View>
|
|
111
|
+
);
|
|
112
|
+
}
|