@controleonline/ui-default 1.0.264 → 1.0.266

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/AGENTS.md +3 -2
  2. package/package.json +21 -18
  3. package/src/react/components/errors/DefaultErrors.js +64 -82
  4. package/src/react/components/filters/CompactFilterSelector.js +58 -23
  5. package/src/react/components/filters/CompactFilterSelector.styles.js +17 -5
  6. package/src/react/components/filters/DefaultColumnFilter.js +177 -45
  7. package/src/react/components/filters/DefaultExternalFilters.js +201 -143
  8. package/src/react/components/filters/DefaultSearch.js +27 -25
  9. package/src/react/components/form/DefaultForm.js +72 -53
  10. package/src/react/components/form/DefaultForm.styles.js +71 -66
  11. package/src/react/components/inputs/DefaultDateInput.js +150 -0
  12. package/src/react/components/inputs/DefaultInput.js +44 -11
  13. package/src/react/components/inputs/DefaultInput.styles.js +6 -0
  14. package/src/react/components/inputs/DefaultSelect.js +200 -43
  15. package/src/react/components/inputs/defaultInputUtils.js +40 -19
  16. package/src/react/components/table/DefaultColumnMenu.js +108 -0
  17. package/src/react/components/table/DefaultDebug.js +234 -0
  18. package/src/react/components/table/DefaultFiltersModal.js +142 -0
  19. package/src/react/components/table/DefaultModalButton.js +18 -0
  20. package/src/react/components/table/DefaultSearchModal.js +49 -0
  21. package/src/react/components/table/DefaultTable.js +322 -1964
  22. package/src/react/components/table/DefaultTable.styles.js +234 -102
  23. package/src/react/components/table/DefaultTable.utils.js +502 -0
  24. package/src/react/components/table/DefaultTableBody.js +26 -0
  25. package/src/react/components/table/DefaultTableCards.js +171 -0
  26. package/src/react/components/table/DefaultTableControls.js +205 -0
  27. package/src/react/components/table/DefaultTableEmptyState.js +22 -0
  28. package/src/react/components/table/DefaultTableFooter.js +172 -0
  29. package/src/react/components/table/DefaultTableInput.js +145 -0
  30. package/src/react/components/table/DefaultTableRows.js +344 -0
  31. package/src/react/components/table/DefaultTableSearch.js +100 -0
  32. package/src/react/components/table/DefaultTableToolbar.js +98 -0
  33. package/src/react/components/table/DefaultToolbarAction.js +56 -0
  34. package/src/react/components/table/useDefaultTablePagination.js +370 -0
  35. package/src/react/components/table/useDefaultTableSorting.js +126 -0
  36. package/src/react/components/table/useDefaultTableTheme.js +52 -0
  37. package/src/react/utils/tableVisibleColumnsPreferences.js +239 -211
  38. package/src/store/default/actions.js +51 -13
  39. package/src/store/default/getters.js +10 -8
  40. package/src/store/default/mutation_types.js +4 -2
  41. package/src/store/default/mutations.js +10 -1
  42. package/src/tests/react/components/form/DefaultForm.test.js +81 -0
  43. package/src/tests/react/components/inputs/DefaultSelect.test.js +106 -14
  44. package/src/tests/react/components/table/DefaultTable.test.js +1318 -334
  45. package/src/tests/react/store/defaultActions.test.js +59 -0
  46. package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +247 -168
@@ -25,7 +25,18 @@ const DEFAULT_TRANSLATABLE_FIELDS = new Set([
25
25
  'status',
26
26
  ]);
27
27
 
28
- export const getColumnKey = column => column?.key || column?.name || '';
28
+ export const getColumnKey = column => column?.key || column?.name || '';
29
+
30
+ export const isDateColumn = column =>
31
+ column?.inputType === 'date' ||
32
+ column?.type === 'date';
33
+
34
+ export const isDateRangeColumn = column =>
35
+ column?.inputType === 'date-range' ||
36
+ column?.type === 'range-date';
37
+
38
+ export const isDateLikeColumn = column =>
39
+ isDateColumn(column) || isDateRangeColumn(column);
29
40
 
30
41
  const normalizeColumnKey = value =>
31
42
  normalizeText(value)
@@ -114,7 +125,8 @@ export const resolveOptionLabel = (column, option, storeName = '') => {
114
125
  return translateOptionLabel(rawLabel);
115
126
  };
