@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.
- package/AGENTS.md +3 -2
- package/package.json +21 -18
- package/src/react/components/errors/DefaultErrors.js +64 -82
- package/src/react/components/filters/CompactFilterSelector.js +58 -23
- package/src/react/components/filters/CompactFilterSelector.styles.js +17 -5
- package/src/react/components/filters/DefaultColumnFilter.js +177 -45
- package/src/react/components/filters/DefaultExternalFilters.js +201 -143
- package/src/react/components/filters/DefaultSearch.js +27 -25
- package/src/react/components/form/DefaultForm.js +72 -53
- package/src/react/components/form/DefaultForm.styles.js +71 -66
- package/src/react/components/inputs/DefaultDateInput.js +150 -0
- package/src/react/components/inputs/DefaultInput.js +44 -11
- package/src/react/components/inputs/DefaultInput.styles.js +6 -0
- package/src/react/components/inputs/DefaultSelect.js +200 -43
- package/src/react/components/inputs/defaultInputUtils.js +40 -19
- package/src/react/components/table/DefaultColumnMenu.js +108 -0
- package/src/react/components/table/DefaultDebug.js +234 -0
- package/src/react/components/table/DefaultFiltersModal.js +142 -0
- package/src/react/components/table/DefaultModalButton.js +18 -0
- package/src/react/components/table/DefaultSearchModal.js +49 -0
- package/src/react/components/table/DefaultTable.js +322 -1964
- package/src/react/components/table/DefaultTable.styles.js +234 -102
- package/src/react/components/table/DefaultTable.utils.js +502 -0
- package/src/react/components/table/DefaultTableBody.js +26 -0
- package/src/react/components/table/DefaultTableCards.js +171 -0
- package/src/react/components/table/DefaultTableControls.js +205 -0
- package/src/react/components/table/DefaultTableEmptyState.js +22 -0
- package/src/react/components/table/DefaultTableFooter.js +172 -0
- package/src/react/components/table/DefaultTableInput.js +145 -0
- package/src/react/components/table/DefaultTableRows.js +344 -0
- package/src/react/components/table/DefaultTableSearch.js +100 -0
- package/src/react/components/table/DefaultTableToolbar.js +98 -0
- package/src/react/components/table/DefaultToolbarAction.js +56 -0
- package/src/react/components/table/useDefaultTablePagination.js +370 -0
- package/src/react/components/table/useDefaultTableSorting.js +126 -0
- package/src/react/components/table/useDefaultTableTheme.js +52 -0
- package/src/react/utils/tableVisibleColumnsPreferences.js +239 -211
- package/src/store/default/actions.js +51 -13
- package/src/store/default/getters.js +10 -8
- package/src/store/default/mutation_types.js +4 -2
- package/src/store/default/mutations.js +10 -1
- package/src/tests/react/components/form/DefaultForm.test.js +81 -0
- package/src/tests/react/components/inputs/DefaultSelect.test.js +106 -14
- package/src/tests/react/components/table/DefaultTable.test.js +1318 -334
- package/src/tests/react/store/defaultActions.test.js +59 -0
- package/src/tests/react/utils/tableVisibleColumnsPreferences.test.js +247 -168
|
@@ -1,70 +1,75 @@
|
|
|
1
1
|
import { StyleSheet } from 'react-native';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
3
|
+
export const createStyles = palette =>
|
|
4
|
+
StyleSheet.create({
|
|
5
|
+
body: {
|
|
6
|
+
maxHeight: 520,
|
|
7
|
+
},
|
|
8
|
+
formGrid: {
|
|
9
|
+
padding: 14,
|
|
10
|
+
flexDirection: 'row',
|
|
11
|
+
flexWrap: 'wrap',
|
|
12
|
+
gap: 10,
|
|
13
|
+
},
|
|
14
|
+
formField: {
|
|
15
|
+
flexGrow: 1,
|
|
16
|
+
flexBasis: 220,
|
|
17
|
+
minWidth: 220,
|
|
18
|
+
},
|
|
19
|
+
emptyBox: {
|
|
20
|
+
padding: 18,
|
|
21
|
+
alignItems: 'center',
|
|
22
|
+
justifyContent: 'center',
|
|
23
|
+
},
|
|
24
|
+
emptyText: {
|
|
25
|
+
color: palette.textSecondary,
|
|
26
|
+
fontSize: 12,
|
|
27
|
+
fontWeight: '700',
|
|
28
|
+
},
|
|
29
|
+
actions: {
|
|
30
|
+
minHeight: 52,
|
|
31
|
+
paddingHorizontal: 14,
|
|
32
|
+
borderTopWidth: 1,
|
|
33
|
+
borderTopColor: palette.dividerBorder,
|
|
34
|
+
flexDirection: 'row',
|
|
35
|
+
alignItems: 'center',
|
|
36
|
+
justifyContent: 'flex-end',
|
|
37
|
+
gap: 8,
|
|
38
|
+
},
|
|
39
|
+
secondaryButton: {
|
|
40
|
+
height: 34,
|
|
41
|
+
borderRadius: 8,
|
|
42
|
+
borderWidth: 1,
|
|
43
|
+
borderColor: palette.buttonBorderSecondary,
|
|
44
|
+
paddingHorizontal: 14,
|
|
45
|
+
alignItems: 'center',
|
|
46
|
+
justifyContent: 'center',
|
|
47
|
+
backgroundColor: palette.buttonBackgroundSecondary,
|
|
48
|
+
},
|
|
49
|
+
secondaryButtonText: {
|
|
50
|
+
color: palette.buttonTextSecondary,
|
|
51
|
+
fontSize: 12,
|
|
52
|
+
fontWeight: '800',
|
|
53
|
+
},
|
|
54
|
+
primaryButton: {
|
|
55
|
+
height: 34,
|
|
56
|
+
borderRadius: 8,
|
|
57
|
+
paddingHorizontal: 16,
|
|
58
|
+
alignItems: 'center',
|
|
59
|
+
justifyContent: 'center',
|
|
60
|
+
},
|
|
61
|
+
primaryButtonDisabled: {
|
|
62
|
+
backgroundColor: palette.buttonDisabledBackground,
|
|
63
|
+
},
|
|
64
|
+
primaryButtonText: {
|
|
65
|
+
color: palette.buttonText,
|
|
66
|
+
fontSize: 12,
|
|
67
|
+
fontWeight: '900',
|
|
68
|
+
},
|
|
69
|
+
primaryButtonTextDisabled: {
|
|
70
|
+
color: palette.buttonDisabledText,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
69
73
|
|
|
74
|
+
const styles = createStyles({});
|
|
70
75
|
export default styles;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { Text, TextInput, TouchableOpacity, View } from 'react-native';
|
|
3
|
+
import Icon from 'react-native-vector-icons/Feather';
|
|
4
|
+
import Formatter from '@controleonline/ui-common/src/utils/formatter.js';
|
|
5
|
+
import {
|
|
6
|
+
getColumnKey,
|
|
7
|
+
isEditableColumn,
|
|
8
|
+
normalizeText,
|
|
9
|
+
resolveCellText,
|
|
10
|
+
resolveEditValue,
|
|
11
|
+
} from './defaultInputUtils';
|
|
12
|
+
import styles from './DefaultInput.styles';
|
|
13
|
+
|
|
14
|
+
const normalizeDateDraft = value => {
|
|
15
|
+
const text = normalizeText(value);
|
|
16
|
+
if (!text) return '';
|
|
17
|
+
|
|
18
|
+
return Formatter.formatDateToBR(text);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const DefaultDateInput = ({
|
|
22
|
+
accentColor = '#2563EB',
|
|
23
|
+
autoFocus = true,
|
|
24
|
+
autoSave = true,
|
|
25
|
+
column,
|
|
26
|
+
columns = [],
|
|
27
|
+
containerStyle = null,
|
|
28
|
+
displayValue,
|
|
29
|
+
editing = false,
|
|
30
|
+
inputStyle = null,
|
|
31
|
+
label = '',
|
|
32
|
+
numberOfLines = 1,
|
|
33
|
+
onCancelEditing = null,
|
|
34
|
+
onChangeValue = null,
|
|
35
|
+
onSave = null,
|
|
36
|
+
onStartEditing = null,
|
|
37
|
+
readTextStyle = null,
|
|
38
|
+
row = {},
|
|
39
|
+
saving = false,
|
|
40
|
+
showLabel = false,
|
|
41
|
+
storeName = '',
|
|
42
|
+
value,
|
|
43
|
+
variant = 'cell',
|
|
44
|
+
}) => {
|
|
45
|
+
const fieldName = getColumnKey(column);
|
|
46
|
+
const isForm = variant === 'form';
|
|
47
|
+
const canEdit = isEditableColumn(column) && typeof onStartEditing === 'function';
|
|
48
|
+
const editValue = useMemo(
|
|
49
|
+
() => normalizeDateDraft(value ?? resolveEditValue(column, row)),
|
|
50
|
+
[column, row, value],
|
|
51
|
+
);
|
|
52
|
+
const [draftValue, setDraftValue] = useState(editValue);
|
|
53
|
+
const [validationMessage, setValidationMessage] = useState('');
|
|
54
|
+
const resolvedLabel =
|
|
55
|
+
displayValue ?? resolveCellText({ column, columns, row, storeName, value });
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
setDraftValue(editValue);
|
|
59
|
+
setValidationMessage('');
|
|
60
|
+
}, [editValue, editing]);
|
|
61
|
+
|
|
62
|
+
const saveDraft = () => {
|
|
63
|
+
const validationResult = Formatter.validateBRDate(draftValue);
|
|
64
|
+
|
|
65
|
+
if (validationResult !== true) {
|
|
66
|
+
setValidationMessage(validationResult);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setValidationMessage('');
|
|
71
|
+
|
|
72
|
+
if (autoSave) {
|
|
73
|
+
onSave?.(draftValue);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onChangeValue?.(draftValue);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const updateDraft = nextValue => {
|
|
81
|
+
setDraftValue(nextValue);
|
|
82
|
+
if (validationMessage) setValidationMessage('');
|
|
83
|
+
if (!autoSave) onChangeValue?.(nextValue);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
if (!editing && !isForm) {
|
|
87
|
+
return (
|
|
88
|
+
<View style={[styles.wrap, containerStyle]}>
|
|
89
|
+
{showLabel ? <Text style={styles.fieldLabel}>{label || column?.label || fieldName}</Text> : null}
|
|
90
|
+
<TouchableOpacity
|
|
91
|
+
style={styles.readButton}
|
|
92
|
+
activeOpacity={canEdit ? 0.78 : 1}
|
|
93
|
+
disabled={!canEdit}
|
|
94
|
+
onPress={() => onStartEditing?.()}
|
|
95
|
+
>
|
|
96
|
+
<Text
|
|
97
|
+
style={[
|
|
98
|
+
styles.readText,
|
|
99
|
+
resolvedLabel === '-' ? styles.mutedText : null,
|
|
100
|
+
readTextStyle,
|
|
101
|
+
]}
|
|
102
|
+
numberOfLines={numberOfLines}
|
|
103
|
+
>
|
|
104
|
+
{resolvedLabel || '-'}
|
|
105
|
+
</Text>
|
|
106
|
+
{saving ? (
|
|
107
|
+
<Text style={[styles.savingText, { color: accentColor }]}>Salvando</Text>
|
|
108
|
+
) : canEdit ? (
|
|
109
|
+
<Icon style={styles.editIcon} name="calendar" size={13} color="#64748B" />
|
|
110
|
+
) : null}
|
|
111
|
+
</TouchableOpacity>
|
|
112
|
+
</View>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<View style={[styles.wrap, containerStyle]}>
|
|
118
|
+
{showLabel ? <Text style={styles.fieldLabel}>{label || column?.label || fieldName}</Text> : null}
|
|
119
|
+
<View style={styles.editingWrap}>
|
|
120
|
+
<View style={styles.editingRow}>
|
|
121
|
+
<TextInput
|
|
122
|
+
style={[
|
|
123
|
+
styles.input,
|
|
124
|
+
isForm ? styles.formInput : null,
|
|
125
|
+
inputStyle,
|
|
126
|
+
]}
|
|
127
|
+
value={draftValue}
|
|
128
|
+
keyboardType="numeric"
|
|
129
|
+
placeholder="DD/MM/YYYY"
|
|
130
|
+
onBlur={autoSave ? saveDraft : undefined}
|
|
131
|
+
onChangeText={updateDraft}
|
|
132
|
+
onSubmitEditing={saveDraft}
|
|
133
|
+
autoFocus={autoFocus && !isForm}
|
|
134
|
+
selectTextOnFocus
|
|
135
|
+
/>
|
|
136
|
+
<Icon name="calendar" size={15} color="#64748B" />
|
|
137
|
+
{!isForm ? (
|
|
138
|
+
<TouchableOpacity style={styles.cancelButton} activeOpacity={0.8} onPress={onCancelEditing}>
|
|
139
|
+
<Icon name="x" size={14} color="#64748B" />
|
|
140
|
+
</TouchableOpacity>
|
|
141
|
+
) : null}
|
|
142
|
+
</View>
|
|
143
|
+
{!!validationMessage && <Text style={styles.validationText}>{validationMessage}</Text>}
|
|
144
|
+
{saving ? <Text style={styles.savingText}>Salvando</Text> : null}
|
|
145
|
+
</View>
|
|
146
|
+
</View>
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export default DefaultDateInput;
|
|
@@ -2,13 +2,15 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|
|
2
2
|
import { Text, TextInput, TouchableOpacity, View } from 'react-native';
|
|
3
3
|
import Icon from 'react-native-vector-icons/Feather';
|
|
4
4
|
import {
|
|
5
|
-
getColumnKey,
|
|
6
|
-
|
|
5
|
+
getColumnKey,
|
|
6
|
+
isDateLikeColumn,
|
|
7
|
+
isEditableColumn,
|
|
7
8
|
normalizeText,
|
|
8
9
|
resolveCellText,
|
|
9
10
|
resolveEditValue,
|
|
10
|
-
} from './defaultInputUtils';
|
|
11
|
-
import
|
|
11
|
+
} from './defaultInputUtils';
|
|
12
|
+
import DefaultDateInput from './DefaultDateInput';
|
|
13
|
+
import DefaultSelect from './DefaultSelect';
|
|
12
14
|
import styles from './DefaultInput.styles';
|
|
13
15
|
|
|
14
16
|
const DefaultInput = ({
|
|
@@ -24,11 +26,12 @@ const DefaultInput = ({
|
|
|
24
26
|
inputStyle = null,
|
|
25
27
|
label = '',
|
|
26
28
|
numberOfLines = 1,
|
|
27
|
-
onBeforeOpen = null,
|
|
28
|
-
onCancelEditing = null,
|
|
29
|
-
onChangeValue = null,
|
|
30
|
-
onSave = null,
|
|
31
|
-
|
|
29
|
+
onBeforeOpen = null,
|
|
30
|
+
onCancelEditing = null,
|
|
31
|
+
onChangeValue = null,
|
|
32
|
+
onSave = null,
|
|
33
|
+
onSearchChange = null,
|
|
34
|
+
onStartEditing = null,
|
|
32
35
|
readTextStyle = null,
|
|
33
36
|
row = {},
|
|
34
37
|
saving = false,
|
|
@@ -52,7 +55,7 @@ const DefaultInput = ({
|
|
|
52
55
|
setDraftValue(editValue);
|
|
53
56
|
}, [editValue, editing]);
|
|
54
57
|
|
|
55
|
-
if (column?.list) {
|
|
58
|
+
if (column?.list) {
|
|
56
59
|
return (
|
|
57
60
|
<DefaultSelect
|
|
58
61
|
accentColor={accentColor}
|
|
@@ -79,7 +82,37 @@ const DefaultInput = ({
|
|
|
79
82
|
variant={variant}
|
|
80
83
|
/>
|
|
81
84
|
);
|
|
82
|
-
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (isDateLikeColumn(column)) {
|
|
88
|
+
return (
|
|
89
|
+
<DefaultDateInput
|
|
90
|
+
accentColor={accentColor}
|
|
91
|
+
autoFocus={autoFocus}
|
|
92
|
+
autoSave={autoSave}
|
|
93
|
+
column={column}
|
|
94
|
+
columns={columns}
|
|
95
|
+
containerStyle={containerStyle}
|
|
96
|
+
displayValue={displayValue}
|
|
97
|
+
editing={editing}
|
|
98
|
+
inputStyle={inputStyle}
|
|
99
|
+
label={label}
|
|
100
|
+
numberOfLines={numberOfLines}
|
|
101
|
+
onCancelEditing={onCancelEditing}
|
|
102
|
+
onChangeValue={onChangeValue}
|
|
103
|
+
onSave={onSave}
|
|
104
|
+
onSearchChange={onSearchChange}
|
|
105
|
+
onStartEditing={onStartEditing}
|
|
106
|
+
readTextStyle={readTextStyle}
|
|
107
|
+
row={row}
|
|
108
|
+
saving={saving}
|
|
109
|
+
showLabel={showLabel}
|
|
110
|
+
storeName={storeName}
|
|
111
|
+
value={value}
|
|
112
|
+
variant={variant}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
83
116
|
|
|
84
117
|
const saveDraft = () => {
|
|
85
118
|
if (autoSave) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useMemo, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Modal,
|
|
4
4
|
ScrollView,
|
|
@@ -7,18 +7,87 @@ import {
|
|
|
7
7
|
TouchableOpacity,
|
|
8
8
|
View,
|
|
9
9
|
} from 'react-native';
|
|
10
|
-
import Icon from 'react-native-vector-icons/Feather';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
import Icon from 'react-native-vector-icons/Feather';
|
|
11
|
+
import { getAllStores, useStore } from '@store';
|
|
12
|
+
import {
|
|
13
|
+
buildOptionsFromColumn,
|
|
14
|
+
getColumnKey,
|
|
15
|
+
isEditableColumn,
|
|
16
|
+
mapOptions,
|
|
17
|
+
normalizeOptionKey,
|
|
18
|
+
normalizeText,
|
|
19
|
+
resolveCellText,
|
|
20
|
+
resolveEditValue,
|
|
21
|
+
resolveStoreNameFromList,
|
|
22
|
+
} from './defaultInputUtils';
|
|
23
|
+
import inputStyles from './DefaultInput.styles';
|
|
24
|
+
import styles from './DefaultSelect.styles';
|
|
25
|
+
|
|
26
|
+
const COMPANY_SCOPED_LIST_STORES = new Set([
|
|
27
|
+
'categories',
|
|
28
|
+
'paymentType',
|
|
29
|
+
'wallet',
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const normalizeCollectionItems = response => {
|
|
33
|
+
if (Array.isArray(response)) return response;
|
|
34
|
+
if (Array.isArray(response?.member)) return response.member;
|
|
35
|
+
if (Array.isArray(response?.['hydra:member'])) return response['hydra:member'];
|
|
36
|
+
return [];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const stableSerialize = value => {
|
|
40
|
+
if (value === null || value === undefined) return 'null';
|
|
41
|
+
if (Array.isArray(value)) return `[${value.map(item => stableSerialize(item)).join(',')}]`;
|
|
42
|
+
if (value instanceof Date) return JSON.stringify(value.toISOString());
|
|
43
|
+
if (value && typeof value === 'object') {
|
|
44
|
+
return `{${Object.keys(value)
|
|
45
|
+
.sort()
|
|
46
|
+
.map(key => `${JSON.stringify(key)}:${stableSerialize(value[key])}`)
|
|
47
|
+
.join(',')}}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return JSON.stringify(value);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const resolveListActionName = list =>
|
|
54
|
+
normalizeText(list).split('/')[1] || 'getItems';
|
|
55
|
+
|
|
56
|
+
const resolveCompanyScopedParams = ({ currentCompanyId, listStoreName }) => {
|
|
57
|
+
if (!currentCompanyId || !COMPANY_SCOPED_LIST_STORES.has(listStoreName)) {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return listStoreName === 'categories'
|
|
62
|
+
? { company: currentCompanyId }
|
|
63
|
+
: { people: currentCompanyId };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const resolveColumnListLoadParams = ({
|
|
67
|
+
column,
|
|
68
|
+
currentCompanyId,
|
|
69
|
+
requestParams = {},
|
|
70
|
+
searchValue = '',
|
|
71
|
+
}) => {
|
|
72
|
+
const listStoreName = resolveStoreNameFromList(column?.list);
|
|
73
|
+
const resolvedCustomParams = typeof column?.listRequestParams === 'function'
|
|
74
|
+
? column.listRequestParams({ currentCompanyId, requestParams })
|
|
75
|
+
: column?.listRequestParams;
|
|
76
|
+
const customParams =
|
|
77
|
+
resolvedCustomParams &&
|
|
78
|
+
typeof resolvedCustomParams === 'object' &&
|
|
79
|
+
!Array.isArray(resolvedCustomParams)
|
|
80
|
+
? resolvedCustomParams
|
|
81
|
+
: {};
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
...resolveCompanyScopedParams({ currentCompanyId, listStoreName }),
|
|
85
|
+
...customParams,
|
|
86
|
+
...(normalizeText(searchValue)
|
|
87
|
+
? { [column?.listSearchParam || column?.searchParam || 'search']: normalizeText(searchValue) }
|
|
88
|
+
: {}),
|
|
89
|
+
};
|
|
90
|
+
};
|
|
22
91
|
|
|
23
92
|
const DefaultSelect = ({
|
|
24
93
|
accentColor = '#2563EB',
|
|
@@ -32,10 +101,11 @@ const DefaultSelect = ({
|
|
|
32
101
|
label = '',
|
|
33
102
|
numberOfLines = 1,
|
|
34
103
|
onBeforeOpen = null,
|
|
35
|
-
onCancelEditing = null,
|
|
36
|
-
onChangeValue = null,
|
|
37
|
-
onSave = null,
|
|
38
|
-
|
|
104
|
+
onCancelEditing = null,
|
|
105
|
+
onChangeValue = null,
|
|
106
|
+
onSave = null,
|
|
107
|
+
onSearchChange = null,
|
|
108
|
+
onStartEditing = null,
|
|
39
109
|
readTextStyle = null,
|
|
40
110
|
row = {},
|
|
41
111
|
saving = false,
|
|
@@ -43,16 +113,32 @@ const DefaultSelect = ({
|
|
|
43
113
|
storeName = '',
|
|
44
114
|
value,
|
|
45
115
|
variant = 'cell',
|
|
46
|
-
}) => {
|
|
47
|
-
const
|
|
48
|
-
const
|
|
116
|
+
}) => {
|
|
117
|
+
const peopleStore = useStore('people');
|
|
118
|
+
const ownerStore = useStore(storeName);
|
|
119
|
+
const loadedListKeysRef = useRef(new Set());
|
|
120
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
121
|
+
const [loadedItems, setLoadedItems] = useState([]);
|
|
122
|
+
const [searchText, setSearchText] = useState('');
|
|
123
|
+
const currentCompanyId = peopleStore?.getters?.currentCompany?.id;
|
|
124
|
+
const requestParams = ownerStore?.getters?.configs?.requestParams || {};
|
|
49
125
|
|
|
50
126
|
const fieldName = getColumnKey(column);
|
|
51
127
|
const selectedKey = normalizeOptionKey(value ?? resolveEditValue(column, row));
|
|
52
|
-
const options = useMemo(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
128
|
+
const options = useMemo(() => {
|
|
129
|
+
const localOptions = buildOptionsFromColumn(column, getOptionsForColumn, storeName);
|
|
130
|
+
const remoteOptions = mapOptions(column, loadedItems, storeName);
|
|
131
|
+
|
|
132
|
+
if (remoteOptions.length === 0) {
|
|
133
|
+
return localOptions;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const seenKeys = new Set(remoteOptions.map(option => option.key));
|
|
137
|
+
return [
|
|
138
|
+
...remoteOptions,
|
|
139
|
+
...localOptions.filter(option => !seenKeys.has(option.key)),
|
|
140
|
+
];
|
|
141
|
+
}, [column, getOptionsForColumn, loadedItems, storeName]);
|
|
56
142
|
const selected = options.find(option => option.key === selectedKey);
|
|
57
143
|
const resolvedLabel =
|
|
58
144
|
displayValue ??
|
|
@@ -60,14 +146,82 @@ const DefaultSelect = ({
|
|
|
60
146
|
resolveCellText({ column, columns, row, storeName, value });
|
|
61
147
|
const canEdit = isEditableColumn(column) && typeof onStartEditing === 'function';
|
|
62
148
|
const isForm = variant === 'form';
|
|
63
|
-
const filteredOptions = useMemo(() => {
|
|
64
|
-
const query = normalizeText(searchText).toLowerCase();
|
|
65
|
-
if (!query) return options;
|
|
66
|
-
return options.filter(option =>
|
|
149
|
+
const filteredOptions = useMemo(() => {
|
|
150
|
+
const query = normalizeText(searchText).toLowerCase();
|
|
151
|
+
if (!query || onSearchChange) return options;
|
|
152
|
+
return options.filter(option =>
|
|
67
153
|
normalizeText(option.label).toLowerCase().includes(query) ||
|
|
68
154
|
normalizeText(option.key).toLowerCase().includes(query),
|
|
69
155
|
);
|
|
70
|
-
}, [options, searchText]);
|
|
156
|
+
}, [options, searchText]);
|
|
157
|
+
|
|
158
|
+
const applyLoadedItems = useCallback(value => {
|
|
159
|
+
const nextItems = normalizeCollectionItems(value);
|
|
160
|
+
if (nextItems.length > 0 || Array.isArray(value)) {
|
|
161
|
+
setLoadedItems(nextItems);
|
|
162
|
+
}
|
|
163
|
+
}, []);
|
|
164
|
+
|
|
165
|
+
const loadRemoteOptions = useCallback((searchValue = '') => {
|
|
166
|
+
const listStoreName = resolveStoreNameFromList(column?.list);
|
|
167
|
+
const actionName = resolveListActionName(column?.list);
|
|
168
|
+
const stores = getAllStores?.() || {};
|
|
169
|
+
const listStore = stores?.[listStoreName];
|
|
170
|
+
const listAction = listStore?.actions?.[actionName];
|
|
171
|
+
|
|
172
|
+
if (!listStoreName || typeof listAction !== 'function') {
|
|
173
|
+
return Promise.resolve([]);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const listLoadParams = resolveColumnListLoadParams({
|
|
177
|
+
column,
|
|
178
|
+
currentCompanyId,
|
|
179
|
+
requestParams,
|
|
180
|
+
searchValue,
|
|
181
|
+
});
|
|
182
|
+
const loadKey = `${getColumnKey(column)}:${listStoreName}:${actionName}:${stableSerialize(listLoadParams)}`;
|
|
183
|
+
|
|
184
|
+
if (
|
|
185
|
+
!normalizeText(searchValue) &&
|
|
186
|
+
loadedListKeysRef.current.has(loadKey) &&
|
|
187
|
+
loadedItems.length > 0
|
|
188
|
+
) {
|
|
189
|
+
return Promise.resolve(loadedItems);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!normalizeText(searchValue)) {
|
|
193
|
+
loadedListKeysRef.current.add(loadKey);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return Promise.resolve(
|
|
197
|
+
listAction({
|
|
198
|
+
...listLoadParams,
|
|
199
|
+
__storeMeta: {
|
|
200
|
+
dedupeKey: `default-select-list-options:${loadKey}`,
|
|
201
|
+
skipSystemError: true,
|
|
202
|
+
},
|
|
203
|
+
}),
|
|
204
|
+
)
|
|
205
|
+
.then(response => {
|
|
206
|
+
applyLoadedItems(response);
|
|
207
|
+
return response;
|
|
208
|
+
})
|
|
209
|
+
.catch(error => {
|
|
210
|
+
loadedListKeysRef.current.delete(loadKey);
|
|
211
|
+
throw error;
|
|
212
|
+
});
|
|
213
|
+
}, [applyLoadedItems, column, currentCompanyId, loadedItems, requestParams]);
|
|
214
|
+
|
|
215
|
+
const handleSearchChange = useCallback(value => {
|
|
216
|
+
setSearchText(value);
|
|
217
|
+
const maybeLoad = onSearchChange ? onSearchChange(value) : loadRemoteOptions(value);
|
|
218
|
+
if (maybeLoad && typeof maybeLoad.then === 'function') {
|
|
219
|
+
maybeLoad.then(applyLoadedItems).catch(() => {});
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
applyLoadedItems(maybeLoad);
|
|
224
|
+
}, [applyLoadedItems, loadRemoteOptions, onSearchChange]);
|
|
71
225
|
|
|
72
226
|
const close = () => {
|
|
73
227
|
setIsOpen(false);
|
|
@@ -105,15 +259,18 @@ const DefaultSelect = ({
|
|
|
105
259
|
}
|
|
106
260
|
}
|
|
107
261
|
|
|
108
|
-
const maybeLoad = onBeforeOpen?.();
|
|
109
|
-
if (maybeLoad && typeof maybeLoad.then === 'function') {
|
|
110
|
-
await maybeLoad;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
262
|
+
const maybeLoad = onBeforeOpen?.();
|
|
263
|
+
if (maybeLoad && typeof maybeLoad.then === 'function') {
|
|
264
|
+
applyLoadedItems(await maybeLoad);
|
|
265
|
+
} else {
|
|
266
|
+
applyLoadedItems(maybeLoad);
|
|
267
|
+
}
|
|
268
|
+
} catch (error) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
setIsOpen(true);
|
|
272
|
+
loadRemoteOptions().catch(() => {});
|
|
273
|
+
};
|
|
117
274
|
|
|
118
275
|
return (
|
|
119
276
|
<View style={[inputStyles.wrap, containerStyle]}>
|
|
@@ -156,11 +313,11 @@ const DefaultSelect = ({
|
|
|
156
313
|
|
|
157
314
|
<View style={styles.searchBox}>
|
|
158
315
|
<TextInput
|
|
159
|
-
style={[inputStyles.input, inputStyles.formInput]}
|
|
160
|
-
value={searchText}
|
|
161
|
-
placeholder={global.t?.t(storeName, 'input', column?.searchParam || 'search')}
|
|
162
|
-
onChangeText={
|
|
163
|
-
/>
|
|
316
|
+
style={[inputStyles.input, inputStyles.formInput]}
|
|
317
|
+
value={searchText}
|
|
318
|
+
placeholder={global.t?.t(storeName, 'input', column?.searchParam || 'search')}
|
|
319
|
+
onChangeText={handleSearchChange}
|
|
320
|
+
/>
|
|
164
321
|
</View>
|
|
165
322
|
|
|
166
323
|
<ScrollView keyboardShouldPersistTaps="handled">
|