@controleonline/ui-default 1.0.267 → 1.0.269
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 +1 -1
- package/src/react/components/filters/CompactFilterSelector.js +110 -39
- package/src/react/components/filters/CompactFilterSelector.styles.js +34 -12
- package/src/react/components/inputs/DefaultInput.js +54 -28
- package/src/react/components/inputs/DefaultInput.styles.js +7 -1
- package/src/react/components/inputs/DefaultSelect.js +77 -36
- package/src/react/components/inputs/DefaultSelect.styles.js +3 -0
- package/src/react/components/inputs/defaultInputUtils.js +26 -5
- package/src/react/components/table/DefaultTable.js +7 -1
- package/src/react/components/table/DefaultTable.styles.js +2 -0
- package/src/react/components/table/DefaultTable.utils.js +33 -3
- package/src/react/components/table/DefaultTableCards.js +11 -2
- package/src/react/components/table/DefaultTableRowActions.js +41 -0
- package/src/react/components/table/DefaultTableRows.js +23 -13
- package/src/tests/react/components/inputs/DefaultSelect.test.js +72 -0
- package/src/tests/react/components/table/DefaultTable.test.js +55 -0
- package/src/tests/react/components/table/DefaultTable.utils.test.js +37 -0
- package/src/tests/react/inputs/defaultInputUtils.test.js +38 -6
package/AGENTS.md
CHANGED
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
- `requestParams` serve para contexto fixo do fluxo, nao para duplicar estado de filtro que ja vive no store.
|
|
22
22
|
- `pageSize` so deve ser usado quando o contrato externo exigir e com justificativa clara no modulo.
|
|
23
23
|
- `summary` e `totalItems` vem do backend/store e pertencem ao rodape interno do `DefaultTable`.
|
|
24
|
-
- `add: true` abre o `DefaultForm` quando a tela nao prover `onAdd`.
|
|
25
|
-
-
|
|
24
|
+
- `add: true` abre o `DefaultForm` quando a tela nao prover `onAdd`.
|
|
25
|
+
- `rowActionsComponent` e o slot canonico para acoes por linha quando a tela precisa renderizar mais do que o botao padrao de edicao. Ele pode ser componente, funcao ou elemento React, recebe `row`, `storeName`, `openEdit`, `openRow` e `helpers`, e a largura da coluna deve ser ajustada com `rowActionsWidth` quando houver mais de uma acao.
|
|
26
|
+
- O modo controlado continua existindo apenas para excecoes reais de integracao; o padrao do sistema e o modo store-driven.
|
|
26
27
|
- Loadings e erros devem seguir o fluxo central do sistema, sem banners ou estados paralelos na tela.
|
|
27
28
|
- O `get` do store default pode preservar o `item` atual durante refresh quando receber `__storeMeta.preserveItem = true`; use esse modo quando a tela precisa atualizar o registro sem desmontar o detalhe em exibição.
|
|
28
29
|
- Explicacoes permanentes de tela devem sair do corpo principal e ir para o `DefaultTooltip`, acionado por `?`, quando houver contexto necessario para o usuario.
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useMemo, useState } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Image,
|
|
3
4
|
Modal,
|
|
4
5
|
ScrollView,
|
|
5
6
|
Text,
|
|
@@ -10,12 +11,13 @@ import {
|
|
|
10
11
|
View,
|
|
11
12
|
} from 'react-native';
|
|
12
13
|
import Icon from 'react-native-vector-icons/Feather';
|
|
13
|
-
import {
|
|
14
|
-
formatStoreColumnLabel,
|
|
15
|
-
resolveStoreColumn,
|
|
16
|
-
resolveStoreConfigByName,
|
|
17
|
-
} from '@controleonline/ui-common/src/react/utils/storeColumns';
|
|
18
|
-
import
|
|
14
|
+
import {
|
|
15
|
+
formatStoreColumnLabel,
|
|
16
|
+
resolveStoreColumn,
|
|
17
|
+
resolveStoreConfigByName,
|
|
18
|
+
} from '@controleonline/ui-common/src/react/utils/storeColumns';
|
|
19
|
+
import { isValidFeatherIcon } from '../inputs/defaultInputUtils';
|
|
20
|
+
import createStyles from './CompactFilterSelector.styles';
|
|
19
21
|
|
|
20
22
|
const buildTheme = ({ accentColor, themeColors = {} }) => ({
|
|
21
23
|
accentColor: accentColor,
|
|
@@ -117,8 +119,23 @@ const CompactFilterSelector = ({
|
|
|
117
119
|
field,
|
|
118
120
|
store,
|
|
119
121
|
});
|
|
120
|
-
const resolvedLabelCaption = labelCaption || storeFieldLabel;
|
|
121
|
-
const resolvedTitle = title || storeFieldLabel;
|
|
122
|
+
const resolvedLabelCaption = labelCaption || storeFieldLabel;
|
|
123
|
+
const resolvedTitle = title || storeFieldLabel;
|
|
124
|
+
const selectedOption = useMemo(
|
|
125
|
+
() => options.find(option => option?.key === selectedKey),
|
|
126
|
+
[options, selectedKey],
|
|
127
|
+
);
|
|
128
|
+
const selectedOptionColor = normalizeText(selectedOption?.color);
|
|
129
|
+
const selectedOptionIcon = normalizeText(selectedOption?.icon);
|
|
130
|
+
const selectedOptionIconName = isValidFeatherIcon(selectedOptionIcon)
|
|
131
|
+
? selectedOptionIcon
|
|
132
|
+
: '';
|
|
133
|
+
const triggerIconName = active && selectedOptionIconName
|
|
134
|
+
? selectedOptionIconName
|
|
135
|
+
: icon;
|
|
136
|
+
const triggerIconColor = active
|
|
137
|
+
? selectedOptionColor || resolvedTheme.activeIconColor
|
|
138
|
+
: resolvedTheme.iconColor;
|
|
122
139
|
|
|
123
140
|
const closeModal = useCallback(() => {
|
|
124
141
|
setVisible(false);
|
|
@@ -201,22 +218,31 @@ const CompactFilterSelector = ({
|
|
|
201
218
|
disabled={disabled}
|
|
202
219
|
onPress={openModal}
|
|
203
220
|
>
|
|
204
|
-
<View style={[styles.iconWrap, active ? styles.iconWrapActive : null]}>
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
221
|
+
<View style={[styles.iconWrap, active ? styles.iconWrapActive : null]}>
|
|
222
|
+
{active && selectedOption?.image ? (
|
|
223
|
+
<Image
|
|
224
|
+
source={selectedOption.image}
|
|
225
|
+
style={styles.triggerIconImage}
|
|
226
|
+
resizeMode="contain"
|
|
227
|
+
/>
|
|
228
|
+
) : (
|
|
229
|
+
<Icon
|
|
230
|
+
name={triggerIconName}
|
|
231
|
+
size={dense ? 14 : 15}
|
|
232
|
+
color={triggerIconColor}
|
|
233
|
+
/>
|
|
234
|
+
)}
|
|
235
|
+
</View>
|
|
211
236
|
|
|
212
237
|
<View style={styles.textWrap}>
|
|
213
238
|
{!!resolvedLabelCaption && (
|
|
214
239
|
<Text
|
|
215
240
|
numberOfLines={1}
|
|
216
241
|
style={[
|
|
217
|
-
styles.triggerCaption,
|
|
218
|
-
active ? styles.triggerCaptionActive : null,
|
|
219
|
-
|
|
242
|
+
styles.triggerCaption,
|
|
243
|
+
active ? styles.triggerCaptionActive : null,
|
|
244
|
+
active && selectedOptionColor ? { color: selectedOptionColor } : null,
|
|
245
|
+
]}
|
|
220
246
|
>
|
|
221
247
|
{resolvedLabelCaption}
|
|
222
248
|
</Text>
|
|
@@ -224,7 +250,11 @@ const CompactFilterSelector = ({
|
|
|
224
250
|
|
|
225
251
|
<Text
|
|
226
252
|
numberOfLines={1}
|
|
227
|
-
style={[
|
|
253
|
+
style={[
|
|
254
|
+
styles.triggerText,
|
|
255
|
+
active ? styles.triggerTextActive : null,
|
|
256
|
+
active && selectedOptionColor ? { color: selectedOptionColor } : null,
|
|
257
|
+
]}
|
|
228
258
|
>
|
|
229
259
|
{label}
|
|
230
260
|
</Text>
|
|
@@ -270,33 +300,74 @@ const CompactFilterSelector = ({
|
|
|
270
300
|
) : null}
|
|
271
301
|
|
|
272
302
|
{filteredOptions.map(option => {
|
|
273
|
-
const isSelected = option.key === selectedKey;
|
|
274
|
-
|
|
275
|
-
|
|
303
|
+
const isSelected = option.key === selectedKey;
|
|
304
|
+
const optionColor = normalizeText(option?.color);
|
|
305
|
+
const optionIcon = normalizeText(option?.icon);
|
|
306
|
+
const optionIconName = isValidFeatherIcon(optionIcon)
|
|
307
|
+
? optionIcon
|
|
308
|
+
: '';
|
|
309
|
+
const shouldRenderOptionFallbackText = Boolean(
|
|
310
|
+
!option?.image &&
|
|
311
|
+
!optionIconName &&
|
|
312
|
+
optionColor &&
|
|
313
|
+
normalizeText(option?.label),
|
|
314
|
+
);
|
|
315
|
+
const hasOptionVisual = Boolean(option?.image || optionIconName || shouldRenderOptionFallbackText);
|
|
316
|
+
|
|
317
|
+
return (
|
|
276
318
|
<TouchableOpacity
|
|
277
319
|
key={option.key}
|
|
278
320
|
style={[
|
|
279
321
|
styles.modalOption,
|
|
280
322
|
isSelected ? styles.modalOptionActive : null,
|
|
281
323
|
]}
|
|
282
|
-
activeOpacity={0.9}
|
|
283
|
-
onPress={() => handleSelect(option.key)}
|
|
284
|
-
>
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
324
|
+
activeOpacity={0.9}
|
|
325
|
+
onPress={() => handleSelect(option.key)}
|
|
326
|
+
>
|
|
327
|
+
{option?.image ? (
|
|
328
|
+
<Image
|
|
329
|
+
source={option.image}
|
|
330
|
+
style={styles.modalOptionImage}
|
|
331
|
+
resizeMode="contain"
|
|
332
|
+
/>
|
|
333
|
+
) : optionIconName ? (
|
|
334
|
+
<Icon
|
|
335
|
+
style={styles.modalOptionIcon}
|
|
336
|
+
name={optionIconName}
|
|
337
|
+
size={16}
|
|
338
|
+
color={optionColor || resolvedTheme.iconColor}
|
|
339
|
+
/>
|
|
340
|
+
) : shouldRenderOptionFallbackText ? (
|
|
341
|
+
<Text
|
|
342
|
+
style={[
|
|
343
|
+
styles.modalOptionFallbackText,
|
|
344
|
+
{ color: optionColor },
|
|
345
|
+
]}
|
|
346
|
+
numberOfLines={1}
|
|
347
|
+
>
|
|
348
|
+
{option.label}
|
|
349
|
+
</Text>
|
|
350
|
+
) : null}
|
|
351
|
+
|
|
352
|
+
{shouldRenderOptionFallbackText ? null : (
|
|
353
|
+
<Text
|
|
354
|
+
style={[
|
|
355
|
+
styles.modalOptionText,
|
|
356
|
+
isSelected ? styles.modalOptionTextActive : null,
|
|
357
|
+
optionColor ? { color: optionColor } : null,
|
|
358
|
+
!hasOptionVisual ? styles.modalOptionTextNoVisual : null,
|
|
359
|
+
]}
|
|
360
|
+
>
|
|
361
|
+
{option.label}
|
|
362
|
+
</Text>
|
|
363
|
+
)}
|
|
293
364
|
|
|
294
365
|
{isSelected ? (
|
|
295
|
-
<Icon
|
|
296
|
-
name="check"
|
|
297
|
-
size={16}
|
|
298
|
-
color={resolvedTheme.activeIconColor}
|
|
299
|
-
/>
|
|
366
|
+
<Icon
|
|
367
|
+
name="check"
|
|
368
|
+
size={16}
|
|
369
|
+
color={optionColor || resolvedTheme.activeIconColor}
|
|
370
|
+
/>
|
|
300
371
|
) : null}
|
|
301
372
|
</TouchableOpacity>
|
|
302
373
|
);
|
|
@@ -29,9 +29,14 @@ const createStyles = (theme, dense = false, windowWidth = 0) => {
|
|
|
29
29
|
justifyContent: 'center',
|
|
30
30
|
backgroundColor: theme.iconBackgroundColor,
|
|
31
31
|
},
|
|
32
|
-
iconWrapActive: {
|
|
33
|
-
backgroundColor: theme.activeIconBackgroundColor,
|
|
34
|
-
},
|
|
32
|
+
iconWrapActive: {
|
|
33
|
+
backgroundColor: theme.activeIconBackgroundColor,
|
|
34
|
+
},
|
|
35
|
+
triggerIconImage: {
|
|
36
|
+
width: dense ? 16 : 18,
|
|
37
|
+
height: dense ? 16 : 18,
|
|
38
|
+
flexShrink: 0,
|
|
39
|
+
},
|
|
35
40
|
textWrap: {
|
|
36
41
|
flex: 1,
|
|
37
42
|
minWidth: 0,
|
|
@@ -105,7 +110,7 @@ const createStyles = (theme, dense = false, windowWidth = 0) => {
|
|
|
105
110
|
fontWeight: '700',
|
|
106
111
|
},
|
|
107
112
|
modalOption: {
|
|
108
|
-
minHeight: 50,
|
|
113
|
+
minHeight: 50,
|
|
109
114
|
borderWidth: 1,
|
|
110
115
|
borderColor: theme.optionBorderColor,
|
|
111
116
|
borderRadius: 16,
|
|
@@ -115,18 +120,35 @@ const createStyles = (theme, dense = false, windowWidth = 0) => {
|
|
|
115
120
|
flexDirection: 'row',
|
|
116
121
|
alignItems: 'center',
|
|
117
122
|
justifyContent: 'space-between',
|
|
118
|
-
gap: 8,
|
|
119
|
-
},
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
gap: 8,
|
|
124
|
+
},
|
|
125
|
+
modalOptionIcon: {
|
|
126
|
+
flexShrink: 0,
|
|
127
|
+
},
|
|
128
|
+
modalOptionImage: {
|
|
129
|
+
width: 18,
|
|
130
|
+
height: 18,
|
|
131
|
+
flexShrink: 0,
|
|
132
|
+
},
|
|
133
|
+
modalOptionFallbackText: {
|
|
134
|
+
flex: 1,
|
|
135
|
+
minWidth: 0,
|
|
136
|
+
fontSize: 14,
|
|
137
|
+
fontWeight: '800',
|
|
138
|
+
},
|
|
139
|
+
modalOptionActive: {
|
|
140
|
+
borderColor: theme.accentColor,
|
|
141
|
+
backgroundColor: theme.activeBackgroundColor,
|
|
123
142
|
},
|
|
124
143
|
modalOptionText: {
|
|
125
144
|
flex: 1,
|
|
126
145
|
fontSize: 14,
|
|
127
|
-
fontWeight: '700',
|
|
128
|
-
color: theme.textColor,
|
|
129
|
-
},
|
|
146
|
+
fontWeight: '700',
|
|
147
|
+
color: theme.textColor,
|
|
148
|
+
},
|
|
149
|
+
modalOptionTextNoVisual: {
|
|
150
|
+
marginLeft: 0,
|
|
151
|
+
},
|
|
130
152
|
modalOptionTextActive: {
|
|
131
153
|
color: theme.optionSelectedTextColor,
|
|
132
154
|
},
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
getColumnKey,
|
|
7
7
|
isDateLikeColumn,
|
|
8
8
|
isEditableColumn,
|
|
9
|
+
isValidFeatherIcon,
|
|
9
10
|
normalizeText,
|
|
10
11
|
resolveCellPresentation,
|
|
11
12
|
resolveCellText,
|
|
@@ -142,6 +143,17 @@ const DefaultInput = ({
|
|
|
142
143
|
};
|
|
143
144
|
|
|
144
145
|
if (!editing && !isForm) {
|
|
146
|
+
const readIconName = isValidFeatherIcon(readPresentation.icon)
|
|
147
|
+
? readPresentation.icon
|
|
148
|
+
: '';
|
|
149
|
+
const shouldRenderIconFallbackText = Boolean(
|
|
150
|
+
readPresentationStyles.hasDecoration &&
|
|
151
|
+
!readIconName &&
|
|
152
|
+
readPresentationStyles.color &&
|
|
153
|
+
normalizeText(resolvedLabel) &&
|
|
154
|
+
resolvedLabel !== '-',
|
|
155
|
+
);
|
|
156
|
+
|
|
145
157
|
return (
|
|
146
158
|
<View style={[styles.wrap, containerStyle]}>
|
|
147
159
|
{showLabel ? <Text style={styles.fieldLabel}>{label || column?.label || fieldName}</Text> : null}
|
|
@@ -151,43 +163,57 @@ const DefaultInput = ({
|
|
|
151
163
|
disabled={!canEdit}
|
|
152
164
|
onPress={() => onStartEditing?.()}
|
|
153
165
|
>
|
|
154
|
-
|
|
155
|
-
style={[
|
|
156
|
-
styles.readValueWrap,
|
|
157
|
-
readPresentationStyles.hasDecoration ? styles.readBadge : null,
|
|
158
|
-
readPresentationStyles.badgeStyle,
|
|
159
|
-
]}
|
|
160
|
-
>
|
|
161
|
-
{readPresentation.image ? (
|
|
162
|
-
<Image
|
|
163
|
-
source={readPresentation.image}
|
|
164
|
-
style={styles.readImage}
|
|
165
|
-
resizeMode="contain"
|
|
166
|
-
/>
|
|
167
|
-
) : null}
|
|
168
|
-
{readPresentationStyles.hasDecoration && readPresentation.icon && readPresentationStyles.color ? (
|
|
169
|
-
<Icon
|
|
170
|
-
style={styles.readBadgeIcon}
|
|
171
|
-
name={readPresentation.icon}
|
|
172
|
-
size={12}
|
|
173
|
-
color={readPresentationStyles.color}
|
|
174
|
-
/>
|
|
175
|
-
) : null}
|
|
166
|
+
{shouldRenderIconFallbackText ? (
|
|
176
167
|
<Text
|
|
177
168
|
style={[
|
|
178
169
|
styles.readText,
|
|
179
|
-
|
|
180
|
-
resolvedLabel === '-' ? styles.mutedText : null,
|
|
170
|
+
styles.readTextBadge,
|
|
181
171
|
readPresentationStyles.color ? { color: readPresentationStyles.color } : null,
|
|
182
172
|
readTextStyle,
|
|
183
173
|
]}
|
|
184
174
|
numberOfLines={numberOfLines}
|
|
185
175
|
>
|
|
186
|
-
{
|
|
187
|
-
{resolvedLabel || '-'}
|
|
188
|
-
{column?.sufix || column?.suffix || ''}
|
|
176
|
+
{resolvedLabel}
|
|
189
177
|
</Text>
|
|
190
|
-
|
|
178
|
+
) : (
|
|
179
|
+
<View
|
|
180
|
+
style={[
|
|
181
|
+
styles.readValueWrap,
|
|
182
|
+
readPresentationStyles.hasDecoration ? styles.readBadge : null,
|
|
183
|
+
readPresentationStyles.badgeStyle,
|
|
184
|
+
]}
|
|
185
|
+
>
|
|
186
|
+
{readPresentation.image ? (
|
|
187
|
+
<Image
|
|
188
|
+
source={readPresentation.image}
|
|
189
|
+
style={styles.readImage}
|
|
190
|
+
resizeMode="contain"
|
|
191
|
+
/>
|
|
192
|
+
) : null}
|
|
193
|
+
{readPresentationStyles.hasDecoration && readIconName && readPresentationStyles.color ? (
|
|
194
|
+
<Icon
|
|
195
|
+
style={styles.readBadgeIcon}
|
|
196
|
+
name={readIconName}
|
|
197
|
+
size={12}
|
|
198
|
+
color={readPresentationStyles.color}
|
|
199
|
+
/>
|
|
200
|
+
) : null}
|
|
201
|
+
<Text
|
|
202
|
+
style={[
|
|
203
|
+
styles.readText,
|
|
204
|
+
readPresentationStyles.hasDecoration ? styles.readBadgeText : null,
|
|
205
|
+
resolvedLabel === '-' ? styles.mutedText : null,
|
|
206
|
+
readPresentationStyles.color ? { color: readPresentationStyles.color } : null,
|
|
207
|
+
readTextStyle,
|
|
208
|
+
]}
|
|
209
|
+
numberOfLines={numberOfLines}
|
|
210
|
+
>
|
|
211
|
+
{column?.prefix || ''}
|
|
212
|
+
{resolvedLabel || '-'}
|
|
213
|
+
{column?.sufix || column?.suffix || ''}
|
|
214
|
+
</Text>
|
|
215
|
+
</View>
|
|
216
|
+
)}
|
|
191
217
|
{saving ? (
|
|
192
218
|
<Text style={[styles.savingText, { color: accentColor }]}>Salvando</Text>
|
|
193
219
|
) : canEdit ? (
|
|
@@ -24,6 +24,7 @@ const styles = StyleSheet.create({
|
|
|
24
24
|
readBadge: {
|
|
25
25
|
flex: 0,
|
|
26
26
|
alignSelf: 'flex-start',
|
|
27
|
+
minWidth: 22,
|
|
27
28
|
minHeight: 22,
|
|
28
29
|
maxWidth: '100%',
|
|
29
30
|
borderWidth: 1,
|
|
@@ -33,10 +34,15 @@ const styles = StyleSheet.create({
|
|
|
33
34
|
},
|
|
34
35
|
readBadgeText: {
|
|
35
36
|
flex: 0,
|
|
36
|
-
flexShrink:
|
|
37
|
+
flexShrink: 0,
|
|
38
|
+
maxWidth: '100%',
|
|
37
39
|
fontSize: 11,
|
|
38
40
|
fontWeight: '800',
|
|
39
41
|
},
|
|
42
|
+
readTextBadge: {
|
|
43
|
+
flex: 1,
|
|
44
|
+
maxWidth: '100%',
|
|
45
|
+
},
|
|
40
46
|
readBadgeIcon: {
|
|
41
47
|
flexShrink: 0,
|
|
42
48
|
},
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
buildOptionsFromColumn,
|
|
16
16
|
getColumnKey,
|
|
17
17
|
isEditableColumn,
|
|
18
|
+
isValidFeatherIcon,
|
|
18
19
|
mapOptions,
|
|
19
20
|
normalizeOptionKey,
|
|
20
21
|
normalizeText,
|
|
@@ -159,7 +160,17 @@ const DefaultSelect = ({
|
|
|
159
160
|
[readPresentation, resolvedLabel],
|
|
160
161
|
);
|
|
161
162
|
const canEdit = isEditableColumn(column) && typeof onStartEditing === 'function';
|
|
162
|
-
const isForm = variant === 'form';
|
|
163
|
+
const isForm = variant === 'form';
|
|
164
|
+
const readIconName = isValidFeatherIcon(readPresentation.icon)
|
|
165
|
+
? readPresentation.icon
|
|
166
|
+
: '';
|
|
167
|
+
const shouldRenderIconFallbackText = Boolean(
|
|
168
|
+
readPresentationStyles.hasDecoration &&
|
|
169
|
+
!readIconName &&
|
|
170
|
+
readPresentationStyles.color &&
|
|
171
|
+
normalizeText(resolvedLabel) &&
|
|
172
|
+
resolvedLabel !== '-',
|
|
173
|
+
);
|
|
163
174
|
const filteredOptions = useMemo(() => {
|
|
164
175
|
const query = normalizeText(searchText).toLowerCase();
|
|
165
176
|
if (!query || onSearchChange) return options;
|
|
@@ -290,47 +301,61 @@ const DefaultSelect = ({
|
|
|
290
301
|
<View style={[inputStyles.wrap, containerStyle]}>
|
|
291
302
|
{showLabel ? <Text style={inputStyles.fieldLabel}>{label || column?.label || fieldName}</Text> : null}
|
|
292
303
|
|
|
293
|
-
<TouchableOpacity
|
|
294
|
-
style={[inputStyles.readButton, isForm ? inputStyles.readButtonForm : null]}
|
|
295
|
-
activeOpacity={canEdit || isForm ? 0.78 : 1}
|
|
296
|
-
disabled={!canEdit && !isForm}
|
|
297
|
-
onPress={open}
|
|
304
|
+
<TouchableOpacity
|
|
305
|
+
style={[inputStyles.readButton, isForm ? inputStyles.readButtonForm : null]}
|
|
306
|
+
activeOpacity={canEdit || isForm ? 0.78 : 1}
|
|
307
|
+
disabled={!canEdit && !isForm}
|
|
308
|
+
onPress={open}
|
|
298
309
|
>
|
|
299
|
-
|
|
300
|
-
style={[
|
|
301
|
-
inputStyles.readValueWrap,
|
|
302
|
-
readPresentationStyles.hasDecoration ? inputStyles.readBadge : null,
|
|
303
|
-
readPresentationStyles.badgeStyle,
|
|
304
|
-
]}
|
|
305
|
-
>
|
|
306
|
-
{readPresentation.image ? (
|
|
307
|
-
<Image
|
|
308
|
-
source={readPresentation.image}
|
|
309
|
-
style={inputStyles.readImage}
|
|
310
|
-
resizeMode="contain"
|
|
311
|
-
/>
|
|
312
|
-
) : null}
|
|
313
|
-
{readPresentationStyles.hasDecoration && readPresentation.icon && readPresentationStyles.color ? (
|
|
314
|
-
<Icon
|
|
315
|
-
style={inputStyles.readBadgeIcon}
|
|
316
|
-
name={readPresentation.icon}
|
|
317
|
-
size={12}
|
|
318
|
-
color={readPresentationStyles.color}
|
|
319
|
-
/>
|
|
320
|
-
) : null}
|
|
310
|
+
{shouldRenderIconFallbackText ? (
|
|
321
311
|
<Text
|
|
322
312
|
style={[
|
|
323
313
|
inputStyles.readText,
|
|
324
|
-
|
|
325
|
-
resolvedLabel === '-' ? inputStyles.mutedText : null,
|
|
314
|
+
inputStyles.readTextBadge,
|
|
326
315
|
readPresentationStyles.color ? { color: readPresentationStyles.color } : null,
|
|
327
316
|
readTextStyle,
|
|
328
317
|
]}
|
|
329
318
|
numberOfLines={numberOfLines}
|
|
330
319
|
>
|
|
331
|
-
{resolvedLabel
|
|
320
|
+
{resolvedLabel}
|
|
332
321
|
</Text>
|
|
333
|
-
|
|
322
|
+
) : (
|
|
323
|
+
<View
|
|
324
|
+
style={[
|
|
325
|
+
inputStyles.readValueWrap,
|
|
326
|
+
readPresentationStyles.hasDecoration ? inputStyles.readBadge : null,
|
|
327
|
+
readPresentationStyles.badgeStyle,
|
|
328
|
+
]}
|
|
329
|
+
>
|
|
330
|
+
{readPresentation.image ? (
|
|
331
|
+
<Image
|
|
332
|
+
source={readPresentation.image}
|
|
333
|
+
style={inputStyles.readImage}
|
|
334
|
+
resizeMode="contain"
|
|
335
|
+
/>
|
|
336
|
+
) : null}
|
|
337
|
+
{readPresentationStyles.hasDecoration && readIconName && readPresentationStyles.color ? (
|
|
338
|
+
<Icon
|
|
339
|
+
style={inputStyles.readBadgeIcon}
|
|
340
|
+
name={readIconName}
|
|
341
|
+
size={12}
|
|
342
|
+
color={readPresentationStyles.color}
|
|
343
|
+
/>
|
|
344
|
+
) : null}
|
|
345
|
+
<Text
|
|
346
|
+
style={[
|
|
347
|
+
inputStyles.readText,
|
|
348
|
+
readPresentationStyles.hasDecoration ? inputStyles.readBadgeText : null,
|
|
349
|
+
resolvedLabel === '-' ? inputStyles.mutedText : null,
|
|
350
|
+
readPresentationStyles.color ? { color: readPresentationStyles.color } : null,
|
|
351
|
+
readTextStyle,
|
|
352
|
+
]}
|
|
353
|
+
numberOfLines={numberOfLines}
|
|
354
|
+
>
|
|
355
|
+
{resolvedLabel || '-'}
|
|
356
|
+
</Text>
|
|
357
|
+
</View>
|
|
358
|
+
)}
|
|
334
359
|
{saving ? (
|
|
335
360
|
<Text style={[inputStyles.savingText, { color: accentColor }]}>Salvando</Text>
|
|
336
361
|
) : canEdit || isForm ? (
|
|
@@ -363,6 +388,16 @@ const DefaultSelect = ({
|
|
|
363
388
|
{filteredOptions.length > 0 ? (
|
|
364
389
|
filteredOptions.map(option => {
|
|
365
390
|
const isSelected = option.key === selectedKey;
|
|
391
|
+
const optionColor = normalizeText(option.color);
|
|
392
|
+
const optionIconName = isValidFeatherIcon(option.icon)
|
|
393
|
+
? option.icon
|
|
394
|
+
: '';
|
|
395
|
+
const shouldRenderOptionFallbackText = Boolean(
|
|
396
|
+
!option.image &&
|
|
397
|
+
!optionIconName &&
|
|
398
|
+
optionColor &&
|
|
399
|
+
normalizeText(option.label),
|
|
400
|
+
);
|
|
366
401
|
return (
|
|
367
402
|
<TouchableOpacity
|
|
368
403
|
key={`${option.key}_${option.label}`}
|
|
@@ -377,14 +412,20 @@ const DefaultSelect = ({
|
|
|
377
412
|
resizeMode="contain"
|
|
378
413
|
/>
|
|
379
414
|
) : null}
|
|
380
|
-
{isSelected || (
|
|
415
|
+
{isSelected || (optionIconName && optionColor) ? (
|
|
381
416
|
<Icon
|
|
382
|
-
name={isSelected ? 'check-circle' :
|
|
417
|
+
name={isSelected ? 'check-circle' : optionIconName}
|
|
383
418
|
size={15}
|
|
384
|
-
color={isSelected ? accentColor :
|
|
419
|
+
color={isSelected ? accentColor : optionColor}
|
|
385
420
|
/>
|
|
386
421
|
) : null}
|
|
387
|
-
<Text
|
|
422
|
+
<Text
|
|
423
|
+
style={[
|
|
424
|
+
styles.optionText,
|
|
425
|
+
optionColor ? { color: optionColor } : null,
|
|
426
|
+
shouldRenderOptionFallbackText ? styles.optionFallbackText : null,
|
|
427
|
+
]}
|
|
428
|
+
>
|
|
388
429
|
{option.label}
|
|
389
430
|
</Text>
|
|
390
431
|
</TouchableOpacity>
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { getAllStores } from '@store';
|
|
2
|
-
import Formatter from '@controleonline/ui-common/src/utils/formatter.js';
|
|
3
|
-
import { formatStoreColumnValue } from '@controleonline/ui-common/src/react/utils/storeColumns';
|
|
1
|
+
import { getAllStores } from '@store';
|
|
2
|
+
import Formatter from '@controleonline/ui-common/src/utils/formatter.js';
|
|
3
|
+
import { formatStoreColumnValue } from '@controleonline/ui-common/src/react/utils/storeColumns';
|
|
4
|
+
import FeatherGlyphMap from 'react-native-vector-icons/glyphmaps/Feather.json';
|
|
4
5
|
|
|
5
|
-
export const normalizeText = value => String(value ?? '').trim();
|
|
6
|
-
const
|
|
6
|
+
export const normalizeText = value => String(value ?? '').trim();
|
|
7
|
+
export const isValidFeatherIcon = icon => Boolean(FeatherGlyphMap[normalizeText(icon)]);
|
|
8
|
+
const formatHumanLabel = value =>
|
|
7
9
|
normalizeText(value)
|
|
8
10
|
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
9
11
|
.replace(/[_-]+/g, ' ')
|
|
@@ -242,6 +244,15 @@ const resolveFormattedListMeta = (column, rawValue, row) => {
|
|
|
242
244
|
return normalizeObject(formatted);
|
|
243
245
|
};
|
|
244
246
|
|
|
247
|
+
const resolveFormattedValueMeta = (column, rawValue, row) => {
|
|
248
|
+
if (typeof column?.format !== 'function') {
|
|
249
|
+
return {};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const formatted = column.format(rawValue, column, row, false);
|
|
253
|
+
return normalizeObject(formatted);
|
|
254
|
+
};
|
|
255
|
+
|
|
245
256
|
const resolveStyleMeta = (column, row) => {
|
|
246
257
|
if (typeof column?.style !== 'function') {
|
|
247
258
|
return normalizeObject(column?.style);
|
|
@@ -260,6 +271,7 @@ export const resolveCellPresentation = ({
|
|
|
260
271
|
const fieldName = getColumnKey(column);
|
|
261
272
|
const rawValue = value ?? row?.[fieldName];
|
|
262
273
|
const rawMeta = normalizeObject(rawValue);
|
|
274
|
+
const formattedValueMeta = resolveFormattedValueMeta(column, rawValue, row);
|
|
263
275
|
const formattedMeta = resolveFormattedListMeta(column, rawValue, row);
|
|
264
276
|
const styleMeta = resolveStyleMeta(column, row);
|
|
265
277
|
const label = resolveCellText({
|
|
@@ -272,23 +284,29 @@ export const resolveCellPresentation = ({
|
|
|
272
284
|
|
|
273
285
|
return {
|
|
274
286
|
backgroundColor:
|
|
287
|
+
formattedValueMeta.backgroundColor ??
|
|
275
288
|
formattedMeta.backgroundColor ??
|
|
289
|
+
formattedValueMeta.bgColor ??
|
|
276
290
|
formattedMeta.bgColor ??
|
|
277
291
|
rawMeta.backgroundColor ??
|
|
278
292
|
rawMeta.bgColor ??
|
|
279
293
|
styleMeta.backgroundColor ??
|
|
280
294
|
styleMeta.bgColor,
|
|
281
295
|
borderColor:
|
|
296
|
+
formattedValueMeta.borderColor ??
|
|
282
297
|
formattedMeta.borderColor ??
|
|
283
298
|
rawMeta.borderColor ??
|
|
284
299
|
styleMeta.borderColor,
|
|
285
300
|
color:
|
|
301
|
+
formattedValueMeta.color ??
|
|
286
302
|
formattedMeta.color ??
|
|
287
303
|
rawMeta.color ??
|
|
288
304
|
rawMeta.statusColor ??
|
|
289
305
|
rawMeta.categoryColor ??
|
|
290
306
|
styleMeta.color,
|
|
291
307
|
icon:
|
|
308
|
+
formattedValueMeta.icon ??
|
|
309
|
+
formattedValueMeta.iconName ??
|
|
292
310
|
formattedMeta.icon ??
|
|
293
311
|
formattedMeta.iconName ??
|
|
294
312
|
rawMeta.icon ??
|
|
@@ -296,6 +314,9 @@ export const resolveCellPresentation = ({
|
|
|
296
314
|
styleMeta.icon ??
|
|
297
315
|
styleMeta.iconName,
|
|
298
316
|
image:
|
|
317
|
+
formattedValueMeta.image ??
|
|
318
|
+
formattedValueMeta.logo ??
|
|
319
|
+
formattedValueMeta.source ??
|
|
299
320
|
formattedMeta.image ??
|
|
300
321
|
formattedMeta.logo ??
|
|
301
322
|
formattedMeta.source ??
|
|
@@ -77,6 +77,7 @@ const DefaultTable = ({
|
|
|
77
77
|
renderCard = null,
|
|
78
78
|
requestParams = {},
|
|
79
79
|
rowActionsComponent = null,
|
|
80
|
+
rowActionsWidth = null,
|
|
80
81
|
rowStyle = null,
|
|
81
82
|
showColumnFiltersButton = true,
|
|
82
83
|
showRowActions = true,
|
|
@@ -221,6 +222,7 @@ const DefaultTable = ({
|
|
|
221
222
|
requestSort,
|
|
222
223
|
resolvedSort,
|
|
223
224
|
rowActionsComponent,
|
|
225
|
+
rowActionsWidth,
|
|
224
226
|
rowStyle,
|
|
225
227
|
showColumnFiltersButton,
|
|
226
228
|
showRowActions,
|
|
@@ -260,6 +262,7 @@ const DefaultTable = ({
|
|
|
260
262
|
requestSort,
|
|
261
263
|
resolvedSort,
|
|
262
264
|
rowActionsComponent,
|
|
265
|
+
rowActionsWidth,
|
|
263
266
|
rowStyle,
|
|
264
267
|
showColumnFiltersButton,
|
|
265
268
|
showRowActions,
|
|
@@ -279,12 +282,13 @@ const DefaultTable = ({
|
|
|
279
282
|
stableSerialize({
|
|
280
283
|
add,
|
|
281
284
|
compactBreakpoint,
|
|
282
|
-
debugFallbackParameters,
|
|
283
285
|
effectiveViewMode,
|
|
284
286
|
filters,
|
|
285
287
|
forceCardsOnCompact,
|
|
286
288
|
initialViewMode,
|
|
287
289
|
pinRowActions,
|
|
290
|
+
rowActionsComponentType: rowActionsComponent ? typeof rowActionsComponent : '',
|
|
291
|
+
rowActionsWidth,
|
|
288
292
|
resolvedSort,
|
|
289
293
|
showColumnFiltersButton,
|
|
290
294
|
showRowActions,
|
|
@@ -305,6 +309,8 @@ const DefaultTable = ({
|
|
|
305
309
|
forceCardsOnCompact,
|
|
306
310
|
initialViewMode,
|
|
307
311
|
pinRowActions,
|
|
312
|
+
rowActionsComponent,
|
|
313
|
+
rowActionsWidth,
|
|
308
314
|
resolvedSort,
|
|
309
315
|
showColumnFiltersButton,
|
|
310
316
|
showRowActions,
|
|
@@ -445,9 +445,11 @@ const styles = StyleSheet.create({
|
|
|
445
445
|
},
|
|
446
446
|
rowActionsGroup: {
|
|
447
447
|
flexDirection: 'row',
|
|
448
|
+
flexWrap: 'wrap',
|
|
448
449
|
alignItems: 'center',
|
|
449
450
|
justifyContent: 'center',
|
|
450
451
|
gap: 6,
|
|
452
|
+
width: '100%',
|
|
451
453
|
},
|
|
452
454
|
iconButton: {
|
|
453
455
|
width: 28,
|
|
@@ -485,14 +485,44 @@ export const resolveSortComparable = ({ column, row, storeName, columns }) => {
|
|
|
485
485
|
|
|
486
486
|
export const getColumnStyle = column => {
|
|
487
487
|
const key = getColumnKey(column);
|
|
488
|
-
|
|
488
|
+
const customSize = {};
|
|
489
|
+
const columnWidth = Number(column?.width);
|
|
490
|
+
const columnMinWidth = Number(column?.minWidth);
|
|
491
|
+
const columnFlexBasis = Number(column?.flexBasis);
|
|
492
|
+
|
|
493
|
+
if (Number.isFinite(columnWidth) && columnWidth > 0) {
|
|
494
|
+
customSize.width = columnWidth;
|
|
495
|
+
customSize.flexBasis = columnWidth;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (Number.isFinite(columnMinWidth) && columnMinWidth > 0) {
|
|
499
|
+
customSize.minWidth = columnMinWidth;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (Number.isFinite(columnFlexBasis) && columnFlexBasis > 0) {
|
|
503
|
+
customSize.flexBasis = columnFlexBasis;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const columnSizeStyle = Object.keys(customSize).length ? customSize : null;
|
|
507
|
+
|
|
508
|
+
if (column?.isIdentity) return [styles.cell, styles.identityCell, columnSizeStyle];
|
|
489
509
|
if (['price', 'total', 'amount', 'value'].includes(key)) {
|
|
490
|
-
return [styles.cell, styles.moneyCell];
|
|
510
|
+
return [styles.cell, styles.moneyCell, columnSizeStyle];
|
|
491
511
|
}
|
|
492
|
-
return styles.cell;
|
|
512
|
+
return [styles.cell, columnSizeStyle];
|
|
493
513
|
};
|
|
494
514
|
|
|
495
515
|
export const getColumnMinWidth = column => {
|
|
516
|
+
const columnMinWidth = Number(column?.minWidth);
|
|
517
|
+
if (Number.isFinite(columnMinWidth) && columnMinWidth > 0) {
|
|
518
|
+
return columnMinWidth;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const columnWidth = Number(column?.width);
|
|
522
|
+
if (Number.isFinite(columnWidth) && columnWidth > 0) {
|
|
523
|
+
return columnWidth;
|
|
524
|
+
}
|
|
525
|
+
|
|
496
526
|
const key = getColumnKey(column);
|
|
497
527
|
if (column?.isIdentity) return IDENTITY_CELL_MIN_WIDTH;
|
|
498
528
|
if (['price', 'total', 'amount', 'value'].includes(key)) {
|
|
@@ -5,6 +5,9 @@ import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/util
|
|
|
5
5
|
import { getColumnKey, resolveCellText } from '../inputs/defaultInputUtils';
|
|
6
6
|
import DefaultTableEmptyState from './DefaultTableEmptyState';
|
|
7
7
|
import DefaultTableInput from './DefaultTableInput';
|
|
8
|
+
import DefaultTableRowActions, {
|
|
9
|
+
hasDefaultTableRowActionsComponent,
|
|
10
|
+
} from './DefaultTableRowActions';
|
|
8
11
|
import {
|
|
9
12
|
END_REACHED_THRESHOLD,
|
|
10
13
|
getRowKey,
|
|
@@ -63,14 +66,20 @@ const DefaultTableCards = ({ storeName }) => {
|
|
|
63
66
|
};
|
|
64
67
|
const RowActionsComponent = configs.rowActionsComponent;
|
|
65
68
|
const hasRowPress = typeof configs.onRowPress === 'function';
|
|
66
|
-
const hasCustomRowActions =
|
|
69
|
+
const hasCustomRowActions = hasDefaultTableRowActionsComponent(RowActionsComponent);
|
|
67
70
|
const hasEditAction = typeof configs.onEditRow === 'function';
|
|
68
71
|
const hasRowActions = configs.showRowActions !== false && (hasCustomRowActions || hasEditAction);
|
|
69
72
|
const customRowActions = hasCustomRowActions ? (
|
|
70
|
-
<
|
|
73
|
+
<DefaultTableRowActions
|
|
74
|
+
component={RowActionsComponent}
|
|
75
|
+
helpers={{
|
|
76
|
+
openEdit: () => configs.onEditRow?.(row),
|
|
77
|
+
openRow: hasRowPress ? () => configs.onRowPress(row) : null,
|
|
78
|
+
}}
|
|
71
79
|
openEdit={() => configs.onEditRow?.(row)}
|
|
72
80
|
openRow={hasRowPress ? () => configs.onRowPress(row) : null}
|
|
73
81
|
row={row}
|
|
82
|
+
storeName={storeName}
|
|
74
83
|
/>
|
|
75
84
|
) : null;
|
|
76
85
|
const editButton = hasEditAction ? (
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_ROW_ACTIONS_WIDTH = 96;
|
|
4
|
+
|
|
5
|
+
const isReactComponentType = value =>
|
|
6
|
+
value &&
|
|
7
|
+
typeof value === 'object' &&
|
|
8
|
+
typeof value.$$typeof === 'symbol';
|
|
9
|
+
|
|
10
|
+
export const hasDefaultTableRowActionsComponent = component =>
|
|
11
|
+
typeof component === 'function' ||
|
|
12
|
+
React.isValidElement(component) ||
|
|
13
|
+
isReactComponentType(component);
|
|
14
|
+
|
|
15
|
+
export const resolveDefaultTableRowActionsWidth = configs => {
|
|
16
|
+
const rawWidth = configs?.rowActionsWidth ?? configs?.actionsColumnWidth;
|
|
17
|
+
const width = Number(rawWidth);
|
|
18
|
+
|
|
19
|
+
if (Number.isFinite(width) && width > 0) {
|
|
20
|
+
return width;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return DEFAULT_ROW_ACTIONS_WIDTH;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const DefaultTableRowActions = ({ component, ...props }) => {
|
|
27
|
+
if (React.isValidElement(component)) {
|
|
28
|
+
return React.cloneElement(component, {
|
|
29
|
+
...(component.props || {}),
|
|
30
|
+
...props,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (hasDefaultTableRowActionsComponent(component)) {
|
|
35
|
+
return React.createElement(component, props);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return null;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default DefaultTableRowActions;
|
|
@@ -5,8 +5,12 @@ import { useStore } from '@store';
|
|
|
5
5
|
import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
|
|
6
6
|
import { getColumnKey } from '../inputs/defaultInputUtils';
|
|
7
7
|
import DefaultColumnFilter from '../filters/DefaultColumnFilter';
|
|
8
|
-
import DefaultTableEmptyState from './DefaultTableEmptyState';
|
|
9
|
-
import DefaultTableInput from './DefaultTableInput';
|
|
8
|
+
import DefaultTableEmptyState from './DefaultTableEmptyState';
|
|
9
|
+
import DefaultTableInput from './DefaultTableInput';
|
|
10
|
+
import DefaultTableRowActions, {
|
|
11
|
+
hasDefaultTableRowActionsComponent,
|
|
12
|
+
resolveDefaultTableRowActionsWidth,
|
|
13
|
+
} from './DefaultTableRowActions';
|
|
10
14
|
import {
|
|
11
15
|
END_REACHED_THRESHOLD,
|
|
12
16
|
getColumnStyle,
|
|
@@ -48,14 +52,14 @@ const DefaultTableRows = ({ storeName }) => {
|
|
|
48
52
|
const tableTextColor = palette.text;
|
|
49
53
|
const rowStyle = configs.rowStyle;
|
|
50
54
|
const hasRowPress = typeof configs.onRowPress === 'function';
|
|
51
|
-
const hasCustomRowActions =
|
|
52
|
-
const hasEditAction = typeof configs.onEditRow === 'function';
|
|
53
|
-
const hasRowActions = configs.showRowActions !== false && (hasCustomRowActions || hasEditAction);
|
|
55
|
+
const hasCustomRowActions = hasDefaultTableRowActionsComponent(configs.rowActionsComponent);
|
|
56
|
+
const hasEditAction = typeof configs.onEditRow === 'function';
|
|
57
|
+
const hasRowActions = configs.showRowActions !== false && (hasCustomRowActions || hasEditAction);
|
|
54
58
|
const shouldPinRowActions = configs.pinRowActions !== false;
|
|
55
59
|
const hasColumnFilters =
|
|
56
60
|
configs.showColumnFiltersButton !== false &&
|
|
57
61
|
configs.tableFiltersVisible === true;
|
|
58
|
-
const actionsCellWidth = hasRowActions ?
|
|
62
|
+
const actionsCellWidth = hasRowActions ? resolveDefaultTableRowActionsWidth(configs) : 0;
|
|
59
63
|
const [horizontalMetrics, setHorizontalMetrics] = useState({
|
|
60
64
|
contentWidth: 0,
|
|
61
65
|
viewportWidth: 0,
|
|
@@ -179,13 +183,19 @@ const DefaultTableRows = ({ storeName }) => {
|
|
|
179
183
|
]}
|
|
180
184
|
>
|
|
181
185
|
<View style={styles.rowActionsGroup}>
|
|
182
|
-
{hasCustomRowActions ? (
|
|
183
|
-
<
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
186
|
+
{hasCustomRowActions ? (
|
|
187
|
+
<DefaultTableRowActions
|
|
188
|
+
component={RowActionsComponent}
|
|
189
|
+
helpers={{
|
|
190
|
+
openEdit: () => configs.onEditRow?.(row),
|
|
191
|
+
openRow: hasRowPress ? () => configs.onRowPress(row) : null,
|
|
192
|
+
}}
|
|
193
|
+
openEdit={() => configs.onEditRow?.(row)}
|
|
194
|
+
openRow={hasRowPress ? () => configs.onRowPress(row) : null}
|
|
195
|
+
row={row}
|
|
196
|
+
storeName={storeName}
|
|
197
|
+
/>
|
|
198
|
+
) : null}
|
|
189
199
|
{hasEditAction ? (
|
|
190
200
|
<TouchableOpacity
|
|
191
201
|
style={[styles.iconButton, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}
|
|
@@ -182,4 +182,76 @@ describe('DefaultSelect', () => {
|
|
|
182
182
|
}),
|
|
183
183
|
});
|
|
184
184
|
});
|
|
185
|
+
|
|
186
|
+
it('keeps the status label visible when the row status has color but no icon', async () => {
|
|
187
|
+
global.t = {
|
|
188
|
+
t: jest.fn(() => 'Waiting payment'),
|
|
189
|
+
};
|
|
190
|
+
let tree;
|
|
191
|
+
|
|
192
|
+
await renderer.act(async () => {
|
|
193
|
+
tree = renderer.create(
|
|
194
|
+
React.createElement(DefaultSelect, {
|
|
195
|
+
column: {
|
|
196
|
+
name: 'status',
|
|
197
|
+
label: 'Status',
|
|
198
|
+
list: 'status/getItems',
|
|
199
|
+
translate: false,
|
|
200
|
+
format(value) {
|
|
201
|
+
return {
|
|
202
|
+
value: value.id,
|
|
203
|
+
label: global.t.t('orders', 'status', value.status),
|
|
204
|
+
color: value.color,
|
|
205
|
+
};
|
|
206
|
+
},
|
|
207
|
+
formatList(value) {
|
|
208
|
+
return {
|
|
209
|
+
value: value.id,
|
|
210
|
+
label: global.t.t('orders', 'status', value.status),
|
|
211
|
+
color: value.color,
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
row: {
|
|
216
|
+
status: {
|
|
217
|
+
id: 6,
|
|
218
|
+
status: 'waiting payment',
|
|
219
|
+
color: '#000000',
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
storeName: 'orders',
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const visibleStatus = tree.root
|
|
228
|
+
.findAllByType('Text')
|
|
229
|
+
.find(node => node.props.children === 'Waiting payment');
|
|
230
|
+
|
|
231
|
+
expect(visibleStatus).toBeTruthy();
|
|
232
|
+
expect(visibleStatus.props.style).toEqual(
|
|
233
|
+
expect.arrayContaining([
|
|
234
|
+
expect.objectContaining({
|
|
235
|
+
flex: 1,
|
|
236
|
+
fontSize: 13,
|
|
237
|
+
fontWeight: '600',
|
|
238
|
+
}),
|
|
239
|
+
{color: '#000000'},
|
|
240
|
+
]),
|
|
241
|
+
);
|
|
242
|
+
expect(visibleStatus.props.style).not.toEqual(
|
|
243
|
+
expect.arrayContaining([
|
|
244
|
+
expect.objectContaining({
|
|
245
|
+
backgroundColor: expect.any(String),
|
|
246
|
+
}),
|
|
247
|
+
]),
|
|
248
|
+
);
|
|
249
|
+
expect(visibleStatus.props.style).not.toEqual(
|
|
250
|
+
expect.arrayContaining([
|
|
251
|
+
expect.objectContaining({
|
|
252
|
+
borderColor: expect.any(String),
|
|
253
|
+
}),
|
|
254
|
+
]),
|
|
255
|
+
);
|
|
256
|
+
});
|
|
185
257
|
});
|
|
@@ -401,6 +401,61 @@ describe('DefaultTable', () => {
|
|
|
401
401
|
);
|
|
402
402
|
});
|
|
403
403
|
|
|
404
|
+
it('renders a custom row actions component with the configured actions width', () => {
|
|
405
|
+
mockWindowDimensions = {width: 1200, height: 800};
|
|
406
|
+
mockStores.orders = {
|
|
407
|
+
actions: {},
|
|
408
|
+
getters: {
|
|
409
|
+
columns: [
|
|
410
|
+
{key: 'id', label: 'ID', isIdentity: true},
|
|
411
|
+
{key: 'name', label: 'Nome'},
|
|
412
|
+
],
|
|
413
|
+
items: [{id: 12, name: 'Pedido'}],
|
|
414
|
+
visibleColumns: {},
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
const rowActionsComponent = props =>
|
|
418
|
+
React.createElement('row-actions', props, props.row.name);
|
|
419
|
+
let tree;
|
|
420
|
+
|
|
421
|
+
renderer.act(() => {
|
|
422
|
+
tree = renderer.create(
|
|
423
|
+
React.createElement(DefaultTable, {
|
|
424
|
+
onEditRow: jest.fn(),
|
|
425
|
+
rowActionsComponent,
|
|
426
|
+
rowActionsWidth: 180,
|
|
427
|
+
storeName: 'orders',
|
|
428
|
+
}),
|
|
429
|
+
);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const rowActions = tree.root.findByType('row-actions');
|
|
433
|
+
expect(rowActions.props.row).toEqual({id: 12, name: 'Pedido'});
|
|
434
|
+
expect(rowActions.props.storeName).toBe('orders');
|
|
435
|
+
expect(typeof rowActions.props.openEdit).toBe('function');
|
|
436
|
+
expect(rowActions.props.helpers).toEqual(
|
|
437
|
+
expect.objectContaining({
|
|
438
|
+
openEdit: expect.any(Function),
|
|
439
|
+
}),
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
const renderedStyles = [
|
|
443
|
+
...tree.root.findAllByType('View'),
|
|
444
|
+
...tree.root.findAllByType('TouchableOpacity'),
|
|
445
|
+
].map(node => flattenStyle(node.props.style));
|
|
446
|
+
|
|
447
|
+
expect(renderedStyles).toEqual(
|
|
448
|
+
expect.arrayContaining([
|
|
449
|
+
expect.objectContaining({
|
|
450
|
+
flexBasis: 180,
|
|
451
|
+
maxWidth: 180,
|
|
452
|
+
minWidth: 180,
|
|
453
|
+
width: 180,
|
|
454
|
+
}),
|
|
455
|
+
]),
|
|
456
|
+
);
|
|
457
|
+
});
|
|
458
|
+
|
|
404
459
|
it('keeps only the identity column pinned when row actions are not pinned', () => {
|
|
405
460
|
mockWindowDimensions = {width: 1200, height: 800};
|
|
406
461
|
mockStores.orders = {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const {describe, expect, it} = require('@jest/globals');
|
|
2
|
+
|
|
3
|
+
jest.mock('@store', () => ({
|
|
4
|
+
getAllStores: () => ({}),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
jest.mock('react-native', () => ({
|
|
8
|
+
Platform: {
|
|
9
|
+
select: value => value.web || value.default,
|
|
10
|
+
},
|
|
11
|
+
StyleSheet: {
|
|
12
|
+
create: value => value,
|
|
13
|
+
},
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
jest.mock('@controleonline/ui-common/src/utils/formatter.js', () => ({}));
|
|
17
|
+
jest.mock('@controleonline/ui-common/src/react/utils/dateRangeFilter', () => ({
|
|
18
|
+
getDateRange: () => ({}),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
getColumnMinWidth,
|
|
23
|
+
getColumnStyle,
|
|
24
|
+
} = require('../../../../react/components/table/DefaultTable.utils');
|
|
25
|
+
|
|
26
|
+
describe('DefaultTable utils', () => {
|
|
27
|
+
it('uses column width metadata from the store configuration', () => {
|
|
28
|
+
expect(getColumnMinWidth({name: 'status', minWidth: 180})).toBe(180);
|
|
29
|
+
expect(getColumnStyle({name: 'status', minWidth: 180})).toEqual(
|
|
30
|
+
expect.arrayContaining([
|
|
31
|
+
expect.objectContaining({
|
|
32
|
+
minWidth: 180,
|
|
33
|
+
}),
|
|
34
|
+
]),
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const {describe, expect, it, jest} = require('@jest/globals');
|
|
2
|
-
|
|
3
|
-
jest.mock('@store', () => ({
|
|
4
|
-
getAllStores: () => ({}),
|
|
5
|
-
}));
|
|
1
|
+
const {describe, expect, it, jest} = require('@jest/globals');
|
|
2
|
+
|
|
3
|
+
jest.mock('@store', () => ({
|
|
4
|
+
getAllStores: () => ({}),
|
|
5
|
+
}));
|
|
6
6
|
|
|
7
7
|
jest.mock('@controleonline/ui-common/src/utils/formatter.js', () => ({}));
|
|
8
8
|
|
|
@@ -16,7 +16,7 @@ const {
|
|
|
16
16
|
resolveCellPresentation,
|
|
17
17
|
} = require('../../../react/components/inputs/defaultInputUtils');
|
|
18
18
|
|
|
19
|
-
describe('defaultInputUtils', () => {
|
|
19
|
+
describe('defaultInputUtils', () => {
|
|
20
20
|
it('deduplicates list options by normalized key', () => {
|
|
21
21
|
const column = {
|
|
22
22
|
formatList(item) {
|
|
@@ -100,6 +100,38 @@ describe('defaultInputUtils', () => {
|
|
|
100
100
|
});
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
it('resolves table cell presentation from the column format object', () => {
|
|
104
|
+
const presentation = resolveCellPresentation({
|
|
105
|
+
column: {
|
|
106
|
+
name: 'status',
|
|
107
|
+
format(value) {
|
|
108
|
+
return {
|
|
109
|
+
label: value.status,
|
|
110
|
+
color: value.color,
|
|
111
|
+
icon: value.icon,
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
row: {
|
|
116
|
+
status: {
|
|
117
|
+
status: 'Preparando',
|
|
118
|
+
color: '#0EA5E9',
|
|
119
|
+
icon: 'clock',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
storeName: 'orders',
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(presentation).toEqual({
|
|
126
|
+
backgroundColor: undefined,
|
|
127
|
+
borderColor: undefined,
|
|
128
|
+
color: '#0EA5E9',
|
|
129
|
+
icon: 'clock',
|
|
130
|
+
image: undefined,
|
|
131
|
+
label: 'Preparando',
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
103
135
|
it('builds subtle badge colors from resource colors', () => {
|
|
104
136
|
expect(buildReadPresentationStyles({
|
|
105
137
|
color: '#16A34A',
|