@controleonline/ui-default 1.0.263 → 1.0.264

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/AGENTS.md +48 -47
  2. package/package.json +3 -6
  3. package/src/react/components/errors/DefaultErrors.js +360 -360
  4. package/src/react/components/files/DefaultFile.js +46 -46
  5. package/src/react/components/filters/CompactFilterSelector.js +22 -3
  6. package/src/react/components/filters/DefaultColumnFilter.js +15 -13
  7. package/src/react/components/filters/DefaultExternalFilters.js +44 -39
  8. package/src/react/components/filters/DefaultSearch.js +140 -103
  9. package/src/react/components/filters/DefaultSearch.styles.js +65 -70
  10. package/src/react/components/form/DefaultForm.js +200 -198
  11. package/src/react/components/help/DefaultTooltip.js +87 -87
  12. package/src/react/components/help/DefaultTooltip.styles.js +61 -61
  13. package/src/react/components/inputs/DefaultInput.js +162 -160
  14. package/src/react/components/inputs/DefaultSelect.js +203 -192
  15. package/src/react/components/map/DefaultGoogleMap.styles.js +115 -9
  16. package/src/react/components/map/DefaultGoogleMap.web.js +560 -446
  17. package/src/react/components/map/DefaultMap.native.js +73 -71
  18. package/src/react/components/map/DefaultMap.styles.js +16 -16
  19. package/src/react/components/map/DefaultMap.web.js +68 -66
  20. package/src/react/components/map/DefaultNativeMap.native.js +307 -183
  21. package/src/react/components/map/DefaultNativeMap.shared.js +439 -340
  22. package/src/react/components/map/DefaultNativeMap.styles.js +156 -122
  23. package/src/react/components/table/DefaultTable.js +529 -355
  24. package/src/react/components/table/DefaultTable.styles.js +30 -39
  25. package/src/react/index.js +10 -10
  26. package/src/tests/react/components/errors/DefaultErrors.test.js +162 -162
  27. package/src/tests/react/components/files/DefaultFile.test.js +80 -80
  28. package/src/tests/react/components/inputs/DefaultSelect.test.js +93 -0
  29. package/src/tests/react/components/table/DefaultTable.test.js +334 -0
