@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
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text, TouchableOpacity } from 'react-native';
|
|
3
|
+
import Icon from 'react-native-vector-icons/Feather';
|
|
4
|
+
import { useStore } from '@store';
|
|
5
|
+
import DefaultColumnMenu from './DefaultColumnMenu';
|
|
6
|
+
import DefaultDebug from './DefaultDebug';
|
|
7
|
+
import DefaultFiltersModal from './DefaultFiltersModal';
|
|
8
|
+
import DefaultModalButton from './DefaultModalButton';
|
|
9
|
+
import styles from './DefaultTable.styles';
|
|
10
|
+
import { normalizeText } from '../inputs/defaultInputUtils';
|
|
11
|
+
import {
|
|
12
|
+
persistTableViewModePreference,
|
|
13
|
+
resolveDefaultTablePreferenceScope,
|
|
14
|
+
} from '../../utils/tableVisibleColumnsPreferences';
|
|
15
|
+
import { shouldIncludeColumn } from './DefaultTable.utils';
|
|
16
|
+
import useDefaultTableTheme from './useDefaultTableTheme';
|
|
17
|
+
|
|
18
|
+
const DefaultTableControls = ({ storeName }) => {
|
|
19
|
+
const store = useStore(storeName);
|
|
20
|
+
const configs = store?.getters?.configs || {};
|
|
21
|
+
const effectiveViewMode =
|
|
22
|
+
configs.effectiveViewMode ||
|
|
23
|
+
configs.viewMode ||
|
|
24
|
+
configs.initialViewMode ||
|
|
25
|
+
'table';
|
|
26
|
+
const tablePreferenceScope =
|
|
27
|
+
configs.tablePreferenceScope ||
|
|
28
|
+
resolveDefaultTablePreferenceScope({ storeName });
|
|
29
|
+
const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
|
|
30
|
+
const filters = store?.getters?.filters || {};
|
|
31
|
+
const hasFilterableColumns = columns.some(
|
|
32
|
+
column =>
|
|
33
|
+
configs.showColumnFiltersButton !== false &&
|
|
34
|
+
shouldIncludeColumn(column) &&
|
|
35
|
+
column?.filter !== false &&
|
|
36
|
+
column?.filters !== false,
|
|
37
|
+
);
|
|
38
|
+
const tableFiltersVisible = Boolean(configs.tableFiltersVisible);
|
|
39
|
+
const activeFilterCount = Object.values(filters).filter(value => normalizeText(value) !== '').length;
|
|
40
|
+
const addConfig = store?.getters?.add;
|
|
41
|
+
const shouldRenderAddButton =
|
|
42
|
+
(addConfig === true || configs.add === true) &&
|
|
43
|
+
(typeof configs.onAdd === 'function' || typeof store?.actions?.save === 'function');
|
|
44
|
+
const { tableButtonColors } = useDefaultTableTheme();
|
|
45
|
+
const {
|
|
46
|
+
backgroundColor,
|
|
47
|
+
borderColor,
|
|
48
|
+
iconColor,
|
|
49
|
+
pressedBackgroundColor,
|
|
50
|
+
pressedBorderColor,
|
|
51
|
+
pressedIconColor,
|
|
52
|
+
textColor,
|
|
53
|
+
} = tableButtonColors;
|
|
54
|
+
|
|
55
|
+
const buttonStyle = [
|
|
56
|
+
styles.toolbarButton,
|
|
57
|
+
{ borderColor, backgroundColor },
|
|
58
|
+
];
|
|
59
|
+
const pressedStyle = {
|
|
60
|
+
backgroundColor: pressedBackgroundColor,
|
|
61
|
+
borderColor: pressedBorderColor,
|
|
62
|
+
};
|
|
63
|
+
const updateConfigs = nextConfigs => {
|
|
64
|
+
if (typeof store?.actions?.setConfigs === 'function') {
|
|
65
|
+
store.actions.setConfigs(nextConfigs);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (store?.getters) {
|
|
70
|
+
store.getters.configs = nextConfigs;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const nextViewMode = effectiveViewMode === 'table' ? 'cards' : 'table';
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<>
|
|
77
|
+
<DefaultDebug storeName={storeName} />
|
|
78
|
+
{hasFilterableColumns && effectiveViewMode === 'table' ? (
|
|
79
|
+
<TouchableOpacity
|
|
80
|
+
style={[
|
|
81
|
+
...buttonStyle,
|
|
82
|
+
tableFiltersVisible ? pressedStyle : null,
|
|
83
|
+
]}
|
|
84
|
+
activeOpacity={0.82}
|
|
85
|
+
onPress={() =>
|
|
86
|
+
updateConfigs({
|
|
87
|
+
...configs,
|
|
88
|
+
tableFiltersVisible: !tableFiltersVisible,
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
>
|
|
92
|
+
<Icon
|
|
93
|
+
name="filter"
|
|
94
|
+
size={14}
|
|
95
|
+
color={tableFiltersVisible ? pressedIconColor : textColor}
|
|
96
|
+
/>
|
|
97
|
+
{activeFilterCount > 0 ? (
|
|
98
|
+
<Text
|
|
99
|
+
style={[
|
|
100
|
+
styles.toolbarBadgeText,
|
|
101
|
+
{ color: tableFiltersVisible ? pressedIconColor : textColor },
|
|
102
|
+
]}
|
|
103
|
+
>
|
|
104
|
+
{activeFilterCount}
|
|
105
|
+
</Text>
|
|
106
|
+
) : null}
|
|
107
|
+
</TouchableOpacity>
|
|
108
|
+
) : null}
|
|
109
|
+
{hasFilterableColumns && effectiveViewMode === 'cards' ? (
|
|
110
|
+
<DefaultModalButton
|
|
111
|
+
renderButton={({ isOpen, open }) => (
|
|
112
|
+
<TouchableOpacity
|
|
113
|
+
style={[
|
|
114
|
+
...buttonStyle,
|
|
115
|
+
isOpen ? pressedStyle : null,
|
|
116
|
+
]}
|
|
117
|
+
activeOpacity={0.82}
|
|
118
|
+
onPress={open}
|
|
119
|
+
>
|
|
120
|
+
<Icon
|
|
121
|
+
name="filter"
|
|
122
|
+
size={14}
|
|
123
|
+
color={isOpen ? pressedIconColor : textColor}
|
|
124
|
+
/>
|
|
125
|
+
{activeFilterCount > 0 ? (
|
|
126
|
+
<Text
|
|
127
|
+
style={[
|
|
128
|
+
styles.toolbarBadgeText,
|
|
129
|
+
{ color: isOpen ? pressedIconColor : textColor },
|
|
130
|
+
]}>
|
|
131
|
+
{activeFilterCount}
|
|
132
|
+
</Text>
|
|
133
|
+
) : null}
|
|
134
|
+
</TouchableOpacity>
|
|
135
|
+
)}
|
|
136
|
+
>
|
|
137
|
+
{({ close, isOpen }) => (
|
|
138
|
+
<DefaultFiltersModal
|
|
139
|
+
storeName={storeName}
|
|
140
|
+
visible={isOpen}
|
|
141
|
+
onClose={close}
|
|
142
|
+
/>
|
|
143
|
+
)}
|
|
144
|
+
</DefaultModalButton>
|
|
145
|
+
) : null}
|
|
146
|
+
<TouchableOpacity
|
|
147
|
+
style={buttonStyle}
|
|
148
|
+
activeOpacity={0.82}
|
|
149
|
+
onPress={() => {
|
|
150
|
+
const nextConfigs = {
|
|
151
|
+
...configs,
|
|
152
|
+
effectiveViewMode: nextViewMode,
|
|
153
|
+
viewMode: nextViewMode,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
persistTableViewModePreference(tablePreferenceScope, nextConfigs.viewMode);
|
|
157
|
+
updateConfigs(nextConfigs);
|
|
158
|
+
}}
|
|
159
|
+
>
|
|
160
|
+
<Icon
|
|
161
|
+
name={nextViewMode === 'cards' ? 'grid' : 'list'}
|
|
162
|
+
size={14}
|
|
163
|
+
color={textColor}
|
|
164
|
+
/>
|
|
165
|
+
</TouchableOpacity>
|
|
166
|
+
<DefaultModalButton
|
|
167
|
+
renderButton={({ isOpen, toggle }) => (
|
|
168
|
+
<TouchableOpacity
|
|
169
|
+
style={[
|
|
170
|
+
...buttonStyle,
|
|
171
|
+
isOpen ? pressedStyle : null,
|
|
172
|
+
]}
|
|
173
|
+
activeOpacity={0.82}
|
|
174
|
+
onPress={toggle}
|
|
175
|
+
>
|
|
176
|
+
<Icon name="columns" size={14} color={isOpen ? pressedIconColor : textColor} />
|
|
177
|
+
</TouchableOpacity>
|
|
178
|
+
)}
|
|
179
|
+
>
|
|
180
|
+
{({ close, isOpen }) => (
|
|
181
|
+
<DefaultColumnMenu
|
|
182
|
+
storeName={storeName}
|
|
183
|
+
visible={isOpen}
|
|
184
|
+
onClose={close}
|
|
185
|
+
/>
|
|
186
|
+
)}
|
|
187
|
+
</DefaultModalButton>
|
|
188
|
+
{shouldRenderAddButton ? (
|
|
189
|
+
<TouchableOpacity
|
|
190
|
+
style={[
|
|
191
|
+
styles.toolbarButton,
|
|
192
|
+
styles.toolbarAddButton,
|
|
193
|
+
{ backgroundColor: backgroundColor, borderColor: backgroundColor },
|
|
194
|
+
]}
|
|
195
|
+
activeOpacity={0.85}
|
|
196
|
+
onPress={() => configs.onAdd?.()}
|
|
197
|
+
>
|
|
198
|
+
<Icon name="plus" size={16} color={textColor} />
|
|
199
|
+
</TouchableOpacity>
|
|
200
|
+
) : null}
|
|
201
|
+
</>
|
|
202
|
+
);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export default DefaultTableControls;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text, View } from 'react-native';
|
|
3
|
+
import StateStore from '@controleonline/ui-common/src/react/components/StateStore';
|
|
4
|
+
import styles from './DefaultTable.styles';
|
|
5
|
+
|
|
6
|
+
const DefaultTableEmptyState = ({
|
|
7
|
+
emptyStateLabel,
|
|
8
|
+
isLoading,
|
|
9
|
+
isTable = false,
|
|
10
|
+
tableLayoutStyle = null,
|
|
11
|
+
tableMutedColor,
|
|
12
|
+
}) => (
|
|
13
|
+
<View style={[styles.emptyBox, isTable ? tableLayoutStyle : null]}>
|
|
14
|
+
{isLoading ? (
|
|
15
|
+
<StateStore compact loading={emptyStateLabel} />
|
|
16
|
+
) : (
|
|
17
|
+
<Text style={[styles.emptyText, { color: tableMutedColor }]}>{emptyStateLabel}</Text>
|
|
18
|
+
)}
|
|
19
|
+
</View>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export default DefaultTableEmptyState;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { Text, View } from 'react-native';
|
|
3
|
+
import { useStore } from '@store';
|
|
4
|
+
import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
|
|
5
|
+
import { getColumnKey, normalizeText } from '../inputs/defaultInputUtils';
|
|
6
|
+
import {
|
|
7
|
+
flattenSummaryEntries,
|
|
8
|
+
formatSummaryValue,
|
|
9
|
+
getSummaryField,
|
|
10
|
+
getSummaryOperations,
|
|
11
|
+
isObject,
|
|
12
|
+
} from './DefaultTable.utils';
|
|
13
|
+
import styles from './DefaultTable.styles';
|
|
14
|
+
import useDefaultTableTheme from './useDefaultTableTheme';
|
|
15
|
+
|
|
16
|
+
const DefaultTableFooter = ({ storeName }) => {
|
|
17
|
+
const store = useStore(storeName);
|
|
18
|
+
const configs = store?.getters?.configs || {};
|
|
19
|
+
const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
|
|
20
|
+
const footerComponent = configs.footerComponent || null;
|
|
21
|
+
const isCompactView = false;
|
|
22
|
+
const showTotalItemsInCompactToolbar = configs.showTotalItemsInCompactToolbar === true;
|
|
23
|
+
const showTotalItemsInFooter = configs.showTotalItemsInFooter !== false;
|
|
24
|
+
const tableColumns = columns;
|
|
25
|
+
const { themeColors } = useDefaultTableTheme();
|
|
26
|
+
const tableFooterBackgroundColor = themeColors.tableFooterBackground;
|
|
27
|
+
const tableFooterBorderColor = themeColors.tableFooterBorder;
|
|
28
|
+
const tableFooterTextColor = themeColors.tableFooterText;
|
|
29
|
+
const toolbarCountBackgroundColor = themeColors.badgeBackground;
|
|
30
|
+
const toolbarCountTextColor = themeColors.badgeText;
|
|
31
|
+
const storeTotalItems = store?.getters?.totalItems;
|
|
32
|
+
const resolvedTotalItems = storeTotalItems;
|
|
33
|
+
const totalItemsNumber = Number(resolvedTotalItems);
|
|
34
|
+
const shouldRenderTotalItems =
|
|
35
|
+
resolvedTotalItems !== null &&
|
|
36
|
+
resolvedTotalItems !== undefined &&
|
|
37
|
+
Number.isFinite(totalItemsNumber);
|
|
38
|
+
const shouldRenderFooterTotalItems =
|
|
39
|
+
shouldRenderTotalItems &&
|
|
40
|
+
showTotalItemsInFooter !== false;
|
|
41
|
+
const shouldRenderCompactToolbarTotalItems =
|
|
42
|
+
showTotalItemsInCompactToolbar === true &&
|
|
43
|
+
isCompactView &&
|
|
44
|
+
shouldRenderTotalItems &&
|
|
45
|
+
!shouldRenderFooterTotalItems;
|
|
46
|
+
const resolvedTotalItemsText =
|
|
47
|
+
shouldRenderTotalItems
|
|
48
|
+
? `${totalItemsNumber} ${global.t?.t(storeName, 'label', 'items')}`
|
|
49
|
+
: '';
|
|
50
|
+
const configuredSummary = configs.summary;
|
|
51
|
+
const resolvedSummary = configuredSummary !== undefined
|
|
52
|
+
? configuredSummary
|
|
53
|
+
: store?.getters?.summary;
|
|
54
|
+
const summaryLabels = isObject(configs.summaryLabels) ? configs.summaryLabels : {};
|
|
55
|
+
const shouldReadSummary = resolvedSummary !== false && isObject(resolvedSummary);
|
|
56
|
+
const summaryEntries = useMemo(() => {
|
|
57
|
+
if (!shouldReadSummary) return [];
|
|
58
|
+
|
|
59
|
+
const usedPaths = new Set();
|
|
60
|
+
const columnEntries = tableColumns.flatMap(column => {
|
|
61
|
+
const operations = getSummaryOperations(column);
|
|
62
|
+
if (!operations.length) return [];
|
|
63
|
+
|
|
64
|
+
const fieldName = getColumnKey(column);
|
|
65
|
+
const columnLabel = formatStoreColumnLabel({
|
|
66
|
+
columns,
|
|
67
|
+
fieldName,
|
|
68
|
+
fallbackLabel: column?.label || fieldName,
|
|
69
|
+
storeName,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return operations.map(operation => {
|
|
73
|
+
const summaryField = getSummaryField(column, operation);
|
|
74
|
+
const path = [operation, summaryField];
|
|
75
|
+
const pathKey = path.join('.');
|
|
76
|
+
const value = resolvedSummary?.[operation]?.[summaryField];
|
|
77
|
+
if (value === undefined) return null;
|
|
78
|
+
|
|
79
|
+
usedPaths.add(pathKey);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
key: pathKey,
|
|
83
|
+
label: operations.length > 1 ? `${columnLabel} ${operation}` : columnLabel,
|
|
84
|
+
path,
|
|
85
|
+
value,
|
|
86
|
+
column,
|
|
87
|
+
};
|
|
88
|
+
}).filter(Boolean);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const genericEntries = flattenSummaryEntries({
|
|
92
|
+
summaryLabels,
|
|
93
|
+
usedPaths,
|
|
94
|
+
value: resolvedSummary,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return [...columnEntries, ...genericEntries].filter(entry =>
|
|
98
|
+
entry?.value !== undefined &&
|
|
99
|
+
entry?.value !== null &&
|
|
100
|
+
normalizeText(entry.value) !== '',
|
|
101
|
+
);
|
|
102
|
+
}, [columns, resolvedSummary, shouldReadSummary, storeName, summaryLabels, tableColumns]);
|
|
103
|
+
const shouldRenderFooterBar =
|
|
104
|
+
(shouldRenderFooterTotalItems && !shouldRenderCompactToolbarTotalItems) ||
|
|
105
|
+
summaryEntries.length > 0;
|
|
106
|
+
|
|
107
|
+
if (footerComponent) {
|
|
108
|
+
const footerProps = {
|
|
109
|
+
columns,
|
|
110
|
+
resolvedTotalItemsText,
|
|
111
|
+
shouldRenderCompactToolbarTotalItems,
|
|
112
|
+
shouldRenderFooterTotalItems,
|
|
113
|
+
storeName,
|
|
114
|
+
summaryEntries,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return React.isValidElement(footerComponent)
|
|
118
|
+
? footerComponent
|
|
119
|
+
: React.createElement(footerComponent, footerProps);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!shouldRenderFooterBar) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<View style={[styles.footerBar, { backgroundColor: tableFooterBackgroundColor, borderTopColor: tableFooterBorderColor }]}>
|
|
128
|
+
{summaryEntries.length > 0 ? (
|
|
129
|
+
<View style={styles.footerSummaryList}>
|
|
130
|
+
{summaryEntries.map(entry => (
|
|
131
|
+
<View key={entry.key} style={styles.footerSummaryItem}>
|
|
132
|
+
<Text style={[styles.footerSummaryLabel, { color: tableFooterTextColor }]} numberOfLines={1}>
|
|
133
|
+
{entry.label}
|
|
134
|
+
</Text>
|
|
135
|
+
<Text
|
|
136
|
+
style={[
|
|
137
|
+
styles.footerSummaryValue,
|
|
138
|
+
{ color: tableFooterTextColor },
|
|
139
|
+
]}
|
|
140
|
+
numberOfLines={1}
|
|
141
|
+
adjustsFontSizeToFit
|
|
142
|
+
minimumFontScale={0.78}
|
|
143
|
+
>
|
|
144
|
+
{formatSummaryValue({
|
|
145
|
+
column: entry.column,
|
|
146
|
+
columns,
|
|
147
|
+
path: entry.path,
|
|
148
|
+
storeName,
|
|
149
|
+
value: entry.value,
|
|
150
|
+
})}
|
|
151
|
+
</Text>
|
|
152
|
+
</View>
|
|
153
|
+
))}
|
|
154
|
+
</View>
|
|
155
|
+
) : null}
|
|
156
|
+
{shouldRenderFooterTotalItems && !shouldRenderCompactToolbarTotalItems ? (
|
|
157
|
+
<View style={[styles.footerCountPill, { backgroundColor: toolbarCountBackgroundColor }]}>
|
|
158
|
+
<Text
|
|
159
|
+
style={[styles.footerCountText, { color: toolbarCountTextColor }]}
|
|
160
|
+
numberOfLines={1}
|
|
161
|
+
adjustsFontSizeToFit
|
|
162
|
+
minimumFontScale={0.82}
|
|
163
|
+
>
|
|
164
|
+
{resolvedTotalItemsText}
|
|
165
|
+
</Text>
|
|
166
|
+
</View>
|
|
167
|
+
) : null}
|
|
168
|
+
</View>
|
|
169
|
+
);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export default DefaultTableFooter;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { useStore } from '@store';
|
|
4
|
+
import DefaultInput from '../inputs/DefaultInput';
|
|
5
|
+
import {
|
|
6
|
+
formatSaveValue,
|
|
7
|
+
getColumnKey,
|
|
8
|
+
isEditableColumn,
|
|
9
|
+
normalizeId,
|
|
10
|
+
} from '../inputs/defaultInputUtils';
|
|
11
|
+
import { getColumnStyle } from './DefaultTable.utils';
|
|
12
|
+
import styles from './DefaultTable.styles';
|
|
13
|
+
import useDefaultTableTheme from './useDefaultTableTheme';
|
|
14
|
+
|
|
15
|
+
const STORE_ACTION_META_KEY = '__storeMeta';
|
|
16
|
+
|
|
17
|
+
const isPlainObject = value =>
|
|
18
|
+
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
19
|
+
|
|
20
|
+
const asStyleArray = style => (Array.isArray(style) ? style : [style]).filter(Boolean);
|
|
21
|
+
|
|
22
|
+
const buildSavedItemPatch = (column, fieldName, value) => {
|
|
23
|
+
if (!column?.list || !fieldName || !isPlainObject(value)) {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (isPlainObject(value.object)) {
|
|
28
|
+
return {
|
|
29
|
+
[fieldName]: value.object,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (value.value == null && value.label == null) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const labelField = column?.listSearchParam || column?.searchParam || fieldName;
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
[fieldName]: {
|
|
41
|
+
id: value.value,
|
|
42
|
+
value: value.value,
|
|
43
|
+
label: value.label,
|
|
44
|
+
[labelField]: value.label,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const DefaultTableInput = ({
|
|
50
|
+
column: columnProp = null,
|
|
51
|
+
fieldName = '',
|
|
52
|
+
options = {},
|
|
53
|
+
row = {},
|
|
54
|
+
storeName = '',
|
|
55
|
+
variant = 'cell',
|
|
56
|
+
}) => {
|
|
57
|
+
const store = useStore(storeName);
|
|
58
|
+
const configs = store?.getters?.configs || {};
|
|
59
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
60
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
61
|
+
const columns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
|
|
62
|
+
const hasRowPress = false;
|
|
63
|
+
const { resolvedAccentColor } = useDefaultTableTheme();
|
|
64
|
+
const column = columnProp || columns.find(item => getColumnKey(item) === fieldName);
|
|
65
|
+
|
|
66
|
+
if (!column) return null;
|
|
67
|
+
|
|
68
|
+
const resolvedFieldName = getColumnKey(column);
|
|
69
|
+
const input = (
|
|
70
|
+
<DefaultInput
|
|
71
|
+
accentColor={options.accentColor || resolvedAccentColor}
|
|
72
|
+
column={column}
|
|
73
|
+
columns={columns}
|
|
74
|
+
containerStyle={options.containerStyle}
|
|
75
|
+
displayValue={options.displayValue}
|
|
76
|
+
editing={isEditing}
|
|
77
|
+
getOptionsForColumn={configs.getOptionsForColumn}
|
|
78
|
+
inputStyle={options.inputStyle}
|
|
79
|
+
label={options.label}
|
|
80
|
+
numberOfLines={options.numberOfLines}
|
|
81
|
+
onCancelEditing={() => setIsEditing(false)}
|
|
82
|
+
onSave={value => {
|
|
83
|
+
if (typeof store?.actions?.save !== 'function') {
|
|
84
|
+
setIsEditing(false);
|
|
85
|
+
return Promise.resolve(null);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
setIsSaving(true);
|
|
89
|
+
const savedItemPatch = buildSavedItemPatch(column, resolvedFieldName, value);
|
|
90
|
+
const storeMeta =
|
|
91
|
+
Object.keys(savedItemPatch).length > 0
|
|
92
|
+
? {
|
|
93
|
+
[STORE_ACTION_META_KEY]: {
|
|
94
|
+
savedItemPatch,
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
: {};
|
|
98
|
+
|
|
99
|
+
return Promise.resolve(
|
|
100
|
+
store.actions.save({
|
|
101
|
+
id: normalizeId(row?.['@id'] || row?.id),
|
|
102
|
+
[resolvedFieldName]: formatSaveValue(column, value, row),
|
|
103
|
+
...storeMeta,
|
|
104
|
+
}),
|
|
105
|
+
)
|
|
106
|
+
.then(savedItem => {
|
|
107
|
+
configs.onSaved?.(savedItem, row);
|
|
108
|
+
return savedItem;
|
|
109
|
+
})
|
|
110
|
+
.finally(() => {
|
|
111
|
+
setIsSaving(false);
|
|
112
|
+
setIsEditing(false);
|
|
113
|
+
});
|
|
114
|
+
}}
|
|
115
|
+
onStartEditing={() => setIsEditing(true)}
|
|
116
|
+
readTextStyle={options.readTextStyle || options.textStyle}
|
|
117
|
+
row={row}
|
|
118
|
+
saving={isSaving}
|
|
119
|
+
showLabel={options.showLabel}
|
|
120
|
+
storeName={storeName}
|
|
121
|
+
variant={options.variant || variant}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (variant !== 'cell') {
|
|
126
|
+
return input;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const shouldDelegatePress = hasRowPress && !isEditableColumn(column);
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<View
|
|
133
|
+
style={[
|
|
134
|
+
...asStyleArray(getColumnStyle(column)),
|
|
135
|
+
...asStyleArray(options.cellStyle),
|
|
136
|
+
isEditing ? styles.editingCell : null,
|
|
137
|
+
]}
|
|
138
|
+
pointerEvents={shouldDelegatePress ? 'none' : 'auto'}
|
|
139
|
+
>
|
|
140
|
+
{input}
|
|
141
|
+
</View>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export default DefaultTableInput;
|