@controleonline/ui-default 1.0.270 → 1.0.273
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/.github/workflows/integration-source-gate.yml +68 -0
- package/.github/workflows/manager-worker.yml +19 -0
- package/.github/workflows/publish-npm.yml +91 -0
- package/AGENTS.md +46 -50
- package/docs/technical/Contrato-de-colunas.md +67 -0
- package/docs/technical/Default-Components.md +76 -0
- package/docs/technical/DefaultForm.md +80 -0
- package/docs/technical/DefaultInput-e-DefaultSelect.md +73 -0
- package/docs/technical/DefaultTable.md +272 -0
- package/docs/wiki.md +1 -0
- package/package.json +4 -3
- package/scripts/validate-wiki-pointer.cjs +13 -0
- package/src/react/components/address/DefaultAddress.js +446 -0
- package/src/react/components/address/DefaultAddress.styles.js +133 -0
- package/src/react/components/address/DefaultAddressCoordFields.js +59 -0
- package/src/react/components/address/DefaultAddressMapPane.js +55 -0
- package/src/react/components/address/LatLonReadonlyFields.js +51 -0
- package/src/react/components/address/defaultAddressHelpers.js +163 -0
- package/src/react/components/address/defaultAddressStyles.js +148 -0
- package/src/react/components/address/defaultAddressUtils.js +129 -0
- package/src/react/components/extra-data/DefaultExtraData.js +152 -0
- package/src/react/components/extra-data/DefaultExtraData.styles.js +61 -0
- package/src/react/components/files/DefaultFile.js +231 -46
- package/src/react/components/files/DefaultFileColumn.js +123 -0
- package/src/react/components/files/DefaultFileColumn.styles.js +52 -0
- package/src/react/components/files/DefaultFileColumn.utils.js +104 -0
- package/src/react/components/filters/CompactFilterSelector.js +51 -35
- package/src/react/components/filters/DefaultColumnFilter.js +60 -48
- package/src/react/components/filters/DefaultExternalFilters.js +324 -265
- package/src/react/components/filters/filterValue.js +65 -0
- package/src/react/components/form/DefaultForm.js +99 -79
- package/src/react/components/help/DefaultTooltip.js +48 -12
- package/src/react/components/help/DefaultTooltip.styles.js +20 -0
- package/src/react/components/inputs/DefaultColorInput.js +153 -0
- package/src/react/components/inputs/DefaultFeatherIconPicker.js +146 -0
- package/src/react/components/inputs/DefaultFeatherIconPicker.styles.js +113 -0
- package/src/react/components/inputs/DefaultInput.js +296 -141
- package/src/react/components/inputs/DefaultInput.styles.js +26 -0
- package/src/react/components/inputs/DefaultSelect.js +134 -123
- package/src/react/components/inputs/defaultInputUtils.js +262 -213
- package/src/react/components/map/DefaultMap.native.js +73 -73
- package/src/react/components/map/DefaultMap.shared.js +911 -762
- package/src/react/components/map/DefaultMap.web.js +124 -68
- package/src/react/components/table/DefaultColumnMenu.js +22 -3
- package/src/react/components/table/DefaultDebug.js +44 -37
- package/src/react/components/table/DefaultSearchModal.js +9 -1
- package/src/react/components/table/DefaultTable.grouping.js +98 -0
- package/src/react/components/table/DefaultTable.js +217 -111
- package/src/react/components/table/DefaultTable.styles.js +369 -310
- package/src/react/components/table/DefaultTable.utils.js +38 -4
- package/src/react/components/table/DefaultTableBody.js +13 -8
- package/src/react/components/table/DefaultTableCards.js +20 -1
- package/src/react/components/table/DefaultTableControls.js +180 -205
- package/src/react/components/table/DefaultTableImportModal.js +108 -0
- package/src/react/components/table/DefaultTableInput.js +1 -0
- package/src/react/components/table/DefaultTableRowActions.js +1 -1
- package/src/react/components/table/DefaultTableRows.js +357 -325
- package/src/react/components/table/DefaultTableSearch.js +28 -5
- package/src/react/components/table/DefaultTableToolbar.js +92 -3
- package/src/react/components/table/useDefaultTablePagination.js +56 -7
- package/src/react/components/table/useDefaultTableStoreSync.js +164 -0
- package/src/react/components/upload/DefaultUpload.js +406 -0
- package/src/react/components/upload/DefaultUpload.styles.js +153 -0
- package/src/react/components/upload/DefaultUploadAttachmentsList.js +146 -0
- package/src/react/components/upload/DefaultUploadManagerModal.js +247 -0
- package/src/react/components/upload/defaultUploadHelpers.js +108 -0
- package/src/react/components/upload/defaultUploadLibrary.js +248 -0
- package/src/react/components/upload/fileUpload.js +93 -0
- package/src/react/hooks/usePostalCodeLookup.js +159 -0
- package/src/react/index.js +6 -0
- package/src/react/services/addressFormUtils.js +262 -0
- package/src/react/services/addressGeo.js +230 -0
- package/src/react/utils/tableVisibleColumnsPreferences.js +113 -15
- package/src/tests/react/components/address/defaultAddressHelpers.test.js +104 -0
- package/src/tests/react/components/files/DefaultFile.test.js +22 -6
- package/src/tests/react/components/files/DefaultFileColumn.company.test.js +24 -0
- package/src/tests/react/components/form/DefaultForm.test.js +123 -0
- package/src/tests/react/components/help/DefaultTooltip.test.js +93 -0
- package/src/tests/react/components/inputs/DefaultSelect.test.js +108 -79
- package/src/tests/react/components/table/DefaultTable.grouping.test.js +49 -0
- package/src/tests/react/components/table/DefaultTable.test.js +53 -2
- package/src/tests/react/components/table/DefaultTable.utils.test.js +10 -0
- package/src/tests/react/components/table/DefaultTableControls.filters.test.mjs +21 -0
- package/src/tests/react/components/table/DefaultTableRowActions.test.js +23 -0
- package/src/tests/react/components/table/DefaultTableRows.rowActions.test.js +152 -0
- package/src/tests/react/components/table/resolveRowBackgroundColor.test.mjs +45 -0
- package/src/tests/react/components/table/useDefaultTableStoreSync.test.js +161 -0
- package/src/tests/react/components/upload/DefaultUpload.test.js +353 -0
- package/src/tests/react/components/upload/defaultUploadLibrary.test.js +104 -0
- package/src/tests/react/components/upload/peopleMediaLibraryPreview.test.mjs +57 -0
- package/src/tests/react/filters/filterValue.test.js +46 -0
- package/src/tests/react/hooks/usePostalCodeLookup.test.js +107 -0
- package/src/tests/react/inputs/defaultInputUtils.test.js +9 -1
- package/src/tests/react/services/SMOKE-637-cep-hydrate.md +45 -0
- package/src/tests/react/services/addressFormUtils.test.js +179 -0
- package/src/tests/react/services/addressGeoLookup.async.test.js +104 -0
- package/src/tests/react/services/addressGeoLookup.test.js +105 -0
- package/src/tests/react/services/smoke-637/form-cep-12941040.png +0 -0
- package/src/tests/react/services/smoke-637/index.html +132 -0
- package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +96 -49
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Modal,
|
|
4
|
-
ScrollView,
|
|
5
|
-
Text,
|
|
6
|
-
TextInput,
|
|
7
|
-
TouchableOpacity,
|
|
8
|
-
TouchableWithoutFeedback,
|
|
9
|
-
View,
|
|
10
|
-
useWindowDimensions,
|
|
4
|
+
ScrollView,
|
|
5
|
+
Text,
|
|
6
|
+
TextInput,
|
|
7
|
+
TouchableOpacity,
|
|
8
|
+
TouchableWithoutFeedback,
|
|
9
|
+
View,
|
|
10
|
+
useWindowDimensions,
|
|
11
11
|
} from 'react-native';
|
|
12
12
|
import Icon from 'react-native-vector-icons/Feather';
|
|
13
13
|
import { getAllStores, useStore } from '@store';
|
|
@@ -19,11 +19,17 @@ import {
|
|
|
19
19
|
isDateLikeColumn,
|
|
20
20
|
resolveStoreNameFromList,
|
|
21
21
|
} from '../inputs/defaultInputUtils';
|
|
22
|
-
import CompactFilterSelector from './CompactFilterSelector';
|
|
23
|
-
import DateShortcutFilter from './DateShortcutFilter';
|
|
24
|
-
import { resolveNextDateFilterValue } from './dateFilterSelection';
|
|
25
|
-
import
|
|
26
|
-
|
|
22
|
+
import CompactFilterSelector from './CompactFilterSelector';
|
|
23
|
+
import DateShortcutFilter from './DateShortcutFilter';
|
|
24
|
+
import { resolveNextDateFilterValue } from './dateFilterSelection';
|
|
25
|
+
import { isFilledFilterValue, omitEmptyFilters } from './filterValue';
|
|
26
|
+
import {
|
|
27
|
+
persistTableFiltersPreference,
|
|
28
|
+
resolveDefaultTablePreferenceScope,
|
|
29
|
+
sanitizeTableFiltersPreference,
|
|
30
|
+
} from '../../utils/tableVisibleColumnsPreferences';
|
|
31
|
+
import styles from './DefaultExternalFilters.styles';
|
|
32
|
+
|
|
27
33
|
const DEFAULT_COMPACT_BREAKPOINT = 768;
|
|
28
34
|
const noop = () => {};
|
|
29
35
|
|
|
@@ -94,7 +100,7 @@ const shouldIncludeColumn = column =>
|
|
|
94
100
|
column?.externalFilter === true &&
|
|
95
101
|
column?.filter !== false &&
|
|
96
102
|
column?.filters !== false;
|
|
97
|
-
|
|
103
|
+
|
|
98
104
|
const normalizeFilterValue = value => {
|
|
99
105
|
if (value && typeof value === 'object') {
|
|
100
106
|
return normalizeFilterValue(value.value ?? value.id ?? value['@id'] ?? value.key ?? '');
|
|
@@ -102,56 +108,47 @@ const normalizeFilterValue = value => {
|
|
|
102
108
|
|
|
103
109
|
return normalizeText(value);
|
|
104
110
|
};
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return
|
|
141
|
-
customRange: { from: '', to: '' },
|
|
142
|
-
value: 'all',
|
|
143
|
-
};
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
const resolveIcon = column => {
|
|
147
|
-
const key = getColumnKey(column);
|
|
148
|
-
if (key === 'status') return 'check-circle';
|
|
149
|
-
if (key === 'category') return 'tag';
|
|
111
|
+
|
|
112
|
+
const resolveDateState = filterValue => {
|
|
113
|
+
if (!filterValue || typeof filterValue !== 'object') {
|
|
114
|
+
return {
|
|
115
|
+
customRange: { from: '', to: '' },
|
|
116
|
+
value: 'all',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (filterValue.shortcut) {
|
|
121
|
+
return {
|
|
122
|
+
customRange: filterValue.customRange || { from: '', to: '' },
|
|
123
|
+
value: filterValue.shortcut,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (filterValue.start || filterValue.end || filterValue.after || filterValue.before) {
|
|
128
|
+
return {
|
|
129
|
+
customRange: {
|
|
130
|
+
from: filterValue.start || filterValue.after || '',
|
|
131
|
+
to: filterValue.end || filterValue.before || '',
|
|
132
|
+
},
|
|
133
|
+
value: 'custom',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
customRange: { from: '', to: '' },
|
|
139
|
+
value: 'all',
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const resolveIcon = column => {
|
|
144
|
+
const key = getColumnKey(column);
|
|
145
|
+
if (key === 'status') return 'check-circle';
|
|
146
|
+
if (key === 'category') return 'tag';
|
|
150
147
|
if (isDateLikeColumn(column)) return 'calendar';
|
|
151
148
|
return 'sliders';
|
|
152
149
|
};
|
|
153
|
-
|
|
154
|
-
const DefaultExternalFilters = ({
|
|
150
|
+
|
|
151
|
+
const DefaultExternalFilters = ({
|
|
155
152
|
accentColor = null,
|
|
156
153
|
compactBreakpoint = DEFAULT_COMPACT_BREAKPOINT,
|
|
157
154
|
dateOptionKeys = ['all', 'today', 'yesterday', '7d', '30d', 'custom'],
|
|
@@ -165,22 +162,30 @@ const DefaultExternalFilters = ({
|
|
|
165
162
|
const { width } = useWindowDimensions();
|
|
166
163
|
const externalFilterStore = useStore(storeName);
|
|
167
164
|
const peopleStore = useStore('people');
|
|
165
|
+
const storeConfigs = externalFilterStore?.getters?.configs || {};
|
|
166
|
+
const tablePreferenceScope =
|
|
167
|
+
storeConfigs.tablePreferenceScope ||
|
|
168
|
+
resolveDefaultTablePreferenceScope({
|
|
169
|
+
companyId: peopleStore?.getters?.currentCompany?.id,
|
|
170
|
+
storeName,
|
|
171
|
+
});
|
|
168
172
|
const themeStore = useStore('theme');
|
|
169
173
|
const [isFiltersModalOpen, setIsFiltersModalOpen] = useState(false);
|
|
174
|
+
const [loadedListItemsByColumn, setLoadedListItemsByColumn] = useState({});
|
|
170
175
|
const loadedListStoresRef = useRef(new Set());
|
|
171
176
|
const { currentCompany } = peopleStore.getters || {};
|
|
172
177
|
const { colors: themeColors } = themeStore.getters;
|
|
173
|
-
const themeTokens = useMemo(
|
|
174
|
-
() => ({...themeColors, ...(currentCompany?.theme?.colors || {})}),
|
|
175
|
-
[currentCompany?.theme?.colors, themeColors],
|
|
176
|
-
);
|
|
177
|
-
const palette = useMemo(
|
|
178
|
-
() => resolveThemePalette(themeTokens, colors),
|
|
179
|
-
[themeTokens],
|
|
180
|
-
);
|
|
181
|
-
const resolvedAccentColor = accentColor || palette.primary;
|
|
182
|
-
const surfaceColor = themeTokens['bg-odd-light'] || palette.background;
|
|
183
|
-
const panelColor = themeTokens['bg-headers-light'] || palette.background;
|
|
178
|
+
const themeTokens = useMemo(
|
|
179
|
+
() => ({...themeColors, ...(currentCompany?.theme?.colors || {})}),
|
|
180
|
+
[currentCompany?.theme?.colors, themeColors],
|
|
181
|
+
);
|
|
182
|
+
const palette = useMemo(
|
|
183
|
+
() => resolveThemePalette(themeTokens, colors),
|
|
184
|
+
[themeTokens],
|
|
185
|
+
);
|
|
186
|
+
const resolvedAccentColor = accentColor || palette.primary;
|
|
187
|
+
const surfaceColor = themeTokens['bg-odd-light'] || palette.background;
|
|
188
|
+
const panelColor = themeTokens['bg-headers-light'] || palette.background;
|
|
184
189
|
const borderColor = palette.border;
|
|
185
190
|
const textColor = palette.text;
|
|
186
191
|
const textSecondaryColor = palette.textSecondary;
|
|
@@ -199,35 +204,61 @@ const DefaultExternalFilters = ({
|
|
|
199
204
|
() => resolvedColumns.filter(shouldIncludeColumn),
|
|
200
205
|
[resolvedColumns],
|
|
201
206
|
);
|
|
202
|
-
const activeCount = useMemo(
|
|
203
|
-
() => filterColumns.filter(column => isFilledFilterValue(filters[getColumnKey(column)])).length,
|
|
204
|
-
[filterColumns, filters],
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
useEffect(() => {
|
|
208
|
-
onActiveCountChange?.(activeCount);
|
|
209
|
-
}, [activeCount, onActiveCountChange]);
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
207
|
+
const activeCount = useMemo(
|
|
208
|
+
() => filterColumns.filter(column => isFilledFilterValue(filters[getColumnKey(column)])).length,
|
|
209
|
+
[filterColumns, filters],
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
onActiveCountChange?.(activeCount);
|
|
214
|
+
}, [activeCount, onActiveCountChange]);
|
|
215
|
+
|
|
216
|
+
const applyFilters = useCallback(nextFilters => {
|
|
217
|
+
const resolvedFilters = omitEmptyFilters(
|
|
218
|
+
nextFilters && typeof nextFilters === 'object' && !Array.isArray(nextFilters)
|
|
219
|
+
? nextFilters
|
|
220
|
+
: {},
|
|
221
|
+
);
|
|
222
|
+
const columns = Array.isArray(externalFilterStore?.getters?.columns)
|
|
223
|
+
? externalFilterStore.getters.columns
|
|
224
|
+
: [];
|
|
225
|
+
const persistedFilters = sanitizeTableFiltersPreference({
|
|
226
|
+
columns,
|
|
227
|
+
filters: resolvedFilters,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
persistTableFiltersPreference(tablePreferenceScope, persistedFilters);
|
|
231
|
+
|
|
232
|
+
if (typeof externalFilterStore?.actions?.setFilters === 'function') {
|
|
233
|
+
externalFilterStore.actions.setFilters(resolvedFilters);
|
|
234
|
+
} else if (externalFilterStore?.getters) {
|
|
235
|
+
externalFilterStore.getters.filters = resolvedFilters;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
onChangeFilters?.(resolvedFilters);
|
|
239
|
+
}, [externalFilterStore, onChangeFilters, tablePreferenceScope]);
|
|
240
|
+
|
|
241
|
+
const updateFilter = useCallback((key, value) => {
|
|
242
|
+
const nextFilters = { ...(filters || {}) };
|
|
243
|
+
if (!isFilledFilterValue(value)) {
|
|
244
|
+
delete nextFilters[key];
|
|
245
|
+
} else {
|
|
246
|
+
nextFilters[key] = value;
|
|
247
|
+
}
|
|
248
|
+
applyFilters(nextFilters);
|
|
249
|
+
}, [applyFilters, filters]);
|
|
250
|
+
|
|
251
|
+
const clearFilters = useCallback(() => {
|
|
252
|
+
applyFilters({});
|
|
253
|
+
}, [applyFilters]);
|
|
254
|
+
|
|
225
255
|
useEffect(() => {
|
|
226
256
|
if (!isCompactView) setIsFiltersModalOpen(false);
|
|
227
257
|
}, [isCompactView]);
|
|
228
258
|
|
|
229
259
|
useEffect(() => {
|
|
230
260
|
loadedListStoresRef.current.clear();
|
|
261
|
+
setLoadedListItemsByColumn({});
|
|
231
262
|
}, [currentCompany?.id]);
|
|
232
263
|
|
|
233
264
|
const loadListOptionsForColumn = useCallback((column, searchValue = '') => {
|
|
@@ -254,18 +285,14 @@ const DefaultExternalFilters = ({
|
|
|
254
285
|
});
|
|
255
286
|
const hasScopedParams = Object.keys(listLoadParams).length > 0;
|
|
256
287
|
|
|
288
|
+
const loadKey = `${getColumnKey(column)}:${listStoreName}:${actionName}:${stableSerialize(listLoadParams)}`;
|
|
289
|
+
const columnKey = getColumnKey(column);
|
|
257
290
|
if (
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
Array.isArray(
|
|
261
|
-
listStore.getters.items.length > 0
|
|
291
|
+
loadedListStoresRef.current.has(loadKey) &&
|
|
292
|
+
columnKey &&
|
|
293
|
+
Array.isArray(loadedListItemsByColumn[columnKey])
|
|
262
294
|
) {
|
|
263
|
-
return Promise.resolve(
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const loadKey = `${getColumnKey(column)}:${listStoreName}:${actionName}:${stableSerialize(listLoadParams)}`;
|
|
267
|
-
if (loadedListStoresRef.current.has(loadKey)) {
|
|
268
|
-
return Promise.resolve(listStore?.getters?.items || []);
|
|
295
|
+
return Promise.resolve(loadedListItemsByColumn[columnKey]);
|
|
269
296
|
}
|
|
270
297
|
|
|
271
298
|
loadedListStoresRef.current.add(loadKey);
|
|
@@ -277,65 +304,93 @@ const DefaultExternalFilters = ({
|
|
|
277
304
|
skipSystemError: true,
|
|
278
305
|
},
|
|
279
306
|
}),
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
307
|
+
)
|
|
308
|
+
.then(response => {
|
|
309
|
+
const items = Array.isArray(response)
|
|
310
|
+
? response
|
|
311
|
+
: Array.isArray(response?.member)
|
|
312
|
+
? response.member
|
|
313
|
+
: Array.isArray(response?.['hydra:member'])
|
|
314
|
+
? response['hydra:member']
|
|
315
|
+
: listStore?.getters?.items || [];
|
|
316
|
+
const resolvedColumnKey = getColumnKey(column);
|
|
317
|
+
if (resolvedColumnKey) {
|
|
318
|
+
setLoadedListItemsByColumn(current => ({
|
|
319
|
+
...current,
|
|
320
|
+
[resolvedColumnKey]: items,
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
return items;
|
|
324
|
+
})
|
|
325
|
+
.catch(error => {
|
|
326
|
+
loadedListStoresRef.current.delete(loadKey);
|
|
327
|
+
throw error;
|
|
328
|
+
});
|
|
329
|
+
}, [currentCompany?.id, getOptionsForColumn, loadedListItemsByColumn, requestParams]);
|
|
285
330
|
|
|
286
331
|
if (filterColumns.length === 0) return null;
|
|
287
|
-
|
|
288
|
-
const renderFilterField = (column, compact = false) => {
|
|
289
|
-
const key = getColumnKey(column);
|
|
290
|
-
const fieldStyle = compact ? styles.modalField : styles.field;
|
|
291
|
-
|
|
332
|
+
|
|
333
|
+
const renderFilterField = (column, compact = false) => {
|
|
334
|
+
const key = getColumnKey(column);
|
|
335
|
+
const fieldStyle = compact ? styles.modalField : styles.field;
|
|
336
|
+
|
|
292
337
|
if (isDateLikeColumn(column)) {
|
|
293
338
|
const dateState = resolveDateState(filters[key]);
|
|
294
|
-
|
|
295
|
-
return (
|
|
296
|
-
<View key={key} style={fieldStyle}>
|
|
339
|
+
|
|
340
|
+
return (
|
|
341
|
+
<View key={key} style={fieldStyle}>
|
|
297
342
|
<DateShortcutFilter
|
|
298
343
|
value={dateState.value}
|
|
299
|
-
onChange={optionKey => {
|
|
300
|
-
updateFilter(
|
|
301
|
-
key,
|
|
302
|
-
resolveNextDateFilterValue(filters[key], optionKey),
|
|
303
|
-
);
|
|
304
|
-
}}
|
|
305
|
-
customRange={dateState.customRange}
|
|
306
|
-
onCustomRangeChange={range => {
|
|
307
|
-
updateFilter(key, {
|
|
308
|
-
...(filters[key] || {}),
|
|
309
|
-
shortcut: 'custom',
|
|
310
|
-
customRange: range,
|
|
311
|
-
});
|
|
312
|
-
}}
|
|
313
|
-
dense
|
|
344
|
+
onChange={optionKey => {
|
|
345
|
+
updateFilter(
|
|
346
|
+
key,
|
|
347
|
+
resolveNextDateFilterValue(filters[key], optionKey),
|
|
348
|
+
);
|
|
349
|
+
}}
|
|
350
|
+
customRange={dateState.customRange}
|
|
351
|
+
onCustomRangeChange={range => {
|
|
352
|
+
updateFilter(key, {
|
|
353
|
+
...(filters[key] || {}),
|
|
354
|
+
shortcut: 'custom',
|
|
355
|
+
customRange: range,
|
|
356
|
+
});
|
|
357
|
+
}}
|
|
358
|
+
dense
|
|
314
359
|
store={storeName}
|
|
315
360
|
field={key}
|
|
316
361
|
labelCaption={global.t?.t(storeName, 'label', column.label || key)}
|
|
317
362
|
optionKeys={dateOptionKeys}
|
|
318
|
-
colors={{
|
|
319
|
-
accent: resolvedAccentColor,
|
|
320
|
-
appBg: 'transparent',
|
|
321
|
-
border: borderColor,
|
|
322
|
-
borderSoft: withOpacity(borderColor, 0.72),
|
|
323
|
-
cardBg: palette.background,
|
|
324
|
-
cardBgSoft: surfaceColor,
|
|
325
|
-
danger: palette.error,
|
|
326
|
-
isLight: true,
|
|
327
|
-
panelBg: panelColor,
|
|
328
|
-
pillTextDark: onAccentColor,
|
|
329
|
-
textPrimary: textColor,
|
|
330
|
-
textSecondary: textSecondaryColor,
|
|
331
|
-
}}
|
|
332
|
-
/>
|
|
333
|
-
</View>
|
|
334
|
-
);
|
|
363
|
+
colors={{
|
|
364
|
+
accent: resolvedAccentColor,
|
|
365
|
+
appBg: 'transparent',
|
|
366
|
+
border: borderColor,
|
|
367
|
+
borderSoft: withOpacity(borderColor, 0.72),
|
|
368
|
+
cardBg: palette.background,
|
|
369
|
+
cardBgSoft: surfaceColor,
|
|
370
|
+
danger: palette.error,
|
|
371
|
+
isLight: true,
|
|
372
|
+
panelBg: panelColor,
|
|
373
|
+
pillTextDark: onAccentColor,
|
|
374
|
+
textPrimary: textColor,
|
|
375
|
+
textSecondary: textSecondaryColor,
|
|
376
|
+
}}
|
|
377
|
+
/>
|
|
378
|
+
</View>
|
|
379
|
+
);
|
|
335
380
|
}
|
|
336
381
|
|
|
337
382
|
if (column.list) {
|
|
338
383
|
const listStoreName = resolveStoreNameFromList(column?.list);
|
|
384
|
+
const columnKey = getColumnKey(column);
|
|
385
|
+
const explicitOptions = getOptionsForColumn?.(column);
|
|
386
|
+
const loadedItems = loadedListItemsByColumn[columnKey] || [];
|
|
387
|
+
/*
|
|
388
|
+
* @agents Do not read shared store items here — only options loaded on
|
|
389
|
+
* modal open (or explicitly provided) so company-scoped lists stay correct.
|
|
390
|
+
*/
|
|
391
|
+
const listSource = Array.isArray(explicitOptions) && explicitOptions.length > 0
|
|
392
|
+
? explicitOptions
|
|
393
|
+
: loadedItems;
|
|
339
394
|
const options = [
|
|
340
395
|
{
|
|
341
396
|
key: '',
|
|
@@ -345,21 +400,25 @@ const DefaultExternalFilters = ({
|
|
|
345
400
|
: null) ||
|
|
346
401
|
global.t?.t(storeName || 'invoice', 'label', 'select'),
|
|
347
402
|
},
|
|
348
|
-
...buildOptionsFromColumn(
|
|
403
|
+
...buildOptionsFromColumn(
|
|
404
|
+
column,
|
|
405
|
+
() => listSource,
|
|
406
|
+
storeName,
|
|
407
|
+
),
|
|
349
408
|
];
|
|
350
409
|
const selectedKey = normalizeFilterValue(filters[key]);
|
|
351
|
-
const selectedLabel =
|
|
352
|
-
options.find(option => option.key === selectedKey)?.label ||
|
|
353
|
-
options[0]?.label ||
|
|
354
|
-
'';
|
|
355
|
-
|
|
356
|
-
return (
|
|
357
|
-
<View key={key} style={fieldStyle}>
|
|
358
|
-
<CompactFilterSelector
|
|
359
|
-
icon={resolveIcon(column)}
|
|
360
|
-
label={selectedLabel}
|
|
361
|
-
accentColor={resolvedAccentColor}
|
|
362
|
-
active={Boolean(selectedKey)}
|
|
410
|
+
const selectedLabel =
|
|
411
|
+
options.find(option => option.key === selectedKey)?.label ||
|
|
412
|
+
options[0]?.label ||
|
|
413
|
+
'';
|
|
414
|
+
|
|
415
|
+
return (
|
|
416
|
+
<View key={key} style={fieldStyle}>
|
|
417
|
+
<CompactFilterSelector
|
|
418
|
+
icon={resolveIcon(column)}
|
|
419
|
+
label={selectedLabel}
|
|
420
|
+
accentColor={resolvedAccentColor}
|
|
421
|
+
active={Boolean(selectedKey)}
|
|
363
422
|
dense
|
|
364
423
|
store={storeName}
|
|
365
424
|
field={key}
|
|
@@ -368,36 +427,36 @@ const DefaultExternalFilters = ({
|
|
|
368
427
|
options={options}
|
|
369
428
|
searchable
|
|
370
429
|
selectedKey={selectedKey}
|
|
371
|
-
onSelect={optionKey => {
|
|
372
|
-
updateFilter(key, optionKey);
|
|
373
|
-
return true;
|
|
374
|
-
}}
|
|
375
|
-
/>
|
|
376
|
-
</View>
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return (
|
|
381
|
-
<View key={key} style={[fieldStyle, styles.inputWrap, { borderColor, backgroundColor: surfaceColor }]}>
|
|
382
|
-
<Text style={[styles.inputLabel, { color: textSecondaryColor }]}>
|
|
383
|
-
{global.t?.t(storeName, 'input', column.label || key)}
|
|
384
|
-
</Text>
|
|
385
|
-
<TextInput
|
|
386
|
-
style={[styles.input, { color: textColor }]}
|
|
387
|
-
value={normalizeText(filters[key])}
|
|
388
|
-
onChangeText={value => updateFilter(key, value)}
|
|
389
|
-
onSubmitEditing={() =>
|
|
390
|
-
/>
|
|
391
|
-
</View>
|
|
392
|
-
);
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
const filterTitle = global.t?.t(storeName, 'label', 'filters');
|
|
396
|
-
const filterFields = compact => filterColumns.map(column => renderFilterField(column, compact));
|
|
397
|
-
|
|
430
|
+
onSelect={optionKey => {
|
|
431
|
+
updateFilter(key, optionKey);
|
|
432
|
+
return true;
|
|
433
|
+
}}
|
|
434
|
+
/>
|
|
435
|
+
</View>
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return (
|
|
440
|
+
<View key={key} style={[fieldStyle, styles.inputWrap, { borderColor, backgroundColor: surfaceColor }]}>
|
|
441
|
+
<Text style={[styles.inputLabel, { color: textSecondaryColor }]}>
|
|
442
|
+
{global.t?.t(storeName, 'input', column.label || key)}
|
|
443
|
+
</Text>
|
|
444
|
+
<TextInput
|
|
445
|
+
style={[styles.input, { color: textColor }]}
|
|
446
|
+
value={normalizeText(filters[key])}
|
|
447
|
+
onChangeText={value => updateFilter(key, value)}
|
|
448
|
+
onSubmitEditing={() => applyFilters(filters)}
|
|
449
|
+
/>
|
|
450
|
+
</View>
|
|
451
|
+
);
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const filterTitle = global.t?.t(storeName, 'label', 'filters');
|
|
455
|
+
const filterFields = compact => filterColumns.map(column => renderFilterField(column, compact));
|
|
456
|
+
|
|
398
457
|
if (isCompactView) {
|
|
399
|
-
return (
|
|
400
|
-
<View style={styles.mobileWrap}>
|
|
458
|
+
return (
|
|
459
|
+
<View style={styles.mobileWrap}>
|
|
401
460
|
<TouchableOpacity
|
|
402
461
|
style={[
|
|
403
462
|
styles.mobileButton,
|
|
@@ -422,83 +481,83 @@ const DefaultExternalFilters = ({
|
|
|
422
481
|
<Text style={styles.mobileCountBadgeText}>{activeCount}</Text>
|
|
423
482
|
</View>
|
|
424
483
|
) : null}
|
|
425
|
-
</TouchableOpacity>
|
|
426
|
-
|
|
427
|
-
<Modal
|
|
428
|
-
transparent
|
|
429
|
-
visible={isFiltersModalOpen}
|
|
430
|
-
animationType="fade"
|
|
431
|
-
onRequestClose={() => setIsFiltersModalOpen(false)}
|
|
432
|
-
>
|
|
433
|
-
<TouchableWithoutFeedback onPress={() => setIsFiltersModalOpen(false)}>
|
|
434
|
-
<View style={[styles.modalOverlay, { backgroundColor: withOpacity(textColor, 0.42) }]}>
|
|
435
|
-
<TouchableWithoutFeedback onPress={noop}>
|
|
436
|
-
<View style={[styles.modalCard, { borderColor, backgroundColor: surfaceColor }]}>
|
|
437
|
-
<View style={[styles.modalHeader, { borderBottomColor: borderColor }]}>
|
|
438
|
-
<Text style={[styles.modalTitle, { color: textColor }]}>{filterTitle}</Text>
|
|
439
|
-
<TouchableOpacity
|
|
440
|
-
style={[styles.modalCloseButton, { borderColor: borderColor, backgroundColor: surfaceColor }]}
|
|
441
|
-
activeOpacity={0.82}
|
|
442
|
-
onPress={() => setIsFiltersModalOpen(false)}
|
|
443
|
-
>
|
|
444
|
-
<Icon name="x" size={18} color={textSecondaryColor} />
|
|
445
|
-
</TouchableOpacity>
|
|
446
|
-
</View>
|
|
447
|
-
|
|
448
|
-
<ScrollView
|
|
449
|
-
style={styles.modalScroll}
|
|
450
|
-
contentContainerStyle={styles.modalContent}
|
|
451
|
-
keyboardShouldPersistTaps="handled"
|
|
452
|
-
>
|
|
453
|
-
{filterFields(true)}
|
|
454
|
-
</ScrollView>
|
|
455
|
-
|
|
456
|
-
<View style={[styles.modalActions, { borderTopColor: borderColor }]}>
|
|
457
|
-
{activeCount > 0 ? (
|
|
458
|
-
<TouchableOpacity
|
|
459
|
-
style={[styles.modalSecondaryButton, { borderColor: borderColor, backgroundColor: surfaceColor }]}
|
|
460
|
-
activeOpacity={0.84}
|
|
461
|
-
onPress={clearFilters}
|
|
462
|
-
>
|
|
463
|
-
<Text style={[styles.modalSecondaryButtonText, { color: textColor }]}>
|
|
464
|
-
{global.t?.t(storeName, 'button', 'clear')}
|
|
465
|
-
</Text>
|
|
466
|
-
</TouchableOpacity>
|
|
467
|
-
) : null}
|
|
484
|
+
</TouchableOpacity>
|
|
485
|
+
|
|
486
|
+
<Modal
|
|
487
|
+
transparent
|
|
488
|
+
visible={isFiltersModalOpen}
|
|
489
|
+
animationType="fade"
|
|
490
|
+
onRequestClose={() => setIsFiltersModalOpen(false)}
|
|
491
|
+
>
|
|
492
|
+
<TouchableWithoutFeedback onPress={() => setIsFiltersModalOpen(false)}>
|
|
493
|
+
<View style={[styles.modalOverlay, { backgroundColor: withOpacity(textColor, 0.42) }]}>
|
|
494
|
+
<TouchableWithoutFeedback onPress={noop}>
|
|
495
|
+
<View style={[styles.modalCard, { borderColor, backgroundColor: surfaceColor }]}>
|
|
496
|
+
<View style={[styles.modalHeader, { borderBottomColor: borderColor }]}>
|
|
497
|
+
<Text style={[styles.modalTitle, { color: textColor }]}>{filterTitle}</Text>
|
|
498
|
+
<TouchableOpacity
|
|
499
|
+
style={[styles.modalCloseButton, { borderColor: borderColor, backgroundColor: surfaceColor }]}
|
|
500
|
+
activeOpacity={0.82}
|
|
501
|
+
onPress={() => setIsFiltersModalOpen(false)}
|
|
502
|
+
>
|
|
503
|
+
<Icon name="x" size={18} color={textSecondaryColor} />
|
|
504
|
+
</TouchableOpacity>
|
|
505
|
+
</View>
|
|
506
|
+
|
|
507
|
+
<ScrollView
|
|
508
|
+
style={styles.modalScroll}
|
|
509
|
+
contentContainerStyle={styles.modalContent}
|
|
510
|
+
keyboardShouldPersistTaps="handled"
|
|
511
|
+
>
|
|
512
|
+
{filterFields(true)}
|
|
513
|
+
</ScrollView>
|
|
514
|
+
|
|
515
|
+
<View style={[styles.modalActions, { borderTopColor: borderColor }]}>
|
|
516
|
+
{activeCount > 0 ? (
|
|
517
|
+
<TouchableOpacity
|
|
518
|
+
style={[styles.modalSecondaryButton, { borderColor: borderColor, backgroundColor: surfaceColor }]}
|
|
519
|
+
activeOpacity={0.84}
|
|
520
|
+
onPress={clearFilters}
|
|
521
|
+
>
|
|
522
|
+
<Text style={[styles.modalSecondaryButtonText, { color: textColor }]}>
|
|
523
|
+
{global.t?.t(storeName, 'button', 'clear')}
|
|
524
|
+
</Text>
|
|
525
|
+
</TouchableOpacity>
|
|
526
|
+
) : null}
|
|
468
527
|
<TouchableOpacity
|
|
469
528
|
style={[styles.modalPrimaryButton, { backgroundColor: buttonBackgroundColor }]}
|
|
470
529
|
activeOpacity={0.84}
|
|
471
530
|
onPress={() => setIsFiltersModalOpen(false)}
|
|
472
531
|
>
|
|
473
532
|
<Text style={[styles.modalPrimaryButtonText, { color: buttonTextColor }]}>
|
|
474
|
-
{global.t?.t(storeName, 'button', 'apply')}
|
|
475
|
-
</Text>
|
|
476
|
-
</TouchableOpacity>
|
|
477
|
-
</View>
|
|
478
|
-
</View>
|
|
479
|
-
</TouchableWithoutFeedback>
|
|
480
|
-
</View>
|
|
481
|
-
</TouchableWithoutFeedback>
|
|
482
|
-
</Modal>
|
|
483
|
-
</View>
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
<View style={styles.wrap}>
|
|
489
|
-
{filterFields(false)}
|
|
490
|
-
|
|
491
|
-
{activeCount > 0 ? (
|
|
492
|
-
<TouchableOpacity
|
|
493
|
-
style={[styles.clearButton, { borderColor: borderColor, backgroundColor: surfaceColor }]}
|
|
494
|
-
activeOpacity={0.82}
|
|
495
|
-
onPress={clearFilters}
|
|
496
|
-
>
|
|
497
|
-
<Icon name="x" size={14} color={textSecondaryColor} />
|
|
498
|
-
</TouchableOpacity>
|
|
499
|
-
) : null}
|
|
500
|
-
</View>
|
|
501
|
-
);
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
export default DefaultExternalFilters;
|
|
533
|
+
{global.t?.t(storeName, 'button', 'apply')}
|
|
534
|
+
</Text>
|
|
535
|
+
</TouchableOpacity>
|
|
536
|
+
</View>
|
|
537
|
+
</View>
|
|
538
|
+
</TouchableWithoutFeedback>
|
|
539
|
+
</View>
|
|
540
|
+
</TouchableWithoutFeedback>
|
|
541
|
+
</Modal>
|
|
542
|
+
</View>
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return (
|
|
547
|
+
<View style={styles.wrap}>
|
|
548
|
+
{filterFields(false)}
|
|
549
|
+
|
|
550
|
+
{activeCount > 0 ? (
|
|
551
|
+
<TouchableOpacity
|
|
552
|
+
style={[styles.clearButton, { borderColor: borderColor, backgroundColor: surfaceColor }]}
|
|
553
|
+
activeOpacity={0.82}
|
|
554
|
+
onPress={clearFilters}
|
|
555
|
+
>
|
|
556
|
+
<Icon name="x" size={14} color={textSecondaryColor} />
|
|
557
|
+
</TouchableOpacity>
|
|
558
|
+
) : null}
|
|
559
|
+
</View>
|
|
560
|
+
);
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
export default DefaultExternalFilters;
|