116
127
 
117
- export const resolveStoreNameFromList = list => normalizeText(list).split('/')[0] || '';
128
+ export const resolveStoreNameFromList = list =>
129
+ typeof list === 'string' ? normalizeText(list).split('/')[0] || '' : '';
118
130
 
119
131
  const filterItemsByListRequestParams = (items = [], column = null) => {
120
132
  const requestParams =
@@ -164,13 +176,17 @@ export const mapOptions = (column, items = [], storeName = '') => {
164
176
  return options;
165
177
  };
166
178
 
167
- export const buildOptionsFromColumn = (column, getOptionsForColumn = null, storeName = '') => {
168
- const explicitOptions = getOptionsForColumn?.(column);
169
- if (Array.isArray(explicitOptions) && explicitOptions.length > 0) {
170
- return mapOptions(column, explicitOptions, storeName);
171
- }
172
-
173
- const listStoreName = resolveStoreNameFromList(column?.list);
179
+ export const buildOptionsFromColumn = (column, getOptionsForColumn = null, storeName = '') => {
180
+ const explicitOptions = getOptionsForColumn?.(column);
181
+ if (Array.isArray(explicitOptions) && explicitOptions.length > 0) {
182
+ return mapOptions(column, explicitOptions, storeName);
183
+ }
184
+
185
+ if (Array.isArray(column?.list)) {
186
+ return mapOptions(column, column.list, storeName);
187
+ }
188
+
189
+ const listStoreName = resolveStoreNameFromList(column?.list);
174
190
  if (!listStoreName) return [];
175
191
 
176
192
  const listStore = getAllStores()?.[listStoreName];
@@ -209,9 +225,9 @@ export const resolveEditValue = (column, row, value) => {
209
225
  return normalizeText(column.editFormat(rawValue, column, row, true));
210
226
  }
211
227
 
212
- if ((column?.inputType === 'date' || column?.inputType === 'date-range') && rawValue) {
213
- return normalizeText(Formatter.formatDateYmdTodmY(rawValue));
214
- }
228
+ if (isDateLikeColumn(column) && rawValue) {
229
+ return normalizeText(Formatter.formatDateYmdTodmY(rawValue));
230
+ }
215
231
 
216
232
  return normalizeText(rawValue);
217
233
  };
@@ -221,10 +237,15 @@ export const formatSaveValue = (column, value, row) => {
221
237
  return column.saveFormat(value, column, row);
222
238
  }
223
239
 
224
- if (value && typeof value === 'object') {
225
- if (value.value) return Number.isNaN(Number(value.value)) ? value.value : Number(value.value);
226
- if (value['@id']) return value['@id'];
227
- }
228
-
229
- return Number.isNaN(Number(value)) || value === '' ? value : Number(value);
230
- };
240
+ if (value && typeof value === 'object') {
241
+ if (value.value) return Number.isNaN(Number(value.value)) ? value.value : Number(value.value);
242
+ if (value['@id']) return value['@id'];
243
+ }
244
+
245
+ if (isDateLikeColumn(column) && value) {
246
+ const normalizedDate = Formatter.buildAmericanDate(String(value));
247
+ return normalizedDate || value;
248
+ }
249
+
250
+ return Number.isNaN(Number(value)) || value === '' ? value : Number(value);
251
+ };
@@ -0,0 +1,108 @@
1
+ import React from 'react';
2
+ import { Modal, ScrollView, Text, TouchableOpacity, View } from 'react-native';
3
+ import Icon from 'react-native-vector-icons/Feather';
4
+ import { useStore } from '@store';
5
+ import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
6
+ import { getColumnKey } from '../inputs/defaultInputUtils';
7
+ import {
8
+ persistVisibleColumnsPreference,
9
+ resolveDefaultTablePreferenceScope,
10
+ sanitizeVisibleColumnsPreference,
11
+ } from '../../utils/tableVisibleColumnsPreferences';
12
+ import styles from './DefaultTable.styles';
13
+ import { shouldIncludeColumn } from './DefaultTable.utils';
14
+ import useDefaultTableTheme from './useDefaultTableTheme';
15
+
16
+ const DefaultColumnMenu = ({ storeName, visible = false, onClose }) => {
17
+ const store = useStore(storeName);
18
+ const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
19
+ const visibleColumns = store?.getters?.visibleColumns || {};
20
+ const configs = store?.getters?.configs || {};
21
+ const tablePreferenceScope =
22
+ configs.tablePreferenceScope ||
23
+ resolveDefaultTablePreferenceScope({ storeName });
24
+ const availableColumns = columns.filter(column => shouldIncludeColumn(column));
25
+ const {
26
+ checkboxBorderColor: resolvedCheckboxBorderColor,
27
+ checkboxSelectedMarkColor: resolvedCheckboxSelectedMarkColor,
28
+ modalColors,
29
+ } = useDefaultTableTheme();
30
+ if (!visible) return null;
31
+
32
+ const {
33
+ backgroundColor,
34
+ borderColor,
35
+ closeIconColor,
36
+ headerTextColor,
37
+ overlayColor,
38
+ textColor,
39
+ } = modalColors;
40
+
41
+ return (
42
+ <Modal visible transparent animationType="fade" onRequestClose={onClose}>
43
+ <View style={[styles.modalOverlay, { backgroundColor: overlayColor }]}>
44
+ <View style={[styles.modalCard, styles.columnMenuModalCard, { borderColor, backgroundColor }]}>
45
+ <View style={[styles.modalHeader, { borderBottomColor: borderColor }]}>
46
+ <Text style={[styles.modalTitle, { color: headerTextColor }]} numberOfLines={1}>
47
+ {global.t?.t(storeName, 'label', 'columns')}
48
+ </Text>
49
+ <TouchableOpacity
50
+ style={[styles.modalCloseButton, { borderColor, backgroundColor }]}
51
+ activeOpacity={0.82}
52
+ onPress={onClose}
53
+ >
54
+ <Icon name="x" size={16} color={closeIconColor} />
55
+ </TouchableOpacity>
56
+ </View>
57
+ <ScrollView style={styles.columnMenuModalBody} contentContainerStyle={styles.columnMenuModalList}>
58
+ {availableColumns.map(column => {
59
+ const fieldName = getColumnKey(column);
60
+ const label = formatStoreColumnLabel({
61
+ columns,
62
+ fieldName,
63
+ fallbackLabel: column?.label || fieldName,
64
+ storeName,
65
+ });
66
+ const checked = visibleColumns[fieldName] !== false;
67
+ const toggleColumn = () => {
68
+ const nextVisibleColumns = sanitizeVisibleColumnsPreference({
69
+ columns,
70
+ visibleColumns: {
71
+ ...visibleColumns,
72
+ [fieldName]: visibleColumns[fieldName] === false,
73
+ },
74
+ });
75
+
76
+ persistVisibleColumnsPreference(tablePreferenceScope, nextVisibleColumns);
77
+
78
+ if (typeof store?.actions?.setVisibleColumns === 'function') {
79
+ store.actions.setVisibleColumns(nextVisibleColumns);
80
+ } else if (store?.getters) {
81
+ store.getters.visibleColumns = nextVisibleColumns;
82
+ }
83
+ };
84
+
85
+ return (
86
+ <TouchableOpacity
87
+ key={fieldName}
88
+ style={styles.columnMenuItem}
89
+ activeOpacity={0.82}
90
+ onPress={toggleColumn}
91
+ >
92
+ <Icon
93
+ name={checked ? 'check-square' : 'square'}
94
+ size={16}
95
+ color={checked ? resolvedCheckboxSelectedMarkColor : resolvedCheckboxBorderColor}
96
+ />
97
+ <Text style={[styles.columnMenuText, { color: textColor }]} numberOfLines={1}>{label}</Text>
98
+ </TouchableOpacity>
99
+ );
100
+ })}
101
+ </ScrollView>
102
+ </View>
103
+ </View>
104
+ </Modal>
105
+ );
106
+ };
107
+
108
+ export default DefaultColumnMenu;
@@ -0,0 +1,234 @@
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
+ import { Modal, ScrollView, Text, TouchableOpacity, View } from 'react-native';
3
+ import Icon from 'react-native-vector-icons/Feather';
4
+ import { useStore } from '@store';
5
+ import { useMessage } from '@controleonline/ui-common/src/react/components/MessageService';
6
+ import { normalizeText } from '../inputs/defaultInputUtils';
7
+ import styles from './DefaultTable.styles';
8
+ import useDefaultTableTheme from './useDefaultTableTheme';
9
+
10
+ const isObject = value =>
11
+ value !== null && typeof value === 'object' && !Array.isArray(value);
12
+
13
+ const formatDebugValue = value => {
14
+ if (value === undefined) return '';
15
+ if (typeof value === 'string') return value;
16
+
17
+ try {
18
+ return JSON.stringify(value, null, 2);
19
+ } catch (error) {
20
+ return normalizeText(value);
21
+ }
22
+ };
23
+
24
+ const copyTextToClipboard = async text => {
25
+ const normalizedText = normalizeText(text);
26
+ if (!normalizedText) return false;
27
+
28
+ if (
29
+ typeof navigator !== 'undefined' &&
30
+ navigator.clipboard &&
31
+ typeof navigator.clipboard.writeText === 'function'
32
+ ) {
33
+ await navigator.clipboard.writeText(normalizedText);
34
+ return true;
35
+ }
36
+
37
+ return false;
38
+ };
39
+
40
+ const DebugCopyButton = ({ borderColor, color, onPress }) => (
41
+ <TouchableOpacity
42
+ style={[styles.debugCopyButton, { borderColor }]}
43
+ activeOpacity={0.82}
44
+ onPress={onPress}
45
+ >
46
+ <Icon name="copy" size={13} color={color} />
47
+ <Text style={[styles.debugCopyButtonText, { color }]}>Copiar</Text>
48
+ </TouchableOpacity>
49
+ );
50
+
51
+ const DebugSection = ({ borderColor, children, copyText, onCopy, textColor, title }) => (
52
+ <>
53
+ <View style={styles.debugSectionHeader}>
54
+ <Text style={[styles.debugSectionTitle, { color: textColor }]}>{title}</Text>
55
+ <DebugCopyButton
56
+ borderColor={borderColor}
57
+ color={textColor}
58
+ onPress={() => onCopy(copyText)}
59
+ />
60
+ </View>
61
+ <Text selectable style={[styles.debugCodeBlock, { color: textColor, borderColor }]}>
62
+ {children}
63
+ </Text>
64
+ </>
65
+ );
66
+
67
+ const DefaultDebug = ({
68
+ storeName,
69
+ }) => {
70
+ const [isOpen, setIsOpen] = useState(false);
71
+ const store = useStore(storeName);
72
+ const configs = store?.getters?.configs || {};
73
+ const { showError, showSuccess } = useMessage();
74
+ const { modalColors, resolvedAccentColor, tableButtonColors } = useDefaultTableTheme();
75
+
76
+ const resolvedDebug = isObject(store?.getters?.debug) ? store.getters.debug : {};
77
+ const debugQuery = normalizeText(resolvedDebug?.query);
78
+ const debugFilledQuery = normalizeText(
79
+ resolvedDebug?.filledQuery || resolvedDebug?.interpolatedQuery,
80
+ );
81
+ const hasDebugQuery = debugQuery !== '';
82
+ const debugParameters = isObject(resolvedDebug?.parameters)
83
+ ? resolvedDebug.parameters
84
+ : configs.debugFallbackParameters || null;
85
+ const debugParametersText = useMemo(
86
+ () => formatDebugValue(debugParameters),
87
+ [debugParameters],
88
+ );
89
+ const debugCopyText = useMemo(
90
+ () =>
91
+ [
92
+ 'Query:',
93
+ debugQuery,
94
+ '',
95
+ 'Parameters:',
96
+ debugParametersText,
97
+ '',
98
+ 'Filled query:',
99
+ debugFilledQuery,
100
+ ].join('\n'),
101
+ [debugFilledQuery, debugParametersText, debugQuery],
102
+ );
103
+
104
+ const requestCopyText = useCallback(
105
+ text =>
106
+ copyTextToClipboard(text)
107
+ .then(copied => {
108
+ if (copied) {
109
+ showSuccess?.('Copiado');
110
+ return;
111
+ }
112
+
113
+ showError?.('Nao foi possivel copiar automaticamente.');
114
+ })
115
+ .catch(() => {
116
+ showError?.('Nao foi possivel copiar automaticamente.');
117
+ }),
118
+ [showError, showSuccess],
119
+ );
120
+
121
+ if (!hasDebugQuery) {
122
+ return null;
123
+ }
124
+
125
+ const {
126
+ backgroundColor,
127
+ borderColor,
128
+ closeIconColor,
129
+ headerTextColor,
130
+ overlayColor,
131
+ textColor,
132
+ } = modalColors;
133
+ const {
134
+ backgroundColor: buttonBackgroundColor,
135
+ borderColor: buttonBorderColor,
136
+ iconColor,
137
+ pressedBackgroundColor,
138
+ pressedBorderColor,
139
+ pressedIconColor,
140
+ textColor: buttonTextColor,
141
+ } = tableButtonColors;
142
+
143
+ return (
144
+ <>
145
+ <TouchableOpacity
146
+ accessibilityLabel="Debug query"
147
+ accessibilityRole="button"
148
+ style={[
149
+ styles.toolbarButton,
150
+ { borderColor: buttonBorderColor, backgroundColor: buttonBackgroundColor },
151
+ isOpen
152
+ ? { backgroundColor: pressedBackgroundColor, borderColor: pressedBorderColor }
153
+ : null,
154
+ ]}
155
+ activeOpacity={0.82}
156
+ onPress={() => setIsOpen(true)}
157
+ >
158
+ <Icon name="code" size={14} color={buttonTextColor} />
159
+ </TouchableOpacity>
160
+
161
+ {isOpen ? (
162
+ <Modal visible transparent animationType="fade" onRequestClose={() => setIsOpen(false)}>
163
+ <View style={[styles.modalOverlay, { backgroundColor: overlayColor }]}>
164
+ <View style={[styles.modalCard, styles.debugModalCard, { borderColor, backgroundColor }]}>
165
+ <View style={[styles.modalHeader, { borderBottomColor: borderColor }]}>
166
+ <Text style={[styles.modalTitle, { color: headerTextColor }]} numberOfLines={1}>
167
+ Debug query
168
+ </Text>
169
+ <TouchableOpacity
170
+ style={[styles.modalCloseButton, { borderColor, backgroundColor }]}
171
+ activeOpacity={0.82}
172
+ onPress={() => setIsOpen(false)}
173
+ >
174
+ <Icon name="x" size={16} color={closeIconColor} />
175
+ </TouchableOpacity>
176
+ </View>
177
+
178
+ <ScrollView style={styles.debugModalBody} contentContainerStyle={styles.debugModalContent}>
179
+ <DebugSection
180
+ borderColor={borderColor}
181
+ copyText={debugQuery}
182
+ onCopy={requestCopyText}
183
+ textColor={textColor}
184
+ title="Query"
185
+ >
186
+ {debugQuery}
187
+ </DebugSection>
188
+
189
+ <DebugSection
190
+ borderColor={borderColor}
191
+ copyText={debugParametersText}
192
+ onCopy={requestCopyText}
193
+ textColor={textColor}
194
+ title="Parametros"
195
+ >
196
+ {debugParametersText}
197
+ </DebugSection>
198
+
199
+ <DebugSection
200
+ borderColor={borderColor}
201
+ copyText={debugFilledQuery}
202
+ onCopy={requestCopyText}
203
+ textColor={textColor}
204
+ title="Query preenchida"
205
+ >
206
+ {debugFilledQuery}
207
+ </DebugSection>
208
+ </ScrollView>
209
+
210
+ <View style={[styles.modalActions, { borderTopColor: borderColor }]}>
211
+ <TouchableOpacity
212
+ style={[styles.secondaryButton, { borderColor }]}
213
+ activeOpacity={0.82}
214
+ onPress={() => requestCopyText(debugCopyText)}
215
+ >
216
+ <Text style={[styles.secondaryButtonText, { color: textColor }]}>Copiar tudo</Text>
217
+ </TouchableOpacity>
218
+ <TouchableOpacity
219
+ style={[styles.primaryButton, { backgroundColor: resolvedAccentColor }]}
220
+ activeOpacity={0.82}
221
+ onPress={() => setIsOpen(false)}
222
+ >
223
+ <Text style={styles.primaryButtonText}>Fechar</Text>
224
+ </TouchableOpacity>
225
+ </View>
226
+ </View>
227
+ </View>
228
+ </Modal>
229
+ ) : null}
230
+ </>
231
+ );
232
+ };
233
+
234
+ export default DefaultDebug;
@@ -0,0 +1,142 @@
1
+ import React from 'react';
2
+ import { Modal, ScrollView, Text, TouchableOpacity, View } from 'react-native';
3
+ import Icon from 'react-native-vector-icons/Feather';
4
+ import { useStore } from '@store';
5
+ import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
6
+ import DefaultColumnFilter from '../filters/DefaultColumnFilter';
7
+ import { getColumnKey, normalizeText } from '../inputs/defaultInputUtils';
8
+ import styles from './DefaultTable.styles';
9
+ import { shouldIncludeColumn } from './DefaultTable.utils';
10
+ import {
11
+ persistTableFiltersPreference,
12
+ resolveDefaultTablePreferenceScope,
13
+ sanitizeTableFiltersPreference,
14
+ } from '../../utils/tableVisibleColumnsPreferences';
15
+ import useDefaultTableTheme from './useDefaultTableTheme';
16
+
17
+ const DefaultFiltersModal = ({ storeName, visible = false, onClose }) => {
18
+ const store = useStore(storeName);
19
+ const columns = (Array.isArray(store?.getters?.columns) ? store.getters.columns : []).filter(
20
+ column =>
21
+ shouldIncludeColumn(column) &&
22
+ column?.filter !== false &&
23
+ column?.filters !== false,
24
+ );
25
+ const filters = store?.getters?.filters || {};
26
+ const configs = store?.getters?.configs || {};
27
+ const tablePreferenceScope =
28
+ configs.tablePreferenceScope ||
29
+ resolveDefaultTablePreferenceScope({ storeName });
30
+ const applyFilters = nextFilters => {
31
+ const persistedFilters = sanitizeTableFiltersPreference({
32
+ columns,
33
+ filters: nextFilters,
34
+ });
35
+
36
+ persistTableFiltersPreference(tablePreferenceScope, persistedFilters);
37
+
38
+ if (typeof store?.actions?.setFilters === 'function') {
39
+ store.actions.setFilters(nextFilters);
40
+ } else if (store?.getters) {
41
+ store.getters.filters = nextFilters;
42
+ }
43
+
44
+ configs.onFilterChange?.(nextFilters);
45
+ };
46
+ const updateFilter = (fieldName, value) => {
47
+ const nextFilters = { ...(filters || {}) };
48
+ const isEmpty =
49
+ value === null ||
50
+ value === undefined ||
51
+ normalizeText(value) === '' ||
52
+ (Array.isArray(value) && value.length === 0);
53
+
54
+ if (isEmpty) delete nextFilters[fieldName];
55
+ else nextFilters[fieldName] = value;
56
+
57
+ applyFilters(nextFilters);
58
+ };
59
+ const applyLabel = global.t?.t(storeName, 'button', 'apply');
60
+ const clearLabel = global.t?.t(storeName, 'button', 'clear');
61
+ const title = global.t?.t(storeName, 'label', 'filters');
62
+ const { modalColors, resolvedAccentColor } = useDefaultTableTheme();
63
+ if (!visible) return null;
64
+
65
+ const {
66
+ backgroundColor,
67
+ borderColor,
68
+ closeIconColor,
69
+ headerTextColor,
70
+ overlayColor,
71
+ textColor,
72
+ } = modalColors;
73
+
74
+ return (
75
+ <Modal visible transparent animationType="fade" onRequestClose={onClose}>
76
+ <View style={[styles.modalOverlay, { backgroundColor: overlayColor }]}>
77
+ <View style={[styles.modalCard, styles.filtersModalCard, { borderColor, backgroundColor }]}>
78
+ <View style={[styles.modalHeader, { borderBottomColor: borderColor }]}>
79
+ <Text style={[styles.modalTitle, { color: headerTextColor }]} numberOfLines={1}>
80
+ {title}
81
+ </Text>
82
+ <TouchableOpacity
83
+ style={[styles.modalCloseButton, { borderColor, backgroundColor }]}
84
+ activeOpacity={0.82}
85
+ onPress={onClose}
86
+ >
87
+ <Icon name="x" size={16} color={closeIconColor} />
88
+ </TouchableOpacity>
89
+ </View>
90
+ <ScrollView style={styles.filtersModalBody} contentContainerStyle={styles.filtersModalList}>
91
+ {columns.map(column => {
92
+ const fieldName = getColumnKey(column);
93
+ const label = formatStoreColumnLabel({
94
+ columns,
95
+ fieldName,
96
+ fallbackLabel: column?.label || fieldName,
97
+ storeName,
98
+ });
99
+
100
+ return (
101
+ <View key={fieldName} style={styles.filtersModalField}>
102
+ <Text style={[styles.formLabel, { color: textColor }]} numberOfLines={1}>
103
+ {label}
104
+ </Text>
105
+ <DefaultColumnFilter
106
+ column={column}
107
+ filters={filters}
108
+ onChange={updateFilter}
109
+ storeName={storeName}
110
+ style={styles.filtersModalInput}
111
+ />
112
+ </View>
113
+ );
114
+ })}
115
+ </ScrollView>
116
+ <View style={[styles.modalActions, { borderTopColor: borderColor }]}>
117
+ <TouchableOpacity
118
+ style={[styles.secondaryButton, { borderColor }]}
119
+ activeOpacity={0.82}
120
+ onPress={() => applyFilters({})}
121
+ >
122
+ <Text style={[styles.secondaryButtonText, { color: textColor }]}>
123
+ {clearLabel}
124
+ </Text>
125
+ </TouchableOpacity>
126
+ <TouchableOpacity
127
+ style={[styles.primaryButton, { backgroundColor: resolvedAccentColor }]}
128
+ activeOpacity={0.82}
129
+ onPress={onClose}
130
+ >
131
+ <Text style={styles.primaryButtonText}>
132
+ {applyLabel}
133
+ </Text>
134
+ </TouchableOpacity>
135
+ </View>
136
+ </View>
137
+ </View>
138
+ </Modal>
139
+ );
140
+ };
141
+
142
+ export default DefaultFiltersModal;
@@ -0,0 +1,18 @@
1
+ import React, { useCallback, useState } from 'react';
2
+
3
+ const DefaultModalButton = ({ children, renderButton }) => {
4
+ const [isOpen, setIsOpen] = useState(false);
5
+
6
+ const close = useCallback(() => setIsOpen(false), []);
7
+ const open = useCallback(() => setIsOpen(true), []);
8
+ const toggle = useCallback(() => setIsOpen(prev => !prev), []);
9
+
10
+ return (
11
+ <>
12
+ {renderButton?.({ close, isOpen, open, toggle })}
13
+ {children?.({ close, isOpen, open, toggle })}
14
+ </>
15
+ );
16
+ };
17
+
18
+ export default DefaultModalButton;
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import { Modal, Text, TouchableOpacity, View } from 'react-native';
3
+ import Icon from 'react-native-vector-icons/Feather';
4
+ import DefaultSearch from '../filters/DefaultSearch';
5
+ import styles from './DefaultTable.styles';
6
+ import useDefaultTableTheme from './useDefaultTableTheme';
7
+
8
+ const DefaultSearchModal = ({ storeName, visible = false, onClose }) => {
9
+ const { modalColors } = useDefaultTableTheme();
10
+ if (!visible) return null;
11
+
12
+ const {
13
+ backgroundColor,
14
+ borderColor,
15
+ closeIconColor,
16
+ headerTextColor,
17
+ overlayColor,
18
+ } = modalColors;
19
+
20
+ return (
21
+ <Modal visible transparent animationType="fade" onRequestClose={onClose}>
22
+ <View style={[styles.modalOverlay, { backgroundColor: overlayColor }]}>
23
+ <View style={[styles.modalCard, styles.searchModalCard, { borderColor, backgroundColor }]}>
24
+ <View style={[styles.modalHeader, { borderBottomColor: borderColor }]}>
25
+ <Text style={[styles.modalTitle, { color: headerTextColor }]} numberOfLines={1}>
26
+ {global.t?.t(storeName, 'label', 'search')}
27
+ </Text>
28
+ <TouchableOpacity
29
+ style={[styles.modalCloseButton, { borderColor, backgroundColor }]}
30
+ activeOpacity={0.82}
31
+ onPress={onClose}
32
+ >
33
+ <Icon name="x" size={16} color={closeIconColor} />
34
+ </TouchableOpacity>
35
+ </View>
36
+ <View style={styles.searchModalBody}>
37
+ <DefaultSearch
38
+ autoFocus
39
+ storeName={storeName}
40
+ onSearch={onClose}
41
+ />
42
+ </View>
43
+ </View>
44
+ </View>
45
+ </Modal>
46
+ );
47
+ };
48
+
49
+ export default DefaultSearchModal;