@controleonline/ui-default 1.0.277 → 1.0.279
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 +27 -27
- package/src/react/components/table/DefaultTableCards.js +44 -10
- 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 +38 -1
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import { View } from 'react-native';
|
|
3
3
|
import { useStore } from '@store';
|
|
4
4
|
import DefaultInput from '../inputs/DefaultInput';
|
|
5
|
+
import Formatter from '@controleonline/ui-common/src/utils/formatter';
|
|
5
6
|
import {
|
|
6
7
|
formatSaveValue,
|
|
7
8
|
getColumnKey,
|
|
8
9
|
isEditableColumn,
|
|
9
10
|
normalizeId,
|
|
11
|
+
resolveCellPresentation,
|
|
10
12
|
} from '../inputs/defaultInputUtils';
|
|
11
13
|
import { getColumnStyle } from './DefaultTable.utils';
|
|
12
14
|
import styles from './DefaultTable.styles';
|
|
@@ -58,34 +60,50 @@ const DefaultTableInput = ({
|
|
|
58
60
|
const configs = store?.getters?.configs || {};
|
|
59
61
|
const [isEditing, setIsEditing] = useState(false);
|
|
60
62
|
const [isSaving, setIsSaving] = useState(false);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
options.onInteractionChange?.(isEditing || isSaving);
|
|
65
|
+
return () => options.onInteractionChange?.(false);
|
|
66
|
+
}, [isEditing, isSaving, options.onInteractionChange]);
|
|
61
67
|
const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
|
|
62
68
|
const hasRowPress = false;
|
|
63
|
-
const { resolvedAccentColor } = useDefaultTableTheme();
|
|
69
|
+
const { resolvedAccentColor, themeColors } = useDefaultTableTheme();
|
|
64
70
|
const column = columnProp || columns.find(item => getColumnKey(item) === fieldName);
|
|
65
71
|
|
|
66
72
|
if (!column) return null;
|
|
67
73
|
|
|
68
74
|
const resolvedFieldName = getColumnKey(column);
|
|
75
|
+
const compact = configs.appearance === 'compact';
|
|
76
|
+
const presentation = resolveCellPresentation({column, columns, row, storeName});
|
|
77
|
+
const statusColor = compact && resolvedFieldName === 'status' && typeof column.compactStatusColor === 'function'
|
|
78
|
+
? column.compactStatusColor(row, themeColors) : presentation.color || themeColors.textSecondary;
|
|
79
|
+
const compactType = storeName === 'orders' && resolvedFieldName === 'orderType' ? ({sale: 'Venda', purchase: 'Compra', transfer: 'Transferência', loss: 'Perda', cart: 'Carrinho', tab: 'Comanda', table: 'Mesa', stamp: 'Carimbo'})[row.orderType] : undefined;
|
|
80
|
+
const dateField = compact && storeName === 'orders' && ['orderDate', 'alterDate'].includes(resolvedFieldName);
|
|
81
|
+
const rawValue = row[resolvedFieldName];
|
|
82
|
+
const time = typeof rawValue === 'string' ? /[T ](\d{2}:\d{2})/.exec(rawValue)?.[1] : '';
|
|
83
|
+
const compactDisplay = compact && storeName === 'orders' ? (resolvedFieldName === 'price' ? Formatter.formatMoney(rawValue || 0) : dateField && rawValue ? `${Formatter.formatDateYmdTodmY(rawValue, false)}${time && (time !== '00:00' || resolvedFieldName === 'alterDate') ? '\n' + time : ''}` : compactType) : undefined;
|
|
84
|
+
const compactTextStyle = compact ? {fontSize: 13, fontWeight: column.isIdentity ? '600' : '400', ...(resolvedFieldName === 'status' ? {color: statusColor} : {color: themeColors.textPrimary})} : null;
|
|
85
|
+
const statusStyle = compact && resolvedFieldName === 'status' ? {width: 'auto', alignSelf: 'flex-start', borderWidth: 1, borderColor: statusColor, borderRadius: 18, paddingHorizontal: 8, paddingVertical: 3, backgroundColor: /^#[0-9a-f]{6}$/i.test(statusColor) ? statusColor + '10' : themeColors.panelBackground} : null;
|
|
69
86
|
const input = (
|
|
70
87
|
<DefaultInput
|
|
71
88
|
accentColor={options.accentColor || resolvedAccentColor}
|
|
72
89
|
column={column}
|
|
73
90
|
columns={columns}
|
|
74
|
-
containerStyle={options.containerStyle}
|
|
91
|
+
containerStyle={[options.containerStyle, statusStyle, compact && column.isIdentity ? {alignSelf: 'flex-start', width: 'auto', borderWidth: 1, borderRadius: 8, borderColor: themeColors.border, backgroundColor: themeColors.inputBackground, paddingHorizontal: 9, paddingVertical: 5} : null]}
|
|
75
92
|
defaultColor={options.defaultColor || configs.defaultColor}
|
|
76
|
-
displayValue={options.displayValue}
|
|
93
|
+
displayValue={options.displayValue ?? compactDisplay}
|
|
77
94
|
editing={isEditing}
|
|
78
95
|
getOptionsForColumn={configs.getOptionsForColumn}
|
|
79
96
|
inputStyle={options.inputStyle}
|
|
80
97
|
label={options.label}
|
|
81
|
-
numberOfLines={options.numberOfLines}
|
|
82
|
-
onCancelEditing={() => setIsEditing(false)}
|
|
98
|
+
numberOfLines={options.numberOfLines ?? (dateField ? 2 : undefined)}
|
|
99
|
+
onCancelEditing={() => {options.onInteractionChange?.(isSaving); setIsEditing(false);}}
|
|
83
100
|
onSave={value => {
|
|
84
101
|
if (typeof store?.actions?.save !== 'function') {
|
|
85
102
|
setIsEditing(false);
|
|
86
103
|
return Promise.resolve(null);
|
|
87
104
|
}
|
|
88
105
|
|
|
106
|
+
options.onInteractionChange?.(true);
|
|
89
107
|
setIsSaving(true);
|
|
90
108
|
const savedItemPatch = buildSavedItemPatch(column, resolvedFieldName, value);
|
|
91
109
|
const storeMeta =
|
|
@@ -109,12 +127,13 @@ const DefaultTableInput = ({
|
|
|
109
127
|
return savedItem;
|
|
110
128
|
})
|
|
111
129
|
.finally(() => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
130
|
+
options.onInteractionChange?.(false);
|
|
131
|
+
setIsSaving(false);
|
|
132
|
+
setIsEditing(false);
|
|
133
|
+
});
|
|
115
134
|
}}
|
|
116
|
-
onStartEditing={() => setIsEditing(true)}
|
|
117
|
-
readTextStyle={options.readTextStyle || options.textStyle}
|
|
135
|
+
onStartEditing={() => {options.onInteractionChange?.(true); setIsEditing(true);}}
|
|
136
|
+
readTextStyle={[compactTextStyle, options.readTextStyle || options.textStyle]}
|
|
118
137
|
row={row}
|
|
119
138
|
saving={isSaving}
|
|
120
139
|
showLabel={options.showLabel}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {Text, TouchableOpacity, View, useWindowDimensions} from 'react-native';
|
|
3
|
+
import Icon from 'react-native-vector-icons/Feather';
|
|
4
|
+
import {useStore} from '@store';
|
|
5
|
+
import useDefaultTableTheme from './useDefaultTableTheme';
|
|
6
|
+
import {getVisiblePageNumbers} from './DefaultTableCompact.helpers';
|
|
7
|
+
import s from './DefaultTableCompact.styles';
|
|
8
|
+
|
|
9
|
+
export default function DefaultTablePaginationBar({storeName}) {
|
|
10
|
+
const mobile = useWindowDimensions().width <= 768;
|
|
11
|
+
const store = useStore(storeName);
|
|
12
|
+
const configs = store?.getters?.configs || {};
|
|
13
|
+
const {themeColors: c} = useDefaultTableTheme();
|
|
14
|
+
if (configs.paginationMode !== 'pages') return null;
|
|
15
|
+
const page = configs.currentPage || 1;
|
|
16
|
+
const size = configs.pageSizeNumber || 20;
|
|
17
|
+
const total = Number(store.getters.totalItems || 0);
|
|
18
|
+
const pages = Math.max(1, Math.ceil(total / size));
|
|
19
|
+
const busy = Boolean(configs.paginationLoading);
|
|
20
|
+
const text = c.tableFooterText || c.textPrimary;
|
|
21
|
+
const background = c.tableFooterBackground || c.panelBackground;
|
|
22
|
+
const border = c.tableFooterBorder || c.inputBorder;
|
|
23
|
+
const button = (value, icon, label, disabled = false) => <TouchableOpacity key={label} accessibilityRole="button" accessibilityLabel={label}
|
|
24
|
+
accessibilityState={{disabled: disabled || busy, selected: !icon && page === value}} disabled={disabled || busy}
|
|
25
|
+
onPress={() => configs.onPageChange?.(value)} style={[s.page, {backgroundColor: !icon && page === value ? c.chipSelectedBackground || c.badgeBackground : background, borderColor: border}, (disabled || busy) && s.disabled]}>
|
|
26
|
+
{icon ? <Icon name={icon} size={16} color={text} /> : <Text style={[s.small, {color: text}]}>{value}</Text>}
|
|
27
|
+
</TouchableOpacity>;
|
|
28
|
+
return <View testID="default-table-pagination" style={[s.footer, {backgroundColor: background, borderTopColor: border}]}>
|
|
29
|
+
<Text accessibilityLiveRegion="polite" style={[s.small, {color: text}]}>{configs.showTotalItemsInFooter === false ? `Página ${page}` : `${total ? (page - 1) * size + 1 : 0}–${Math.min(page * size, total)} de ${total}`}</Text>
|
|
30
|
+
<View style={s.pager}>
|
|
31
|
+
{!mobile ? button(1, 'chevrons-left', 'Primeira página', page === 1) : null}
|
|
32
|
+
{button(page - 1, 'chevron-left', 'Página anterior', page === 1)}
|
|
33
|
+
{getVisiblePageNumbers(page, pages).map(value => button(value, null, `Página ${value}`))}
|
|
34
|
+
{button(page + 1, 'chevron-right', 'Próxima página', page >= pages)}
|
|
35
|
+
{!mobile ? button(pages, 'chevrons-right', 'Última página', page >= pages) : null}
|
|
36
|
+
</View>
|
|
37
|
+
</View>;
|
|
38
|
+
}
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
shouldIncludeColumn,
|
|
24
24
|
} from './DefaultTable.utils';
|
|
25
25
|
import styles from './DefaultTable.styles';
|
|
26
|
+
import {getCompactColumnLabel} from './DefaultTableCompact.helpers';
|
|
27
|
+
import useDefaultTableRowInteraction from './useDefaultTableRowInteraction';
|
|
26
28
|
import useDefaultTableTheme from './useDefaultTableTheme';
|
|
27
29
|
|
|
28
30
|
/** Row stripes: themes-map listItemEvenRow / listItemOddRow — never status.color. */
|
|
@@ -31,6 +33,7 @@ export const resolveRowBackgroundColor = ({ index, tableOddColor, tableEvenColor
|
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
const DefaultTableRows = ({ storeName }) => {
|
|
36
|
+
const {canOpenRow, setRowInteraction} = useDefaultTableRowInteraction();
|
|
34
37
|
const store = useStore(storeName);
|
|
35
38
|
const configs = store?.getters?.configs || {};
|
|
36
39
|
const { palette, resolvedAccentColor, tableBorderColors, themeTokens } = useDefaultTableTheme();
|
|
@@ -140,7 +143,7 @@ const DefaultTableRows = ({ storeName }) => {
|
|
|
140
143
|
const rowPressProps = hasRowPress
|
|
141
144
|
? {
|
|
142
145
|
activeOpacity: 0.84,
|
|
143
|
-
onPress: () => configs.onRowPress(row),
|
|
146
|
+
onPress: () => {if (canOpenRow(row)) configs.onRowPress(row);},
|
|
144
147
|
}
|
|
145
148
|
: {};
|
|
146
149
|
const rowBackgroundColor = resolveRowBackgroundColor({
|
|
@@ -163,6 +166,7 @@ const DefaultTableRows = ({ storeName }) => {
|
|
|
163
166
|
width: tableWidth,
|
|
164
167
|
},
|
|
165
168
|
rowStyleValue,
|
|
169
|
+
configs.appearance === 'compact' ? {minHeight: 58} : null,
|
|
166
170
|
]}
|
|
167
171
|
{...rowPressProps}
|
|
168
172
|
>
|
|
@@ -170,7 +174,7 @@ const DefaultTableRows = ({ storeName }) => {
|
|
|
170
174
|
<React.Fragment key={getColumnKey(column)}>
|
|
171
175
|
<DefaultTableInput
|
|
172
176
|
column={column}
|
|
173
|
-
options={column?.isIdentity ? {
|
|
177
|
+
options={{...(column?.isIdentity ? {
|
|
174
178
|
cellStyle: [
|
|
175
179
|
styles.pinnedIdentityCell,
|
|
176
180
|
styles.stickyIdentityCell,
|
|
@@ -182,7 +186,7 @@ const DefaultTableRows = ({ storeName }) => {
|
|
|
182
186
|
cellStyle: {
|
|
183
187
|
backgroundColor: rowBackgroundColor,
|
|
184
188
|
},
|
|
185
|
-
}}
|
|
189
|
+
}), onInteractionChange: blocked => setRowInteraction(row, column, blocked)}}
|
|
186
190
|
row={row}
|
|
187
191
|
storeName={storeName}
|
|
188
192
|
variant="cell"
|
|
@@ -215,10 +219,10 @@ const DefaultTableRows = ({ storeName }) => {
|
|
|
215
219
|
component={RowActionsComponent}
|
|
216
220
|
helpers={{
|
|
217
221
|
openEdit: () => configs.onEditRow?.(row),
|
|
218
|
-
openRow: hasRowPress ? () => configs.onRowPress(row) : null,
|
|
222
|
+
openRow: hasRowPress ? () => {if (canOpenRow(row)) configs.onRowPress(row);} : null,
|
|
219
223
|
}}
|
|
220
224
|
openEdit={() => configs.onEditRow?.(row)}
|
|
221
|
-
openRow={hasRowPress ? () => configs.onRowPress(row) : null}
|
|
225
|
+
openRow={hasRowPress ? () => {if (canOpenRow(row)) configs.onRowPress(row);} : null}
|
|
222
226
|
row={row}
|
|
223
227
|
storeName={storeName}
|
|
224
228
|
/>
|
|
@@ -265,17 +269,21 @@ const DefaultTableRows = ({ storeName }) => {
|
|
|
265
269
|
>
|
|
266
270
|
{tableColumns.map(column => {
|
|
267
271
|
const fieldName = getColumnKey(column);
|
|
268
|
-
const
|
|
272
|
+
const defaultLabel = formatStoreColumnLabel({
|
|
269
273
|
columns,
|
|
270
274
|
fieldName,
|
|
271
275
|
fallbackLabel: column?.label || fieldName,
|
|
272
276
|
storeName,
|
|
273
277
|
});
|
|
278
|
+
const label = configs.appearance === 'compact' ? getCompactColumnLabel(column, defaultLabel) : defaultLabel;
|
|
274
279
|
const sortFieldName = getSortField(column);
|
|
275
280
|
|
|
276
281
|
return (
|
|
277
282
|
<TouchableOpacity
|
|
278
283
|
key={fieldName}
|
|
284
|
+
accessibilityRole={isSortableColumn(column) ? 'button' : undefined}
|
|
285
|
+
accessibilityLabel={`Ordenar por ${label}`}
|
|
286
|
+
accessibilityHint={configs.resolvedSort?.field === sortFieldName ? `Ordem ${configs.resolvedSort.direction === 'asc' ? 'crescente' : 'decrescente'}` : undefined}
|
|
279
287
|
style={[
|
|
280
288
|
getColumnStyle(column),
|
|
281
289
|
column?.isIdentity
|
|
@@ -286,7 +294,7 @@ const DefaultTableRows = ({ storeName }) => {
|
|
|
286
294
|
onPress={() => configs.requestSort?.(column)}
|
|
287
295
|
>
|
|
288
296
|
<View style={styles.sortableHeader}>
|
|
289
|
-
<Text style={[styles.headerText, { color: tableTextColor }]} numberOfLines={1}>{label}</Text>
|
|
297
|
+
<Text style={[styles.headerText, { color: tableTextColor }, configs.appearance === 'compact' ? {fontSize: 12, fontWeight: '600', textTransform: 'none'} : null]} numberOfLines={1}>{label}</Text>
|
|
290
298
|
{isSortableColumn(column) && configs.resolvedSort?.field === sortFieldName ? (
|
|
291
299
|
<Icon name={configs.resolvedSort?.direction === 'desc' ? 'chevron-down' : 'chevron-up'} size={12} color={tableTextColor} />
|
|
292
300
|
) : isSortableColumn(column) ? (
|
|
@@ -314,8 +322,8 @@ const DefaultTableRows = ({ storeName }) => {
|
|
|
314
322
|
},
|
|
315
323
|
]}
|
|
316
324
|
>
|
|
317
|
-
<Text style={[styles.headerText, { color: tableTextColor }]}>
|
|
318
|
-
{global.t?.t(storeName, 'label', 'actions')}
|
|
325
|
+
<Text style={[styles.headerText, { color: tableTextColor }, configs.appearance === 'compact' ? {fontSize: 12, fontWeight: '600', textTransform: 'none'} : null]}>
|
|
326
|
+
{configs.appearance === 'compact' ? 'Ações' : global.t?.t(storeName, 'label', 'actions')}
|
|
319
327
|
</Text>
|
|
320
328
|
</View>
|
|
321
329
|
) : null}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, {useMemo, useState} from 'react';
|
|
2
2
|
import { Text, View, useWindowDimensions } from 'react-native';
|
|
3
3
|
import { useStore } from '@store';
|
|
4
|
+
import DefaultTableCompactToolbar from './DefaultTableCompactToolbar';
|
|
4
5
|
import DefaultTableControls from './DefaultTableControls';
|
|
5
6
|
import DefaultTableImportModal from './DefaultTableImportModal';
|
|
6
7
|
import {
|
|
@@ -154,6 +155,12 @@ const DefaultTableToolbar = ({
|
|
|
154
155
|
</View>
|
|
155
156
|
) : null;
|
|
156
157
|
|
|
158
|
+
if (configs.appearance === 'compact') {
|
|
159
|
+
return <>
|
|
160
|
+
<DefaultTableCompactToolbar storeName={storeName} actions={resolvedToolbarActions} showControls={showToolbarControls} />
|
|
161
|
+
<DefaultTableImportModal onClose={() => setIsImportModalVisible(false)} storeName={storeName} visible={isImportModalVisible} />
|
|
162
|
+
</>;
|
|
163
|
+
}
|
|
157
164
|
return (
|
|
158
165
|
<View
|
|
159
166
|
style={[
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Modal, Text, TouchableOpacity, View } from 'react-native';
|
|
3
3
|
import Icon from 'react-native-vector-icons/Feather';
|
|
4
|
+
import DefaultTablePaginationBar from './DefaultTablePaginationBar';
|
|
4
5
|
import DefaultTableBody from './DefaultTableBody';
|
|
5
6
|
import DefaultTableFooter from './DefaultTableFooter';
|
|
6
7
|
import DefaultTableToolbar from './DefaultTableToolbar';
|
|
7
8
|
import DefaultForm from '../form/DefaultForm';
|
|
8
9
|
import styles from './DefaultTable.styles';
|
|
10
|
+
import {DefaultTableThemeContext} from './useDefaultTableTheme';
|
|
9
11
|
|
|
10
12
|
export default function DefaultTableView({
|
|
11
13
|
tablePanelBorderColor,
|
|
@@ -29,7 +31,7 @@ export default function DefaultTableView({
|
|
|
29
31
|
requestParamsSeed
|
|
30
32
|
}) {
|
|
31
33
|
return (
|
|
32
|
-
<View
|
|
34
|
+
<DefaultTableThemeContext.Provider value={themeColors}><View
|
|
33
35
|
style={[
|
|
34
36
|
styles.wrap,
|
|
35
37
|
{
|
|
@@ -48,6 +50,7 @@ export default function DefaultTableView({
|
|
|
48
50
|
) : null}
|
|
49
51
|
<DefaultTableBody storeName={storeName} />
|
|
50
52
|
<DefaultTableFooter storeName={storeName} />
|
|
53
|
+
<DefaultTablePaginationBar storeName={storeName} />
|
|
51
54
|
{shouldRenderBottomAddButton ? (
|
|
52
55
|
<View style={styles.bottomAddBar}>
|
|
53
56
|
<TouchableOpacity
|
|
@@ -113,6 +116,6 @@ export default function DefaultTableView({
|
|
|
113
116
|
</View>
|
|
114
117
|
</View>
|
|
115
118
|
</Modal>
|
|
116
|
-
</View>
|
|
119
|
+
</View></DefaultTableThemeContext.Provider>
|
|
117
120
|
);
|
|
118
121
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {useCallback, useState} from 'react';
|
|
2
|
+
|
|
3
|
+
export default function useDefaultTableCreateForm({onAdd, onSaved, resolvedActions, handleRefresh}) {
|
|
4
|
+
const [isCreateFormOpen, setIsCreateFormOpen] = useState(false);
|
|
5
|
+
const closeCreateForm = useCallback(() => setIsCreateFormOpen(false), []);
|
|
6
|
+
const canUseDefaultCreateForm = typeof resolvedActions?.save === 'function';
|
|
7
|
+
const handleAdd = useCallback(() => {
|
|
8
|
+
if (typeof onAdd === 'function') {
|
|
9
|
+
return onAdd();
|
|
10
|
+
}
|
|
11
|
+
if (!canUseDefaultCreateForm) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
setIsCreateFormOpen(true);
|
|
15
|
+
return null;
|
|
16
|
+
}, [canUseDefaultCreateForm, onAdd]);
|
|
17
|
+
const resolvedOnAdd = typeof onAdd === 'function' || canUseDefaultCreateForm
|
|
18
|
+
? handleAdd
|
|
19
|
+
: null;
|
|
20
|
+
const handleDefaultCreateSaved = useCallback(
|
|
21
|
+
savedItem => {
|
|
22
|
+
closeCreateForm();
|
|
23
|
+
onSaved?.(savedItem, null);
|
|
24
|
+
handleRefresh?.();
|
|
25
|
+
},
|
|
26
|
+
[closeCreateForm, handleRefresh, onSaved],
|
|
27
|
+
);
|
|
28
|
+
return {isCreateFormOpen, closeCreateForm, resolvedOnAdd, handleDefaultCreateSaved};
|
|
29
|
+
}
|
|
@@ -76,6 +76,7 @@ const useAutoPageLoader = ({
|
|
|
76
76
|
showError,
|
|
77
77
|
storeName,
|
|
78
78
|
}) => {
|
|
79
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
79
80
|
const [autoHasLoaded, setAutoHasLoaded] = useState(false);
|
|
80
81
|
const [autoLoading, setAutoLoading] = useState(false);
|
|
81
82
|
const [autoLoadingMore, setAutoLoadingMore] = useState(false);
|
|
@@ -123,6 +124,7 @@ const useAutoPageLoader = ({
|
|
|
123
124
|
|
|
124
125
|
const pageItems = normalizeCollectionItems(response);
|
|
125
126
|
autoPageRef.current = Number(response?.catalogPage) || page;
|
|
127
|
+
setCurrentPage(autoPageRef.current);
|
|
126
128
|
autoLoadedQueryKeyRef.current = autoQuerySignature;
|
|
127
129
|
autoErroredQueryKeyRef.current = '';
|
|
128
130
|
setAutoHasLoaded(true);
|
|
@@ -161,6 +163,7 @@ const useAutoPageLoader = ({
|
|
|
161
163
|
);
|
|
162
164
|
|
|
163
165
|
return {
|
|
166
|
+
currentPage,
|
|
164
167
|
autoErroredQueryKeyRef,
|
|
165
168
|
autoHasLoaded,
|
|
166
169
|
autoLastPageCount,
|
|
@@ -225,6 +228,7 @@ export const useDefaultTablePagination = ({
|
|
|
225
228
|
onRefresh,
|
|
226
229
|
onEndReached,
|
|
227
230
|
pageSize,
|
|
231
|
+
paginationMode = 'infinite',
|
|
228
232
|
requestParams,
|
|
229
233
|
resolvedActions,
|
|
230
234
|
resolvedSort,
|
|
@@ -275,6 +279,7 @@ export const useDefaultTablePagination = ({
|
|
|
275
279
|
[columnsForTable, filters, pageSizeNumber, requestParams, resolvedSort, resourceEndpoint, storeName, storeReload],
|
|
276
280
|
);
|
|
277
281
|
const {
|
|
282
|
+
currentPage,
|
|
278
283
|
autoErroredQueryKeyRef,
|
|
279
284
|
autoHasLoaded,
|
|
280
285
|
autoLastPageCount,
|
|
@@ -365,6 +370,7 @@ export const useDefaultTablePagination = ({
|
|
|
365
370
|
|
|
366
371
|
const handleEndReached = useCallback(() => {
|
|
367
372
|
if (
|
|
373
|
+
paginationMode === 'pages' ||
|
|
368
374
|
!resolvedHasMore ||
|
|
369
375
|
resolvedIsLoading ||
|
|
370
376
|
endReachedLockRef.current === true
|
|
@@ -384,6 +390,7 @@ export const useDefaultTablePagination = ({
|
|
|
384
390
|
onEndReached();
|
|
385
391
|
}
|
|
386
392
|
}, [
|
|
393
|
+
paginationMode,
|
|
387
394
|
autoMode,
|
|
388
395
|
autoPageRef,
|
|
389
396
|
loadAutoPage,
|
|
@@ -408,7 +415,14 @@ export const useDefaultTablePagination = ({
|
|
|
408
415
|
|
|
409
416
|
return {
|
|
410
417
|
buildRequestQuery,
|
|
411
|
-
currentPage
|
|
418
|
+
currentPage,
|
|
419
|
+
goToPage: page => {
|
|
420
|
+
const totalPages = Math.max(1, Math.ceil(Number(resolvedTotalItems || 0) / pageSizeNumber));
|
|
421
|
+
if (autoMode && paginationMode === 'pages' && Number.isInteger(page) && page >= 1 && page <= totalPages && !resolvedIsLoading) {
|
|
422
|
+
return loadAutoPage(page, {append: false});
|
|
423
|
+
}
|
|
424
|
+
return Promise.resolve([]);
|
|
425
|
+
},
|
|
412
426
|
handleEndReached,
|
|
413
427
|
handleRefresh,
|
|
414
428
|
pageSizeNumber,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {useRef} from 'react';
|
|
2
|
+
import {getColumnKey} from '../inputs/defaultInputUtils';
|
|
3
|
+
import {getRowKey} from './DefaultTable.utils';
|
|
4
|
+
|
|
5
|
+
// Keep navigation locked while any cell in this row is editing or saving.
|
|
6
|
+
export default function useDefaultTableRowInteraction() {
|
|
7
|
+
const blockedFields = useRef(new Map());
|
|
8
|
+
const setRowInteraction = (row, column, blocked) => {
|
|
9
|
+
const key = row?.id != null || row?.['@id'] != null ? getRowKey(row) : row;
|
|
10
|
+
const fields = blockedFields.current.get(key) || new Set();
|
|
11
|
+
const field = getColumnKey(column);
|
|
12
|
+
if (blocked) fields.add(field);
|
|
13
|
+
else fields.delete(field);
|
|
14
|
+
if (fields.size) blockedFields.current.set(key, fields);
|
|
15
|
+
else blockedFields.current.delete(key);
|
|
16
|
+
};
|
|
17
|
+
const canOpenRow = row => !blockedFields.current.has(row?.id != null || row?.['@id'] != null ? getRowKey(row) : row);
|
|
18
|
+
return {canOpenRow, setRowInteraction};
|
|
19
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
persistTableFiltersPreference,
|
|
3
4
|
resolveStoredTableFiltersPreference,
|
|
4
5
|
resolveStoredVisibleColumnsPreference,
|
|
5
6
|
sanitizeTableFiltersPreference,
|
|
@@ -48,12 +49,19 @@ export function useDefaultTableStoreSync({
|
|
|
48
49
|
data,
|
|
49
50
|
defaultTableConfigs,
|
|
50
51
|
defaultTableConfigsSignature,
|
|
52
|
+
filters,
|
|
53
|
+
onFilterChange,
|
|
51
54
|
store,
|
|
52
55
|
storeColumnsLength,
|
|
53
56
|
storeFilters,
|
|
54
57
|
storeName,
|
|
55
58
|
tablePreferenceScope,
|
|
56
59
|
}) {
|
|
60
|
+
const onFilterChangeRef = useRef(onFilterChange);
|
|
61
|
+
onFilterChangeRef.current = onFilterChange;
|
|
62
|
+
const controlledFiltersRef = useRef(filters);
|
|
63
|
+
controlledFiltersRef.current = filters;
|
|
64
|
+
const lastControlledFiltersRef = useRef(filters);
|
|
57
65
|
const storeRef = useRef(store);
|
|
58
66
|
storeRef.current = store;
|
|
59
67
|
const lastPublishedItemsRef = useRef(undefined);
|
|
@@ -123,35 +131,36 @@ export function useDefaultTableStoreSync({
|
|
|
123
131
|
columnsForTable.map(c => c?.key || c?.name || '').join('|'),
|
|
124
132
|
].join('::');
|
|
125
133
|
|
|
134
|
+
if (lastHydratedFiltersKeyRef.current === scopeKey) return;
|
|
135
|
+
lastHydratedFiltersKeyRef.current = scopeKey;
|
|
126
136
|
const storedFilters = resolveStoredTableFiltersPreference(tablePreferenceScope);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
137
|
+
const controlled = typeof onFilterChangeRef.current === 'function';
|
|
138
|
+
if (!storedFilters && !controlled) return;
|
|
132
139
|
const nextFilters = sanitizeTableFiltersPreference({
|
|
133
140
|
columns: columnsForTable,
|
|
134
|
-
filters: storedFilters,
|
|
141
|
+
filters: storedFilters || controlledFiltersRef.current || {},
|
|
135
142
|
});
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
lastHydratedFiltersKeyRef.current = scopeKey;
|
|
139
|
-
return;
|
|
143
|
+
if (!areTableFiltersEqual(storeFiltersRef.current, nextFilters)) {
|
|
144
|
+
publishStoreValue(storeRef.current, 'setFilters', nextFilters, 'filters');
|
|
140
145
|
}
|
|
141
|
-
|
|
142
|
-
//
|
|
143
|
-
if (
|
|
144
|
-
|
|
146
|
+
// The screen builds requestParams from its controlled filters. Hydration must
|
|
147
|
+
// notify it too, including saved {}, or the UI and request disagree.
|
|
148
|
+
if (controlled && !areTableFiltersEqual(controlledFiltersRef.current, nextFilters)) {
|
|
149
|
+
onFilterChangeRef.current(nextFilters);
|
|
145
150
|
}
|
|
151
|
+
}, [columnsForTable, storeName, tablePreferenceScope]);
|
|
146
152
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (typeof onFilterChangeRef.current !== 'function') return;
|
|
155
|
+
if (areTableFiltersEqual(lastControlledFiltersRef.current, filters)) return;
|
|
156
|
+
lastControlledFiltersRef.current = filters;
|
|
157
|
+
persistTableFiltersPreference(tablePreferenceScope, sanitizeTableFiltersPreference({
|
|
158
|
+
columns: columnsForTable, filters: filters || {},
|
|
159
|
+
}));
|
|
160
|
+
if (!areTableFiltersEqual(storeFiltersRef.current, filters)) {
|
|
161
|
+
publishStoreValue(storeRef.current, 'setFilters', filters || {}, 'filters');
|
|
150
162
|
}
|
|
151
|
-
|
|
152
|
-
lastHydratedFiltersKeyRef.current = scopeKey;
|
|
153
|
-
publishStoreValue(storeRef.current, 'setFilters', nextFilters, 'filters');
|
|
154
|
-
}, [columnsForTable, storeName, tablePreferenceScope]);
|
|
163
|
+
}, [filters]);
|
|
155
164
|
|
|
156
165
|
useEffect(() => {
|
|
157
166
|
if (configsSignatureRef.current === defaultTableConfigsSignature) {
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
1
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
2
2
|
import { useStore } from '@store';
|
|
3
3
|
|
|
4
|
+
export const DefaultTableThemeContext = createContext(null);
|
|
5
|
+
|
|
4
6
|
const firstThemeToken = (themeColors, keys) =>
|
|
5
7
|
keys.map(key => themeColors?.[key]).find(Boolean);
|
|
6
8
|
|
|
7
|
-
const useDefaultTableTheme = (accentColor = null) => {
|
|
9
|
+
const useDefaultTableTheme = (accentColor = null, appearance = 'default') => {
|
|
10
|
+
const inheritedColors = useContext(DefaultTableThemeContext);
|
|
8
11
|
const themeStore = useStore('theme');
|
|
9
12
|
const rawThemeColors = themeStore?.getters?.colors || {};
|
|
10
13
|
const themeColors = useMemo(
|
|
11
|
-
() => ({
|
|
12
|
-
[rawThemeColors],
|
|
14
|
+
() => inheritedColors || (appearance === 'compact' ? {...rawThemeColors, compact: true} : rawThemeColors),
|
|
15
|
+
[rawThemeColors, inheritedColors, appearance],
|
|
13
16
|
);
|
|
14
17
|
const themeTokens = useMemo(
|
|
15
18
|
() => ({ ...themeColors }),
|
|
16
19
|
[themeColors],
|
|
17
|
-
);
|
|
20
|
+
);
|
|
18
21
|
const palette = themeColors;
|
|
19
|
-
const resolvedAccentColor = accentColor ?? themeColors.primary;
|
|
22
|
+
const resolvedAccentColor = appearance === 'compact' || inheritedColors ? themeColors.primary : accentColor ?? themeColors.primary;
|
|
20
23
|
const tableBorderColors = {
|
|
21
24
|
containerBorderColor: firstThemeToken(themeColors, [
|
|
22
25
|
'tableContainerBorder',
|
|
@@ -64,38 +67,38 @@ const useDefaultTableTheme = (accentColor = null) => {
|
|
|
64
67
|
resolvedAccentColor,
|
|
65
68
|
tableBorderColors,
|
|
66
69
|
themeColors,
|
|
67
|
-
themeTokens,
|
|
68
|
-
checkboxBorderColor: themeColors.checkboxBorder,
|
|
69
|
-
checkboxSelectedMarkColor: themeColors.checkboxSelectedMark,
|
|
70
|
-
modalColors: {
|
|
71
|
-
backgroundColor: themeColors.modalBackground,
|
|
72
|
-
borderColor: themeColors.modalBorder,
|
|
73
|
-
closeIconColor: themeColors.modalCloseIcon,
|
|
74
|
-
headerTextColor: themeColors.modalHeaderText,
|
|
75
|
-
overlayColor: themeColors.modalOverlay,
|
|
76
|
-
textColor: themeColors.modalText,
|
|
77
|
-
},
|
|
78
|
-
tableButtonColors: {
|
|
79
|
-
backgroundColor: themeColors.buttonBackground,
|
|
80
|
-
borderColor: themeColors.buttonBorder,
|
|
81
|
-
iconColor: themeColors.buttonIcon,
|
|
82
|
-
pressedBackgroundColor: themeColors.buttonPressedBackground,
|
|
83
|
-
pressedBorderColor: themeColors.buttonPressedBorder,
|
|
84
|
-
pressedIconColor: themeColors.buttonPressedIcon,
|
|
85
|
-
textColor: themeColors.buttonText,
|
|
86
|
-
},
|
|
87
|
-
tableActionColors: {
|
|
88
|
-
backgroundColor: themeColors.tableActionBackground,
|
|
89
|
-
borderColor: themeColors.tableActionBorder,
|
|
90
|
-
textColor: themeColors.tableActionIcon,
|
|
91
|
-
},
|
|
92
|
-
toolbarColors: {
|
|
93
|
-
backgroundColor: themeColors.toolbarBackground,
|
|
94
|
-
borderColor: themeColors.toolbarBorder,
|
|
95
|
-
countBackgroundColor: themeColors.badgeBackground,
|
|
96
|
-
countTextColor: themeColors.badgeText,
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
export default useDefaultTableTheme;
|
|
70
|
+
themeTokens,
|
|
71
|
+
checkboxBorderColor: themeColors.checkboxBorder,
|
|
72
|
+
checkboxSelectedMarkColor: themeColors.checkboxSelectedMark,
|
|
73
|
+
modalColors: {
|
|
74
|
+
backgroundColor: themeColors.modalBackground,
|
|
75
|
+
borderColor: themeColors.modalBorder,
|
|
76
|
+
closeIconColor: themeColors.modalCloseIcon,
|
|
77
|
+
headerTextColor: themeColors.modalHeaderText,
|
|
78
|
+
overlayColor: themeColors.modalOverlay,
|
|
79
|
+
textColor: themeColors.modalText,
|
|
80
|
+
},
|
|
81
|
+
tableButtonColors: {
|
|
82
|
+
backgroundColor: themeColors.buttonBackground,
|
|
83
|
+
borderColor: themeColors.buttonBorder,
|
|
84
|
+
iconColor: themeColors.buttonIcon,
|
|
85
|
+
pressedBackgroundColor: themeColors.buttonPressedBackground,
|
|
86
|
+
pressedBorderColor: themeColors.buttonPressedBorder,
|
|
87
|
+
pressedIconColor: themeColors.buttonPressedIcon,
|
|
88
|
+
textColor: themeColors.buttonText,
|
|
89
|
+
},
|
|
90
|
+
tableActionColors: {
|
|
91
|
+
backgroundColor: themeColors.tableActionBackground,
|
|
92
|
+
borderColor: themeColors.tableActionBorder,
|
|
93
|
+
textColor: themeColors.tableActionIcon,
|
|
94
|
+
},
|
|
95
|
+
toolbarColors: {
|
|
96
|
+
backgroundColor: themeColors.toolbarBackground,
|
|
97
|
+
borderColor: themeColors.toolbarBorder,
|
|
98
|
+
countBackgroundColor: themeColors.badgeBackground,
|
|
99
|
+
countTextColor: themeColors.badgeText,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export default useDefaultTableTheme;
|