@controleonline/ui-default 1.0.277 → 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 +27 -27
- 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 +38 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# DefaultTable compact pilot — app-community #1047
|
|
2
|
+
|
|
3
|
+
The compact appearance is opt-in. Order History is the pilot; default consumers retain their existing appearance and actions. Search, controlled filters, accessible card sorting, column visibility, table/cards, numbered pagination and report totals reuse the shared table and order services.
|
|
4
|
+
|
|
5
|
+
Theme authority remains the main company/domain. Theme tokens override status colors; registered status.color follows; neutral theme text is last. Franchise switching does not override domain authority. No database writes or staging Theme bindings are shipped. Business permissions, payment/cancellation eligibility and request/security filters are preserved.
|
|
6
|
+
|
|
7
|
+
Header logos send authentication only to the exact API origin; external images use Image without headers. Inline row/card navigation stays blocked during editing/saving, preserving existing store save callbacks. Provider extraction preserves hook order and unrelated bootstrap dependencies, checked by ordered AST fingerprints.
|
|
8
|
+
|
|
9
|
+
Pre-existing published dependencies are merged by their exact gitHeads rather than importing latest dev:
|
|
10
|
+
- ui-default 1.0.276 f0083eb73074f402e81f4e0c832af0bb829c2047; published patch277 1dff39dc32be3320cd4a786baf5621e8a2d3ac00.
|
|
11
|
+
- ui-orders 1.3.42 bfe2f013dc9bcaf033ba0db80894f021bfb2fbfb.
|
|
12
|
+
- ui-layout 1.0.15 37f1dff3fecf1f08bc8846a319af83c9fcbff502.
|
|
13
|
+
- ui-common 1.2.89 4919724c04f3d6e66280db4c9e2a66db65c47cc0; published patch90 e19f14d4afefb89a0700d63753785815364af835.
|
|
14
|
+
|
|
15
|
+
Patch90 Connections.js had an unterminated searchKey quote. Only that quote is repaired to make the retained published fixes compilable. Existing DefaultTable tests are split without discarding cases; fixtures now model reactive stores, company-scoped preferences, route context and CSS sticky positioning. Legacy toolbar tests isolate the inactive compact branch; dedicated compact suites cover its controls.
|
|
16
|
+
|
|
17
|
+
Validation: 135 assertions in 22 suites, 591 React files parsed, and isolated Expo web export (5492 modules). These are source/composition checks; authenticated staging validation remains a separate promotion gate. Isolation used source-module copies; final released-package pins must be verified again by release validation.
|
|
18
|
+
|
|
19
|
+
From app-community with installed release packages:
|
|
20
|
+
```sh
|
|
21
|
+
node scripts/validate-compact-sources.cjs
|
|
22
|
+
node node_modules/jest/bin/jest.js --config jest.compact.config.cjs --maxWorkers=2 --testPathPattern='compact|Compact|headerLogoProtocol|DefaultCompanyHeaderLogo|useDefaultTableStoreSync|providerEffects|DefaultProviderAuthority|deviceConfigBootstrap|mercadoLivreIntegrationHelpers|DefaultTable\.(group[123]\.)?test.js|OrderHistoryPage.test.js|DefaultTableCards.layout|DefaultTableToolbar.waiter'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For isolated sibling source clones, set CONTROLEONLINE_MODULES_ROOT to their common directory. The committed configuration has no machine-specific absolute path. Published versions planned: ui-default1.0.278, ui-orders1.3.43, ui-layout1.0.16, ui-common1.2.91.
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useMemo, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useContext, useMemo, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Image,
|
|
4
4
|
Modal,
|
|
@@ -19,7 +19,9 @@ import {
|
|
|
19
19
|
} from '@controleonline/ui-common/src/react/utils/storeColumns';
|
|
20
20
|
import { isValidFeatherIcon } from '../inputs/defaultInputUtils';
|
|
21
21
|
import createStyles from './CompactFilterSelector.styles';
|
|
22
|
-
|
|
22
|
+
import {DefaultTableThemeContext} from '../table/useDefaultTableTheme';
|
|
23
|
+
import DefaultCompactFilterPicker from './DefaultCompactFilterPicker';
|
|
24
|
+
|
|
23
25
|
const buildTheme = ({ accentColor, themeColors = {} }) => ({
|
|
24
26
|
accentColor: accentColor,
|
|
25
27
|
activeBackgroundColor:
|
|
@@ -30,68 +32,68 @@ const buildTheme = ({ accentColor, themeColors = {} }) => ({
|
|
|
30
32
|
themeColors.activeChevronColor ||
|
|
31
33
|
themeColors.activeIconColor ||
|
|
32
34
|
accentColor,
|
|
33
|
-
activeIconColor: themeColors.activeIconColor || accentColor,
|
|
34
|
-
activeTextColor: themeColors.activeTextColor || accentColor,
|
|
35
|
-
backgroundColor: themeColors.backgroundColor || '#F8FAFC',
|
|
36
|
-
borderColor: themeColors.borderColor || '#E2E8F0',
|
|
37
|
-
captionColor: themeColors.captionColor || '#94A3B8',
|
|
38
|
-
chevronColor: themeColors.chevronColor || '#94A3B8',
|
|
39
|
-
closeIconColor: themeColors.closeIconColor || '#64748B',
|
|
40
|
-
iconBackgroundColor: themeColors.iconBackgroundColor || '#E2E8F0',
|
|
41
|
-
iconColor: themeColors.iconColor || '#64748B',
|
|
42
|
-
modalBackgroundColor: themeColors.modalBackgroundColor || '#FFFFFF',
|
|
43
|
-
modalOverlayColor:
|
|
44
|
-
themeColors.modalOverlayColor || 'rgba(15, 23, 42, 0.35)',
|
|
45
|
-
modalTitleColor: themeColors.modalTitleColor || '#0F172A',
|
|
46
|
-
optionBackgroundColor: themeColors.optionBackgroundColor || '#F8FAFC',
|
|
47
|
-
optionBorderColor: themeColors.optionBorderColor || '#E2E8F0',
|
|
35
|
+
activeIconColor: themeColors.activeIconColor || accentColor,
|
|
36
|
+
activeTextColor: themeColors.activeTextColor || accentColor,
|
|
37
|
+
backgroundColor: themeColors.backgroundColor || '#F8FAFC',
|
|
38
|
+
borderColor: themeColors.borderColor || '#E2E8F0',
|
|
39
|
+
captionColor: themeColors.captionColor || '#94A3B8',
|
|
40
|
+
chevronColor: themeColors.chevronColor || '#94A3B8',
|
|
41
|
+
closeIconColor: themeColors.closeIconColor || '#64748B',
|
|
42
|
+
iconBackgroundColor: themeColors.iconBackgroundColor || '#E2E8F0',
|
|
43
|
+
iconColor: themeColors.iconColor || '#64748B',
|
|
44
|
+
modalBackgroundColor: themeColors.modalBackgroundColor || '#FFFFFF',
|
|
45
|
+
modalOverlayColor:
|
|
46
|
+
themeColors.modalOverlayColor || 'rgba(15, 23, 42, 0.35)',
|
|
47
|
+
modalTitleColor: themeColors.modalTitleColor || '#0F172A',
|
|
48
|
+
optionBackgroundColor: themeColors.optionBackgroundColor || '#F8FAFC',
|
|
49
|
+
optionBorderColor: themeColors.optionBorderColor || '#E2E8F0',
|
|
48
50
|
optionSelectedTextColor:
|
|
49
51
|
themeColors.optionSelectedTextColor || accentColor,
|
|
50
52
|
textColor: themeColors.textColor || '#0F172A',
|
|
51
53
|
});
|
|
52
|
-
|
|
53
|
-
const noop = () => {};
|
|
54
|
-
|
|
55
|
-
const normalizeText = value => String(value || '').trim();
|
|
56
|
-
|
|
57
|
-
const resolveStoreName = store => {
|
|
58
|
-
if (typeof store === 'string') return normalizeText(store);
|
|
59
|
-
return normalizeText(store?.storeName || store?.name || '');
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const resolveStoreFieldLabel = ({ fallbackLabel = '', field = '', store = '' }) => {
|
|
63
|
-
const storeName = resolveStoreName(store);
|
|
64
|
-
const fieldName = normalizeText(field);
|
|
65
|
-
if (!storeName || !fieldName) return fallbackLabel;
|
|
66
|
-
|
|
67
|
-
const { columns } = resolveStoreConfigByName(storeName);
|
|
68
|
-
const column = resolveStoreColumn(columns, fieldName);
|
|
69
|
-
const labelKey = normalizeText(column?.label || fieldName);
|
|
70
|
-
const translatedLabel =
|
|
71
|
-
global.t?.t(storeName, 'label', labelKey) ||
|
|
72
|
-
global.t?.t(storeName, 'input', labelKey);
|
|
73
|
-
|
|
74
|
-
return (
|
|
75
|
-
translatedLabel ||
|
|
76
|
-
formatStoreColumnLabel({
|
|
77
|
-
columns,
|
|
78
|
-
fallbackLabel: fallbackLabel || labelKey,
|
|
79
|
-
fieldName,
|
|
80
|
-
storeName,
|
|
81
|
-
}) ||
|
|
82
|
-
fallbackLabel
|
|
83
|
-
);
|
|
84
|
-
};
|
|
85
|
-
|
|
54
|
+
|
|
55
|
+
const noop = () => {};
|
|
56
|
+
|
|
57
|
+
const normalizeText = value => String(value || '').trim();
|
|
58
|
+
|
|
59
|
+
const resolveStoreName = store => {
|
|
60
|
+
if (typeof store === 'string') return normalizeText(store);
|
|
61
|
+
return normalizeText(store?.storeName || store?.name || '');
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const resolveStoreFieldLabel = ({ fallbackLabel = '', field = '', store = '' }) => {
|
|
65
|
+
const storeName = resolveStoreName(store);
|
|
66
|
+
const fieldName = normalizeText(field);
|
|
67
|
+
if (!storeName || !fieldName) return fallbackLabel;
|
|
68
|
+
|
|
69
|
+
const { columns } = resolveStoreConfigByName(storeName);
|
|
70
|
+
const column = resolveStoreColumn(columns, fieldName);
|
|
71
|
+
const labelKey = normalizeText(column?.label || fieldName);
|
|
72
|
+
const translatedLabel =
|
|
73
|
+
global.t?.t(storeName, 'label', labelKey) ||
|
|
74
|
+
global.t?.t(storeName, 'input', labelKey);
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
translatedLabel ||
|
|
78
|
+
formatStoreColumnLabel({
|
|
79
|
+
columns,
|
|
80
|
+
fallbackLabel: fallbackLabel || labelKey,
|
|
81
|
+
fieldName,
|
|
82
|
+
storeName,
|
|
83
|
+
}) ||
|
|
84
|
+
fallbackLabel
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
86
88
|
const CompactFilterSelector = ({
|
|
87
89
|
accentColor = null,
|
|
88
90
|
active = false,
|
|
89
91
|
children = null,
|
|
90
92
|
dense = false,
|
|
91
93
|
disabled = false,
|
|
92
|
-
icon = 'sliders',
|
|
93
|
-
label = '',
|
|
94
|
-
labelCaption = '',
|
|
94
|
+
icon = 'sliders',
|
|
95
|
+
label = '',
|
|
96
|
+
labelCaption = '',
|
|
95
97
|
onClose = null,
|
|
96
98
|
onBeforeOpen = null,
|
|
97
99
|
onSearchChange = null,
|
|
@@ -104,6 +106,7 @@ const CompactFilterSelector = ({
|
|
|
104
106
|
themeColors = {},
|
|
105
107
|
title = '',
|
|
106
108
|
}) => {
|
|
109
|
+
const compactTheme = useContext(DefaultTableThemeContext);
|
|
107
110
|
const themeStore = useStore('theme');
|
|
108
111
|
const [visible, setVisible] = useState(false);
|
|
109
112
|
const [searchText, setSearchText] = useState('');
|
|
@@ -126,15 +129,15 @@ const CompactFilterSelector = ({
|
|
|
126
129
|
}),
|
|
127
130
|
[resolvedAccentColor, themeColors, themeStoreColors],
|
|
128
131
|
);
|
|
129
|
-
const styles = useMemo(
|
|
130
|
-
() => createStyles(resolvedTheme, dense, windowWidth),
|
|
131
|
-
[dense, resolvedTheme, windowWidth],
|
|
132
|
-
);
|
|
133
|
-
const storeFieldLabel = resolveStoreFieldLabel({
|
|
134
|
-
fallbackLabel: labelCaption || title,
|
|
135
|
-
field,
|
|
136
|
-
store,
|
|
137
|
-
});
|
|
132
|
+
const styles = useMemo(
|
|
133
|
+
() => createStyles(resolvedTheme, dense, windowWidth),
|
|
134
|
+
[dense, resolvedTheme, windowWidth],
|
|
135
|
+
);
|
|
136
|
+
const storeFieldLabel = resolveStoreFieldLabel({
|
|
137
|
+
fallbackLabel: labelCaption || title,
|
|
138
|
+
field,
|
|
139
|
+
store,
|
|
140
|
+
});
|
|
138
141
|
const resolvedLabelCaption = labelCaption || storeFieldLabel;
|
|
139
142
|
const resolvedTitle = title || storeFieldLabel;
|
|
140
143
|
const selectedOption = useMemo(
|
|
@@ -152,35 +155,35 @@ const CompactFilterSelector = ({
|
|
|
152
155
|
const triggerIconColor = active
|
|
153
156
|
? selectedOptionColor || resolvedTheme.activeIconColor
|
|
154
157
|
: resolvedTheme.iconColor;
|
|
155
|
-
|
|
158
|
+
|
|
156
159
|
const closeModal = useCallback(() => {
|
|
157
160
|
setVisible(false);
|
|
158
161
|
setSearchText('');
|
|
159
162
|
onClose?.();
|
|
160
163
|
}, [onClose]);
|
|
161
|
-
|
|
164
|
+
|
|
162
165
|
const openModal = useCallback(() => {
|
|
163
|
-
if (disabled) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
try {
|
|
168
|
-
const maybePromise = onBeforeOpen?.();
|
|
169
|
-
if (maybePromise && typeof maybePromise.then === 'function') {
|
|
170
|
-
maybePromise
|
|
171
|
-
.then(() => {
|
|
172
|
-
setVisible(true);
|
|
173
|
-
})
|
|
174
|
-
.catch(() => {
|
|
175
|
-
// Keep the modal closed if the lazy load fails.
|
|
176
|
-
});
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
} catch (error) {
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
setVisible(true);
|
|
166
|
+
if (disabled) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const maybePromise = onBeforeOpen?.();
|
|
172
|
+
if (maybePromise && typeof maybePromise.then === 'function') {
|
|
173
|
+
maybePromise
|
|
174
|
+
.then(() => {
|
|
175
|
+
setVisible(true);
|
|
176
|
+
})
|
|
177
|
+
.catch(() => {
|
|
178
|
+
// Keep the modal closed if the lazy load fails.
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
setVisible(true);
|
|
184
187
|
}, [disabled, onBeforeOpen]);
|
|
185
188
|
|
|
186
189
|
/*
|
|
@@ -203,37 +206,43 @@ const CompactFilterSelector = ({
|
|
|
203
206
|
normalizeText(option?.key).toLowerCase().includes(normalizedSearch),
|
|
204
207
|
);
|
|
205
208
|
}, [onSearchChange, options, searchable, searchText]);
|
|
206
|
-
|
|
207
|
-
const handleSelect = useCallback(optionKey => {
|
|
208
|
-
const shouldClose = onSelect?.(optionKey, {
|
|
209
|
-
close: closeModal,
|
|
210
|
-
open: openModal,
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
if (shouldClose !== false) {
|
|
214
|
-
closeModal();
|
|
215
|
-
}
|
|
216
|
-
}, [closeModal, onSelect, openModal]);
|
|
217
|
-
|
|
218
|
-
const resolvedChildren = useMemo(() => {
|
|
219
|
-
if (typeof children === 'function') {
|
|
220
|
-
return children({ close: closeModal, open: openModal });
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return children;
|
|
224
|
-
}, [children, closeModal, openModal]);
|
|
225
|
-
|
|
226
|
-
return
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
209
|
+
|
|
210
|
+
const handleSelect = useCallback(optionKey => {
|
|
211
|
+
const shouldClose = onSelect?.(optionKey, {
|
|
212
|
+
close: closeModal,
|
|
213
|
+
open: openModal,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (shouldClose !== false) {
|
|
217
|
+
closeModal();
|
|
218
|
+
}
|
|
219
|
+
}, [closeModal, onSelect, openModal]);
|
|
220
|
+
|
|
221
|
+
const resolvedChildren = useMemo(() => {
|
|
222
|
+
if (typeof children === 'function') {
|
|
223
|
+
return children({ close: closeModal, open: openModal });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return children;
|
|
227
|
+
}, [children, closeModal, openModal]);
|
|
228
|
+
|
|
229
|
+
if (compactTheme?.compact) return <DefaultCompactFilterPicker visible={visible} disabled={disabled}
|
|
230
|
+
label={label} title={resolvedTitle} selectedKey={selectedKey} options={filteredOptions} searchText={searchText}
|
|
231
|
+
searchable={searchable} onSearch={handleSearchChange} onOpen={openModal} onClose={closeModal} onSelect={handleSelect}>
|
|
232
|
+
{resolvedChildren}
|
|
233
|
+
</DefaultCompactFilterPicker>;
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<>
|
|
237
|
+
<TouchableOpacity
|
|
238
|
+
style={[
|
|
239
|
+
styles.trigger,
|
|
240
|
+
active ? styles.triggerActive : null,
|
|
241
|
+
]}
|
|
242
|
+
activeOpacity={0.9}
|
|
243
|
+
disabled={disabled}
|
|
244
|
+
onPress={openModal}
|
|
245
|
+
>
|
|
237
246
|
<View style={[styles.iconWrap, active ? styles.iconWrapActive : null]}>
|
|
238
247
|
{active && selectedOption?.image ? (
|
|
239
248
|
<Image
|
|
@@ -249,58 +258,58 @@ const CompactFilterSelector = ({
|
|
|
249
258
|
/>
|
|
250
259
|
)}
|
|
251
260
|
</View>
|
|
252
|
-
|
|
253
|
-
<View style={styles.textWrap}>
|
|
254
|
-
{!!resolvedLabelCaption && (
|
|
255
|
-
<Text
|
|
256
|
-
numberOfLines={1}
|
|
257
|
-
style={[
|
|
261
|
+
|
|
262
|
+
<View style={styles.textWrap}>
|
|
263
|
+
{!!resolvedLabelCaption && (
|
|
264
|
+
<Text
|
|
265
|
+
numberOfLines={1}
|
|
266
|
+
style={[
|
|
258
267
|
styles.triggerCaption,
|
|
259
268
|
active ? styles.triggerCaptionActive : null,
|
|
260
269
|
active && selectedOptionColor ? { color: selectedOptionColor } : null,
|
|
261
270
|
]}
|
|
262
|
-
>
|
|
263
|
-
{resolvedLabelCaption}
|
|
264
|
-
</Text>
|
|
265
|
-
)}
|
|
266
|
-
|
|
267
|
-
<Text
|
|
268
|
-
numberOfLines={1}
|
|
271
|
+
>
|
|
272
|
+
{resolvedLabelCaption}
|
|
273
|
+
</Text>
|
|
274
|
+
)}
|
|
275
|
+
|
|
276
|
+
<Text
|
|
277
|
+
numberOfLines={1}
|
|
269
278
|
style={[
|
|
270
279
|
styles.triggerText,
|
|
271
280
|
active ? styles.triggerTextActive : null,
|
|
272
281
|
active && selectedOptionColor ? { color: selectedOptionColor } : null,
|
|
273
282
|
]}
|
|
274
|
-
>
|
|
275
|
-
{label}
|
|
276
|
-
</Text>
|
|
277
|
-
</View>
|
|
278
|
-
|
|
279
|
-
<Icon
|
|
280
|
-
name="chevron-down"
|
|
281
|
-
size={dense ? 14 : 16}
|
|
282
|
-
color={active ? resolvedTheme.activeChevronColor : resolvedTheme.chevronColor}
|
|
283
|
-
/>
|
|
284
|
-
</TouchableOpacity>
|
|
285
|
-
|
|
286
|
-
<Modal
|
|
287
|
-
transparent
|
|
288
|
-
visible={visible}
|
|
289
|
-
animationType="fade"
|
|
290
|
-
onRequestClose={closeModal}
|
|
291
|
-
>
|
|
292
|
-
<TouchableWithoutFeedback onPress={closeModal}>
|
|
293
|
-
<View style={styles.modalOverlay}>
|
|
294
|
-
<TouchableWithoutFeedback onPress={noop}>
|
|
295
|
-
<View style={styles.modalCard}>
|
|
296
|
-
<View style={styles.modalHeader}>
|
|
297
|
-
<Text style={styles.modalTitle}>{resolvedTitle}</Text>
|
|
298
|
-
|
|
299
|
-
<TouchableOpacity onPress={closeModal} activeOpacity={0.8}>
|
|
300
|
-
<Icon name="x" size={20} color={resolvedTheme.closeIconColor} />
|
|
301
|
-
</TouchableOpacity>
|
|
302
|
-
</View>
|
|
303
|
-
|
|
283
|
+
>
|
|
284
|
+
{label}
|
|
285
|
+
</Text>
|
|
286
|
+
</View>
|
|
287
|
+
|
|
288
|
+
<Icon
|
|
289
|
+
name="chevron-down"
|
|
290
|
+
size={dense ? 14 : 16}
|
|
291
|
+
color={active ? resolvedTheme.activeChevronColor : resolvedTheme.chevronColor}
|
|
292
|
+
/>
|
|
293
|
+
</TouchableOpacity>
|
|
294
|
+
|
|
295
|
+
<Modal
|
|
296
|
+
transparent
|
|
297
|
+
visible={visible}
|
|
298
|
+
animationType="fade"
|
|
299
|
+
onRequestClose={closeModal}
|
|
300
|
+
>
|
|
301
|
+
<TouchableWithoutFeedback onPress={closeModal}>
|
|
302
|
+
<View style={styles.modalOverlay}>
|
|
303
|
+
<TouchableWithoutFeedback onPress={noop}>
|
|
304
|
+
<View style={styles.modalCard}>
|
|
305
|
+
<View style={styles.modalHeader}>
|
|
306
|
+
<Text style={styles.modalTitle}>{resolvedTitle}</Text>
|
|
307
|
+
|
|
308
|
+
<TouchableOpacity onPress={closeModal} activeOpacity={0.8}>
|
|
309
|
+
<Icon name="x" size={20} color={resolvedTheme.closeIconColor} />
|
|
310
|
+
</TouchableOpacity>
|
|
311
|
+
</View>
|
|
312
|
+
|
|
304
313
|
<ScrollView
|
|
305
314
|
style={styles.modalScroll}
|
|
306
315
|
contentContainerStyle={styles.modalContent}
|
|
@@ -331,12 +340,12 @@ const CompactFilterSelector = ({
|
|
|
331
340
|
const hasOptionVisual = Boolean(option?.image || optionIconName || shouldRenderOptionFallbackText);
|
|
332
341
|
|
|
333
342
|
return (
|
|
334
|
-
<TouchableOpacity
|
|
335
|
-
key={option.key}
|
|
336
|
-
style={[
|
|
337
|
-
styles.modalOption,
|
|
338
|
-
isSelected ? styles.modalOptionActive : null,
|
|
339
|
-
]}
|
|
343
|
+
<TouchableOpacity
|
|
344
|
+
key={option.key}
|
|
345
|
+
style={[
|
|
346
|
+
styles.modalOption,
|
|
347
|
+
isSelected ? styles.modalOptionActive : null,
|
|
348
|
+
]}
|
|
340
349
|
activeOpacity={0.9}
|
|
341
350
|
onPress={() => handleSelect(option.key)}
|
|
342
351
|
>
|
|
@@ -377,27 +386,27 @@ const CompactFilterSelector = ({
|
|
|
377
386
|
{option.label}
|
|
378
387
|
</Text>
|
|
379
388
|
)}
|
|
380
|
-
|
|
381
|
-
{isSelected ? (
|
|
389
|
+
|
|
390
|
+
{isSelected ? (
|
|
382
391
|
<Icon
|
|
383
392
|
name="check"
|
|
384
393
|
size={16}
|
|
385
394
|
color={optionColor || resolvedTheme.activeIconColor}
|
|
386
395
|
/>
|
|
387
|
-
) : null}
|
|
388
|
-
</TouchableOpacity>
|
|
389
|
-
);
|
|
390
|
-
})}
|
|
391
|
-
|
|
392
|
-
{resolvedChildren}
|
|
393
|
-
</ScrollView>
|
|
394
|
-
</View>
|
|
395
|
-
</TouchableWithoutFeedback>
|
|
396
|
-
</View>
|
|
397
|
-
</TouchableWithoutFeedback>
|
|
398
|
-
</Modal>
|
|
399
|
-
</>
|
|
400
|
-
);
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
export default CompactFilterSelector;
|
|
396
|
+
) : null}
|
|
397
|
+
</TouchableOpacity>
|
|
398
|
+
);
|
|
399
|
+
})}
|
|
400
|
+
|
|
401
|
+
{resolvedChildren}
|
|
402
|
+
</ScrollView>
|
|
403
|
+
</View>
|
|
404
|
+
</TouchableWithoutFeedback>
|
|
405
|
+
</View>
|
|
406
|
+
</TouchableWithoutFeedback>
|
|
407
|
+
</Modal>
|
|
408
|
+
</>
|
|
409
|
+
);
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
export default CompactFilterSelector;
|
|
@@ -42,7 +42,7 @@ const DefaultColumnFilter = ({
|
|
|
42
42
|
const store = useStore(storeName);
|
|
43
43
|
const peopleStore = useStore('people');
|
|
44
44
|
const configs = store?.getters?.configs || {};
|
|
45
|
-
const { resolvedAccentColor } = useDefaultTableTheme(accentColor);
|
|
45
|
+
const { resolvedAccentColor, themeColors } = useDefaultTableTheme(accentColor);
|
|
46
46
|
const [loadedItems, setLoadedItems] = useState([]);
|
|
47
47
|
const [loadedKey, setLoadedKey] = useState('');
|
|
48
48
|
const fieldName = getColumnKey(column);
|
|
@@ -168,7 +168,7 @@ const DefaultColumnFilter = ({
|
|
|
168
168
|
dense
|
|
169
169
|
store={storeName}
|
|
170
170
|
field={fieldName}
|
|
171
|
-
labelCaption={global.t?.t(storeName, 'label', column?.label || fieldName)}
|
|
171
|
+
labelCaption={themeColors.compact ? column?.compactLabel || fieldName : global.t?.t(storeName, 'label', column?.label || fieldName)}
|
|
172
172
|
value={filterValue.shortcut || 'all'}
|
|
173
173
|
customRange={filterValue.customRange || { from: '', to: '' }}
|
|
174
174
|
onChange={optionKey =>
|
|
@@ -196,9 +196,9 @@ const DefaultColumnFilter = ({
|
|
|
196
196
|
const explicitOptions = resolveOptions(column);
|
|
197
197
|
const rawOptions = Array.isArray(explicitOptions)
|
|
198
198
|
? mapOptions(column, explicitOptions, storeName)
|
|
199
|
-
: mapOptions(column, loadedItems, storeName);
|
|
199
|
+
: Array.isArray(column.list) ? buildOptionsFromColumn(column, null, storeName) : mapOptions(column, loadedItems, storeName);
|
|
200
200
|
const options = [
|
|
201
|
-
{ key: '', label: global.t?.t(storeName, 'label', 'select') },
|
|
201
|
+
{ key: '', label: themeColors.compact ? 'Todos' : global.t?.t(storeName, 'label', 'select') },
|
|
202
202
|
...rawOptions,
|
|
203
203
|
];
|
|
204
204
|
const selectedKey = normalizeOptionKey(filters?.[fieldName]);
|
|
@@ -208,11 +208,12 @@ const DefaultColumnFilter = ({
|
|
|
208
208
|
<View style={[style, styles.filterCell]}>
|
|
209
209
|
<CompactFilterSelector
|
|
210
210
|
dense
|
|
211
|
+
title={themeColors.compact ? column?.compactLabel || fieldName : undefined}
|
|
211
212
|
store={storeName}
|
|
212
213
|
field={fieldName}
|
|
213
214
|
icon="filter"
|
|
214
215
|
onBeforeOpen={onBeforeOpen || (() => loadListOptions())}
|
|
215
|
-
onSearchChange={onSearchChange || (value => loadListOptions(value))}
|
|
216
|
+
onSearchChange={onSearchChange || (Array.isArray(column.list) || Array.isArray(explicitOptions) ? undefined : value => loadListOptions(value))}
|
|
216
217
|
accentColor={resolvedAccentColor}
|
|
217
218
|
active={Boolean(selectedKey)}
|
|
218
219
|
label={selected?.label || options[0]?.label || ''}
|
|
@@ -231,7 +232,8 @@ const DefaultColumnFilter = ({
|
|
|
231
232
|
return (
|
|
232
233
|
<View style={[style, styles.filterCell]}>
|
|
233
234
|
<TextInput
|
|
234
|
-
|
|
235
|
+
accessibilityLabel={column?.compactLabel || fieldName}
|
|
236
|
+
style={[styles.filterInput, themeColors.compact ? {minHeight: 46, borderRadius: 9, fontSize: 14, borderColor: themeColors.inputBorder, color: themeColors.inputText, backgroundColor: themeColors.inputBackground} : null]}
|
|
235
237
|
value={normalizeText(filters?.[fieldName])}
|
|
236
238
|
placeholder={global.t?.t(storeName, 'input', column?.label || fieldName)}
|
|
237
239
|
onChangeText={value => requestChange(fieldName, value)}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {Image, StyleSheet, Text, TextInput, TouchableOpacity, View} from 'react-native';
|
|
3
|
+
import Icon from 'react-native-vector-icons/Feather';
|
|
4
|
+
import DefaultCompactDialog from '../table/DefaultCompactDialog';
|
|
5
|
+
import useDefaultTableTheme from '../table/useDefaultTableTheme';
|
|
6
|
+
|
|
7
|
+
// Presentation only: loading, scoped options and date validation stay in the existing selector.
|
|
8
|
+
export default function DefaultCompactFilterPicker({visible, disabled, label, title, selectedKey, options, searchText, searchable, onSearch, onOpen, onClose, onSelect, children}) {
|
|
9
|
+
const {themeColors: c} = useDefaultTableTheme();
|
|
10
|
+
return <>
|
|
11
|
+
<TouchableOpacity accessibilityRole="button" accessibilityLabel={`Selecionar ${title}`} accessibilityState={{expanded: visible, disabled}}
|
|
12
|
+
disabled={disabled} onPress={onOpen} style={[s.trigger, {borderColor: c.inputBorder, backgroundColor: c.inputBackground}]}>
|
|
13
|
+
<Text numberOfLines={1} style={{flex: 1, color: c.inputText, fontSize: 14}}>{label || 'Todos'}</Text><Icon name="chevron-down" size={17} color={c.textSecondary} />
|
|
14
|
+
</TouchableOpacity>
|
|
15
|
+
<DefaultCompactDialog visible={visible} title={title} onClose={onClose} bodyHeight={Math.min(460, 40 + options.length * 48 + (searchable ? 60 : 0) + (children ? 220 : 0))}>
|
|
16
|
+
{searchable ? <TextInput accessibilityLabel={`Buscar em ${title}`} placeholder="Buscar opções" placeholderTextColor={c.inputPlaceholderText}
|
|
17
|
+
value={searchText} onChangeText={onSearch} style={[s.search, {borderColor: c.inputBorder, color: c.inputText, backgroundColor: c.inputBackground}]} /> : null}
|
|
18
|
+
{options.length === 0 ? <Text style={{color: c.textSecondary}}>Nenhuma opção encontrada.</Text> : null}
|
|
19
|
+
{options.map(option => <TouchableOpacity key={option.key} accessibilityRole="radio" accessibilityLabel={option.label || 'Todos'} accessibilityState={{selected: option.key === selectedKey}}
|
|
20
|
+
onPress={() => onSelect(option.key)} style={[s.option, {backgroundColor: option.key === selectedKey ? c.chipSelectedBackground : 'transparent', borderColor: c.tableRowBorder}]}>
|
|
21
|
+
{option.image ? <Image source={option.image} style={{width: 24, height: 24}} resizeMode="contain" /> : null}
|
|
22
|
+
{option.color ? <View style={[s.dot, {backgroundColor: option.color}]} /> : null}
|
|
23
|
+
<Text style={{flex: 1, color: c.textPrimary, fontSize: 14}}>{option.label || 'Todos'}</Text>
|
|
24
|
+
{option.key === selectedKey ? <Icon name="check" size={17} color={c.primary} /> : null}
|
|
25
|
+
</TouchableOpacity>)}
|
|
26
|
+
{children}
|
|
27
|
+
</DefaultCompactDialog>
|
|
28
|
+
</>;
|
|
29
|
+
}
|
|
30
|
+
const s = StyleSheet.create({
|
|
31
|
+
trigger: {minHeight: 46, width: '100%', borderWidth: 1, borderRadius: 9, paddingHorizontal: 12, flexDirection: 'row', alignItems: 'center', gap: 8},
|
|
32
|
+
search: {minHeight: 44, borderWidth: 1, borderRadius: 9, paddingHorizontal: 12, fontSize: 16},
|
|
33
|
+
option: {minHeight: 46, borderRadius: 8, paddingHorizontal: 12, flexDirection: 'row', alignItems: 'center', gap: 10, borderBottomWidth: 1},
|
|
34
|
+
dot: {width: 9, height: 9, borderRadius: 5},
|
|
35
|
+
});
|