@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.
Files changed (37) hide show
  1. package/docs/compact-order-history-1047.md +25 -0
  2. package/package.json +1 -1
  3. package/src/react/components/filters/CompactFilterSelector.js +194 -185
  4. package/src/react/components/filters/DefaultColumnFilter.js +8 -6
  5. package/src/react/components/filters/DefaultCompactFilterPicker.js +35 -0
  6. package/src/react/components/table/DefaultColumnMenu.js +36 -2
  7. package/src/react/components/table/DefaultCompactDialog.js +36 -0
  8. package/src/react/components/table/DefaultDebug.js +199 -197
  9. package/src/react/components/table/DefaultFiltersModal.js +44 -12
  10. package/src/react/components/table/DefaultTable.js +27 -27
  11. package/src/react/components/table/DefaultTableCards.js +44 -10
  12. package/src/react/components/table/DefaultTableCompact.helpers.js +26 -0
  13. package/src/react/components/table/DefaultTableCompact.styles.js +28 -0
  14. package/src/react/components/table/DefaultTableCompactToolbar.js +112 -0
  15. package/src/react/components/table/DefaultTableInput.js +30 -11
  16. package/src/react/components/table/DefaultTablePaginationBar.js +38 -0
  17. package/src/react/components/table/DefaultTableRows.js +17 -9
  18. package/src/react/components/table/DefaultTableToolbar.js +7 -0
  19. package/src/react/components/table/DefaultTableView.js +5 -2
  20. package/src/react/components/table/useDefaultTableCreateForm.js +29 -0
  21. package/src/react/components/table/useDefaultTablePagination.js +15 -1
  22. package/src/react/components/table/useDefaultTableRowInteraction.js +19 -0
  23. package/src/react/components/table/useDefaultTableStoreSync.js +30 -21
  24. package/src/react/components/table/useDefaultTableTheme.js +44 -41
  25. package/src/tests/react/components/table/DefaultTable.group1.test.js +441 -0
  26. package/src/tests/react/components/table/DefaultTable.group2.test.js +453 -0
  27. package/src/tests/react/components/table/DefaultTable.group3.test.js +289 -0
  28. package/src/tests/react/components/table/DefaultTable.test.js +1 -1381
  29. package/src/tests/react/components/table/DefaultTableToolbar.waiter.test.js +1 -0
  30. package/src/tests/react/components/table/compactDialogs.test.js +53 -0
  31. package/src/tests/react/components/table/compactFilters.test.js +47 -0
  32. package/src/tests/react/components/table/compactPagination.test.js +66 -0
  33. package/src/tests/react/components/table/compactTheme.test.js +34 -0
  34. package/src/tests/react/components/table/compactToolbar.test.js +41 -0
  35. package/src/tests/react/components/table/defaultTableStoreHarness.cjs +35 -0
  36. package/src/tests/react/components/table/defaultTableTestSetup.cjs +207 -0
  37. 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
- setIsSaving(false);
113
- setIsEditing(false);
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 label = formatStoreColumnLabel({
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: autoPageRef.current,
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
- if (!storedFilters) {
128
- lastHydratedFiltersKeyRef.current = scopeKey;
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
- if (Object.keys(nextFilters).length === 0) {
138
- lastHydratedFiltersKeyRef.current = scopeKey;
139
- return;
143
+ if (!areTableFiltersEqual(storeFiltersRef.current, nextFilters)) {
144
+ publishStoreValue(storeRef.current, 'setFilters', nextFilters, 'filters');
140
145
  }
141
-
142
- // Hydrate once per scope/columns; never chase storeFilters (loop on period toggle).
143
- if (lastHydratedFiltersKeyRef.current === scopeKey) {
144
- return;
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
- if (areTableFiltersEqual(storeFiltersRef.current, nextFilters)) {
148
- lastHydratedFiltersKeyRef.current = scopeKey;
149
- return;
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
- () => ({ ...rawThemeColors }),
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;