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