@@ -1,198 +1,200 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
- import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
3
- import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
4
- import DefaultInput from '../inputs/DefaultInput';
5
- import {
6
- formatSaveValue,
7
- getColumnKey,
8
- isEditableColumn,
9
- normalizeId,
10
- normalizeText,
11
- resolveEditValue,
12
- } from '../inputs/defaultInputUtils';
13
- import styles from './DefaultForm.styles';
14
-
15
- const hasValue = value => {
16
- if (value === null || value === undefined) return false;
17
- if (typeof value === 'boolean' || typeof value === 'number') return true;
18
- if (typeof value === 'object') {
19
- return normalizeText(value.value ?? value.id ?? value['@id'] ?? '') !== '';
20
- }
21
-
22
- return normalizeText(value) !== '';
23
- };
24
-
25
- const resolveDefaultValue = (column, row) => {
26
- const fieldName = getColumnKey(column);
27
- if (row && row[fieldName] !== undefined) return row[fieldName];
28
- if (typeof column?.defaultValue === 'function') return column.defaultValue(row, column);
29
- if (column?.defaultValue !== undefined) return column.defaultValue;
30
- if (typeof column?.initialValue === 'function') return column.initialValue(row, column);
31
- if (column?.initialValue !== undefined) return column.initialValue;
32
- return undefined;
33
- };
34
-
35
- const shouldIncludeColumn = (column, row, mode) => {
36
- const fieldName = getColumnKey(column);
37
- if (!fieldName || column?.isIdentity === true) return false;
38
- if (column?.form === false || column?.multiline === true) return false;
39
- if (mode === 'create' && column?.add === false) return false;
40
- if (mode !== 'create' && !isEditableColumn(column)) return false;
41
- if (typeof column?.visibleForm === 'function') {
42
- return column.visibleForm(row, column) !== false;
43
- }
44
-
45
- return column?.visibleForm !== false;
46
- };
47
-
48
- const buildDraft = (columns, row) =>
49
- columns.reduce((draft, column) => {
50
- const fieldName = getColumnKey(column);
51
- const defaultValue = resolveDefaultValue(column, row);
52
- draft[fieldName] = resolveEditValue(column, row, defaultValue);
53
- return draft;
54
- }, {});
55
-
56
- const DefaultForm = ({
57
- accentColor = '#2563EB',
58
- actions = {},
59
- columns = [],
60
- getOptionsForColumn = null,
61
- mode = 'edit',
62
- onCancel = null,
63
- onSaved = null,
64
- row = {},
65
- storeName = '',
66
- }) => {
67
- const formColumns = useMemo(
68
- () => columns.filter(column => shouldIncludeColumn(column, row, mode)),
69
- [columns, mode, row],
70
- );
71
- const [draft, setDraft] = useState(() => buildDraft(formColumns, row));
72
- const [isSaving, setIsSaving] = useState(false);
73
-
74
- useEffect(() => {
75
- setDraft(buildDraft(formColumns, row));
76
- }, [formColumns, row]);
77
-
78
- const save = useCallback(() => {
79
- if (typeof actions.save !== 'function') {
80
- onCancel?.();
81
- return Promise.resolve(null);
82
- }
83
-
84
- const isCreate = mode === 'create';
85
- const payload = isCreate
86
- ? {}
87
- : { id: normalizeId(row?.['@id'] || row?.id) };
88
-
89
- formColumns.forEach(column => {
90
- const fieldName = getColumnKey(column);
91
- const nextValue = draft[fieldName];
92
-
93
- if (isCreate) {
94
- if (hasValue(nextValue)) {
95
- payload[fieldName] = formatSaveValue(column, nextValue, row);
96
- }
97
- return;
98
- }
99
-
100
- const currentValue = resolveEditValue(column, row);
101
- if (normalizeText(currentValue) !== normalizeText(nextValue)) {
102
- payload[fieldName] = formatSaveValue(column, nextValue, row);
103
- }
104
- });
105
-
106
- const payloadKeys = Object.keys(payload);
107
- if ((isCreate && payloadKeys.length === 0) || (!isCreate && payloadKeys.length <= 1)) {
108
- onCancel?.();
109
- return Promise.resolve(null);
110
- }
111
-
112
- setIsSaving(true);
113
-
114
- return actions.save(payload)
115
- .then(savedItem => {
116
- onSaved?.(savedItem || { ...row, ...draft }, row);
117
- return savedItem;
118
- })
119
- .catch(error => {
120
- console.error(error);
121
- return null;
122
- })
123
- .finally(() => {
124
- setIsSaving(false);
125
- });
126
- }, [actions, draft, formColumns, mode, onCancel, onSaved, row]);
127
-
128
- return (
129
- <>
130
- <ScrollView style={styles.body} keyboardShouldPersistTaps="handled">
131
- {formColumns.length > 0 ? (
132
- <View style={styles.formGrid}>
133
- {formColumns.map(column => {
134
- const fieldName = getColumnKey(column);
135
- const label = formatStoreColumnLabel({
136
- columns,
137
- fieldName,
138
- fallbackLabel: column?.label || fieldName,
139
- storeName,
140
- });
141
-
142
- return (
143
- <View key={fieldName} style={styles.formField}>
144
- <DefaultInput
145
- accentColor={accentColor}
146
- autoFocus={false}
147
- autoSave={false}
148
- column={column}
149
- columns={columns}
150
- editing
151
- getOptionsForColumn={getOptionsForColumn}
152
- label={label}
153
- onChangeValue={value => setDraft(prev => ({ ...prev, [fieldName]: value }))}
154
- row={row}
155
- showLabel
156
- storeName={storeName}
157
- value={draft[fieldName]}
158
- variant="form"
159
- />
160
- </View>
161
- );
162
- })}
163
- </View>
164
- ) : (
165
- <View style={styles.emptyBox}>
166
- <Text style={styles.emptyText}>
167
- {global.t?.t(storeName, 'label', 'no_fields') || 'Nenhum campo disponivel'}
168
- </Text>
169
- </View>
170
- )}
171
- </ScrollView>
172
-
173
- <View style={styles.actions}>
174
- <TouchableOpacity style={styles.secondaryButton} onPress={onCancel}>
175
- <Text style={styles.secondaryButtonText}>
176
- {global.t?.t(storeName, 'button', 'cancel') || 'Cancelar'}
177
- </Text>
178
- </TouchableOpacity>
179
- <TouchableOpacity
180
- style={[
181
- styles.primaryButton,
182
- { backgroundColor: accentColor },
183
- isSaving ? styles.primaryButtonDisabled : null,
184
- ]}
185
- activeOpacity={0.86}
186
- disabled={isSaving}
187
- onPress={save}
188
- >
189
- <Text style={styles.primaryButtonText}>
190
- {isSaving ? 'Salvando' : global.t?.t(storeName, 'button', 'save') || 'Salvar'}
191
- </Text>
192
- </TouchableOpacity>
193
- </View>
194
- </>
195
- );
196
- };
197
-
198
- export default DefaultForm;
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
3
+ import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
4
+ import DefaultInput from '../inputs/DefaultInput';
5
+ import {
6
+ formatSaveValue,
7
+ getColumnKey,
8
+ isEditableColumn,
9
+ normalizeId,
10
+ normalizeText,
11
+ resolveEditValue,
12
+ } from '../inputs/defaultInputUtils';
13
+ import styles from './DefaultForm.styles';
14
+
15
+ const hasValue = value => {
16
+ if (value === null || value === undefined) return false;
17
+ if (typeof value === 'boolean' || typeof value === 'number') return true;
18
+ if (typeof value === 'object') {
19
+ return normalizeText(value.value ?? value.id ?? value['@id'] ?? '') !== '';
20
+ }
21
+
22
+ return normalizeText(value) !== '';
23
+ };
24
+
25
+ const resolveDefaultValue = (column, row) => {
26
+ const fieldName = getColumnKey(column);
27
+ if (row && row[fieldName] !== undefined) return row[fieldName];
28
+ if (typeof column?.defaultValue === 'function') return column.defaultValue(row, column);
29
+ if (column?.defaultValue !== undefined) return column.defaultValue;
30
+ if (typeof column?.initialValue === 'function') return column.initialValue(row, column);
31
+ if (column?.initialValue !== undefined) return column.initialValue;
32
+ return undefined;
33
+ };
34
+
35
+ const shouldIncludeColumn = (column, row, mode) => {
36
+ const fieldName = getColumnKey(column);
37
+ if (!fieldName || column?.isIdentity === true) return false;
38
+ if (column?.form === false || column?.multiline === true) return false;
39
+ if (mode === 'create' && column?.add === false) return false;
40
+ if (mode !== 'create' && !isEditableColumn(column)) return false;
41
+ if (typeof column?.visibleForm === 'function') {
42
+ return column.visibleForm(row, column) !== false;
43
+ }
44
+
45
+ return column?.visibleForm !== false;
46
+ };
47
+
48
+ const buildDraft = (columns, row) =>
49
+ columns.reduce((draft, column) => {
50
+ const fieldName = getColumnKey(column);
51
+ const defaultValue = resolveDefaultValue(column, row);
52
+ draft[fieldName] = resolveEditValue(column, row, defaultValue);
53
+ return draft;
54
+ }, {});
55
+
56
+ const DefaultForm = ({
57
+ accentColor = '#2563EB',
58
+ actions = {},
59
+ columns = [],
60
+ getOptionsForColumn = null,
61
+ mode = 'edit',
62
+ onBeforeOpen = null,
63
+ onCancel = null,
64
+ onSaved = null,
65
+ row = {},
66
+ storeName = '',
67
+ }) => {
68
+ const formColumns = useMemo(
69
+ () => columns.filter(column => shouldIncludeColumn(column, row, mode)),
70
+ [columns, mode, row],
71
+ );
72
+ const [draft, setDraft] = useState(() => buildDraft(formColumns, row));
73
+ const [isSaving, setIsSaving] = useState(false);
74
+
75
+ useEffect(() => {
76
+ setDraft(buildDraft(formColumns, row));
77
+ }, [formColumns, row]);
78
+
79
+ const save = useCallback(() => {
80
+ if (typeof actions.save !== 'function') {
81
+ onCancel?.();
82
+ return Promise.resolve(null);
83
+ }
84
+
85
+ const isCreate = mode === 'create';
86
+ const payload = isCreate
87
+ ? {}
88
+ : { id: normalizeId(row?.['@id'] || row?.id) };
89
+
90
+ formColumns.forEach(column => {
91
+ const fieldName = getColumnKey(column);
92
+ const nextValue = draft[fieldName];
93
+
94
+ if (isCreate) {
95
+ if (hasValue(nextValue)) {
96
+ payload[fieldName] = formatSaveValue(column, nextValue, row);
97
+ }
98
+ return;
99
+ }
100
+
101
+ const currentValue = resolveEditValue(column, row);
102
+ if (normalizeText(currentValue) !== normalizeText(nextValue)) {
103
+ payload[fieldName] = formatSaveValue(column, nextValue, row);
104
+ }
105
+ });
106
+
107
+ const payloadKeys = Object.keys(payload);
108
+ if ((isCreate && payloadKeys.length === 0) || (!isCreate && payloadKeys.length <= 1)) {
109
+ onCancel?.();
110
+ return Promise.resolve(null);
111
+ }
112
+
113
+ setIsSaving(true);
114
+
115
+ return actions.save(payload)
116
+ .then(savedItem => {
117
+ onSaved?.(savedItem || { ...row, ...draft }, row);
118
+ return savedItem;
119
+ })
120
+ .catch(error => {
121
+ console.error(error);
122
+ return null;
123
+ })
124
+ .finally(() => {
125
+ setIsSaving(false);
126
+ });
127
+ }, [actions, draft, formColumns, mode, onCancel, onSaved, row]);
128
+
129
+ return (
130
+ <>
131
+ <ScrollView style={styles.body} keyboardShouldPersistTaps="handled">
132
+ {formColumns.length > 0 ? (
133
+ <View style={styles.formGrid}>
134
+ {formColumns.map(column => {
135
+ const fieldName = getColumnKey(column);
136
+ const label = formatStoreColumnLabel({
137
+ columns,
138
+ fieldName,
139
+ fallbackLabel: column?.label || fieldName,
140
+ storeName,
141
+ });
142
+
143
+ return (
144
+ <View key={fieldName} style={styles.formField}>
145
+ <DefaultInput
146
+ accentColor={accentColor}
147
+ autoFocus={false}
148
+ autoSave={false}
149
+ column={column}
150
+ columns={columns}
151
+ editing
152
+ getOptionsForColumn={getOptionsForColumn}
153
+ label={label}
154
+ onBeforeOpen={onBeforeOpen ? () => onBeforeOpen(column) : null}
155
+ onChangeValue={value => setDraft(prev => ({ ...prev, [fieldName]: value }))}
156
+ row={row}
157
+ showLabel
158
+ storeName={storeName}
159
+ value={draft[fieldName]}
160
+ variant="form"
161
+ />
162
+ </View>
163
+ );
164
+ })}
165
+ </View>
166
+ ) : (
167
+ <View style={styles.emptyBox}>
168
+ <Text style={styles.emptyText}>
169
+ {global.t?.t(storeName, 'label', 'no_fields') || 'Nenhum campo disponivel'}
170
+ </Text>
171
+ </View>
172
+ )}
173
+ </ScrollView>
174
+
175
+ <View style={styles.actions}>
176
+ <TouchableOpacity style={styles.secondaryButton} onPress={onCancel}>
177
+ <Text style={styles.secondaryButtonText}>
178
+ {global.t?.t(storeName, 'button', 'cancel') || 'Cancelar'}
179
+ </Text>
180
+ </TouchableOpacity>
181
+ <TouchableOpacity
182
+ style={[
183
+ styles.primaryButton,
184
+ { backgroundColor: accentColor },
185
+ isSaving ? styles.primaryButtonDisabled : null,
186
+ ]}
187
+ activeOpacity={0.86}
188
+ disabled={isSaving}
189
+ onPress={save}
190
+ >
191
+ <Text style={styles.primaryButtonText}>
192
+ {isSaving ? 'Salvando' : global.t?.t(storeName, 'button', 'save') || 'Salvar'}
193
+ </Text>
194
+ </TouchableOpacity>
195
+ </View>
196
+ </>
197
+ );
198
+ };
199
+
200
+ export default DefaultForm;
@@ -1,87 +1,87 @@
1
- import React, {useCallback, useState} from 'react';
2
- import {Modal, Pressable, Text, TouchableOpacity, View} from 'react-native';
3
- import styles from './DefaultTooltip.styles';
4
-
5
- const DefaultTooltip = ({
6
- accentColor = '#0EA5E9',
7
- label = '?',
8
- message = '',
9
- style = null,
10
- textStyle = null,
11
- title = 'Ajuda',
12
- }) => {
13
- const [visible, setVisible] = useState(false);
14
-
15
- const openTooltip = useCallback(() => {
16
- if (!message) {
17
- return;
18
- }
19
-
20
- setVisible(true);
21
- }, [message]);
22
-
23
- const closeTooltip = useCallback(() => {
24
- setVisible(false);
25
- }, []);
26
-
27
- return (
28
- <>
29
- <TouchableOpacity
30
- accessibilityRole="button"
31
- activeOpacity={0.85}
32
- disabled={!message}
33
- onPress={openTooltip}
34
- style={[
35
- styles.button,
36
- {
37
- borderColor: accentColor,
38
- backgroundColor: `${accentColor}14`,
39
- },
40
- !message && {opacity: 0.55},
41
- style,
42
- ]}>
43
- <Text style={[styles.label, {color: accentColor}, textStyle]}>
44
- {label}
45
- </Text>
46
- </TouchableOpacity>
47
-
48
- <Modal
49
- animationType="fade"
50
- onRequestClose={closeTooltip}
51
- transparent
52
- visible={visible}>
53
- <Pressable style={styles.overlay} onPress={closeTooltip}>
54
- <View
55
- onStartShouldSetResponder={() => true}
56
- style={[
57
- styles.card,
58
- {
59
- borderColor: accentColor,
60
- },
61
- ]}>
62
- <View style={styles.cardHeader}>
63
- <Text style={[styles.title, {color: accentColor}]}>{title}</Text>
64
- <TouchableOpacity
65
- accessibilityRole="button"
66
- activeOpacity={0.8}
67
- onPress={closeTooltip}
68
- style={[
69
- styles.closeButton,
70
- {
71
- borderColor: accentColor,
72
- },
73
- ]}>
74
- <Text style={[styles.closeButtonText, {color: accentColor}]}>
75
- Fechar
76
- </Text>
77
- </TouchableOpacity>
78
- </View>
79
- <Text style={styles.message}>{message}</Text>
80
- </View>
81
- </Pressable>
82
- </Modal>
83
- </>
84
- );
85
- };
86
-
87
- export default DefaultTooltip;
1
+ import React, {useCallback, useState} from 'react';
2
+ import {Modal, Pressable, Text, TouchableOpacity, View} from 'react-native';
3
+ import styles from './DefaultTooltip.styles';
4
+
5
+ const DefaultTooltip = ({
6
+ accentColor = '#0EA5E9',
7
+ label = '?',
8
+ message = '',
9
+ style = null,
10
+ textStyle = null,
11
+ title = 'Ajuda',
12
+ }) => {
13
+ const [visible, setVisible] = useState(false);
14
+
15
+ const openTooltip = useCallback(() => {
16
+ if (!message) {
17
+ return;
18
+ }
19
+
20
+ setVisible(true);
21
+ }, [message]);
22
+
23
+ const closeTooltip = useCallback(() => {
24
+ setVisible(false);
25
+ }, []);
26
+
27
+ return (
28
+ <>
29
+ <TouchableOpacity
30
+ accessibilityRole="button"
31
+ activeOpacity={0.85}
32
+ disabled={!message}
33
+ onPress={openTooltip}
34
+ style={[
35
+ styles.button,
36
+ {
37
+ borderColor: accentColor,
38
+ backgroundColor: `${accentColor}14`,
39
+ },
40
+ !message && {opacity: 0.55},
41
+ style,
42
+ ]}>
43
+ <Text style={[styles.label, {color: accentColor}, textStyle]}>
44
+ {label}
45
+ </Text>
46
+ </TouchableOpacity>
47
+
48
+ <Modal
49
+ animationType="fade"
50
+ onRequestClose={closeTooltip}
51
+ transparent
52
+ visible={visible}>
53
+ <Pressable style={styles.overlay} onPress={closeTooltip}>
54
+ <View
55
+ onStartShouldSetResponder={() => true}
56
+ style={[
57
+ styles.card,
58
+ {
59
+ borderColor: accentColor,
60
+ },
61
+ ]}>
62
+ <View style={styles.cardHeader}>
63
+ <Text style={[styles.title, {color: accentColor}]}>{title}</Text>
64
+ <TouchableOpacity
65
+ accessibilityRole="button"
66
+ activeOpacity={0.8}
67
+ onPress={closeTooltip}
68
+ style={[
69
+ styles.closeButton,
70
+ {
71
+ borderColor: accentColor,
72
+ },
73
+ ]}>
74
+ <Text style={[styles.closeButtonText, {color: accentColor}]}>
75
+ Fechar
76
+ </Text>
77
+ </TouchableOpacity>
78
+ </View>
79
+ <Text style={styles.message}>{message}</Text>
80
+ </View>
81
+ </Pressable>
82
+ </Modal>
83
+ </>
84
+ );
85
+ };
86
+
87
+ export default DefaultTooltip;