@controleonline/ui-common 1.2.101 → 1.2.102
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/.release/rc-manifest.json +7 -7
- package/docs/homepage-1056.md +1 -5
- package/package.json +1 -1
- package/src/api/index.js +40 -54
- package/src/react/components/AnimatedModal.js +6 -1
- package/src/react/components/BottomNavigationBar.js +6 -5
- package/src/react/components/BottomNavigationBar.soft.styles.js +5 -4
- package/src/react/components/ProductCatalogCacheService.js +53 -45
- package/src/react/components/RuntimeBottomNavigationBar.js +11 -4
- package/src/react/components/UserAvatar.js +7 -16
- package/src/react/hooks/useBottomNavigationPreference.js +49 -0
- package/src/react/pages/Devices/deviceTypes/DeviceGroupCard.js +6 -4
- package/src/react/pages/Devices/deviceTypes/shared.js +9 -4
- package/src/react/pages/Devices.js +184 -183
- package/src/react/pages/IntegrationConfigPage.js +59 -13
- package/src/react/pages/IntegrationConfigPage.utils.js +17 -0
- package/src/react/pages/integrationsCatalog.js +71 -0
- package/src/react/utils/fileUrl.js +7 -1
- package/src/react/utils/productCatalogCacheRevision.js +13 -0
- package/src/tests/react/components/UserAvatar.test.js +30 -0
- package/src/tests/react/utils/fileUrl.test.js +23 -6
- package/src/tests/react/utils/runtimeFooter.test.js +189 -189
- package/src/tests/react/utils/storedSessionToken.test.js +22 -0
- package/src/utils/integrationConfigs.js +23 -0
- package/src/utils/storedSessionToken.js +18 -0
- package/src/utils/trustedApiUrl.js +19 -0
|
@@ -1,183 +1,184 @@
|
|
|
1
|
-
import React, {useEffect, useMemo, useState} from 'react';
|
|
2
|
-
import {Text, TouchableOpacity, View} from 'react-native';
|
|
3
|
-
import {SafeAreaView} from 'react-native-safe-area-context';
|
|
4
|
-
import {useNavigation} from '@react-navigation/native';
|
|
5
|
-
import {useStore} from '@store';
|
|
6
|
-
import {resolveThemePalette} from '@controleonline/../../src/styles/branding';
|
|
7
|
-
import {colors} from '@controleonline/../../src/styles/colors';
|
|
8
|
-
import CompactFilterSelector from '@controleonline/ui-default/src/react/components/filters/CompactFilterSelector';
|
|
9
|
-
import {PDV_DEVICE_TYPE} from '@controleonline/ui-common/src/react/utils/printerDevices';
|
|
10
|
-
import Icon from 'react-native-vector-icons/Feather';
|
|
11
|
-
import {
|
|
12
|
-
ALL_DEVICE_FILTER,
|
|
13
|
-
getDeviceCreationAction,
|
|
14
|
-
getDeviceFilterAccent,
|
|
15
|
-
getDeviceFilterDefinition,
|
|
16
|
-
getDeviceFilterIcon,
|
|
17
|
-
getDeviceFilterOptions,
|
|
18
|
-
getPdvSubfilterOptions,
|
|
19
|
-
hasRegisteredDeviceFilter,
|
|
20
|
-
isPdvSubfilter,
|
|
21
|
-
} from './Devices/deviceTypes';
|
|
22
|
-
import { createStyles } from './Devices.styles';
|
|
23
|
-
|
|
24
|
-
const tt = (type, key) => global.t?.t('configs', type, key);
|
|
25
|
-
|
|
26
|
-
const Devices = () => {
|
|
27
|
-
const navigation = useNavigation();
|
|
28
|
-
const peopleStore = useStore('people');
|
|
29
|
-
const themeStore = useStore('theme');
|
|
30
|
-
|
|
31
|
-
const {currentCompany} = peopleStore.getters;
|
|
32
|
-
const {colors: themeColors} = themeStore.getters;
|
|
33
|
-
|
|
34
|
-
const [activeFilter, setActiveFilter] = useState(ALL_DEVICE_FILTER);
|
|
35
|
-
|
|
36
|
-
const brandColors = useMemo(
|
|
37
|
-
() =>
|
|
38
|
-
resolveThemePalette(
|
|
39
|
-
{...themeColors, ...(currentCompany?.theme?.colors || {})},
|
|
40
|
-
colors,
|
|
41
|
-
),
|
|
42
|
-
[themeColors, currentCompany?.id],
|
|
43
|
-
);
|
|
44
|
-
const styles = useMemo(() => createStyles(brandColors), [brandColors]);
|
|
45
|
-
const accentPalette = useMemo(
|
|
46
|
-
() => ({
|
|
47
|
-
primary: brandColors.info,
|
|
48
|
-
success: brandColors.success,
|
|
49
|
-
danger: brandColors.error,
|
|
50
|
-
warning: brandColors.warning,
|
|
51
|
-
}),
|
|
52
|
-
[brandColors.error, brandColors.info, brandColors.success, brandColors.warning],
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
const filterOptions = useMemo(() => getDeviceFilterOptions(), []);
|
|
56
|
-
const pdvSubfilterOptions = useMemo(() => getPdvSubfilterOptions(), []);
|
|
57
|
-
const activeFilterDefinition = useMemo(
|
|
58
|
-
() => getDeviceFilterDefinition(activeFilter),
|
|
59
|
-
[activeFilter],
|
|
60
|
-
);
|
|
61
|
-
const topLevelActiveFilter = useMemo(
|
|
62
|
-
() => (isPdvSubfilter(activeFilter) ? PDV_DEVICE_TYPE : activeFilter),
|
|
63
|
-
[activeFilter],
|
|
64
|
-
);
|
|
65
|
-
const activeFilterAction = useMemo(
|
|
66
|
-
() => getDeviceCreationAction(topLevelActiveFilter),
|
|
67
|
-
[topLevelActiveFilter],
|
|
68
|
-
);
|
|
69
|
-
const deviceTypeSelectorOptions = useMemo(
|
|
70
|
-
() => filterOptions.map(option => ({
|
|
71
|
-
key: option.key,
|
|
72
|
-
label: option.label,
|
|
73
|
-
})),
|
|
74
|
-
[filterOptions],
|
|
75
|
-
);
|
|
76
|
-
const pdvGatewaySelectorOptions = useMemo(
|
|
77
|
-
() => [
|
|
78
|
-
{key: PDV_DEVICE_TYPE, label: tt('device_filter', 'all') || 'Todos'},
|
|
79
|
-
...pdvSubfilterOptions.map(option => ({
|
|
80
|
-
key: option.key,
|
|
81
|
-
label: option.label,
|
|
82
|
-
})),
|
|
83
|
-
],
|
|
84
|
-
[pdvSubfilterOptions],
|
|
85
|
-
);
|
|
86
|
-
const selectedDeviceTypeLabel = useMemo(
|
|
87
|
-
() =>
|
|
88
|
-
deviceTypeSelectorOptions.find(option => option.key === topLevelActiveFilter)?.label ||
|
|
89
|
-
tt('device_filter', 'allTypes') ||
|
|
90
|
-
'Todos os tipos',
|
|
91
|
-
[deviceTypeSelectorOptions, topLevelActiveFilter],
|
|
92
|
-
);
|
|
93
|
-
const selectedPdvGatewayLabel = useMemo(
|
|
94
|
-
() =>
|
|
95
|
-
pdvGatewaySelectorOptions.find(option => option.key === activeFilter)?.label ||
|
|
96
|
-
tt('device_filter', 'all') ||
|
|
97
|
-
'Todos',
|
|
98
|
-
[activeFilter, pdvGatewaySelectorOptions],
|
|
99
|
-
);
|
|
100
|
-
const ActiveTabComponent = activeFilterDefinition.TabComponent;
|
|
101
|
-
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
if (!hasRegisteredDeviceFilter(activeFilter)) {
|
|
104
|
-
setActiveFilter(ALL_DEVICE_FILTER);
|
|
105
|
-
}
|
|
106
|
-
}, [activeFilter]);
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<SafeAreaView
|
|
110
|
-
style={[styles.container, {backgroundColor: brandColors.background}]}>
|
|
111
|
-
<View style={styles.filtersBlock}>
|
|
112
|
-
<Text style={styles.filtersLabel}>{tt('device_filter', 'filterByType') || 'Filtrar por tipo'}</Text>
|
|
113
|
-
<CompactFilterSelector
|
|
114
|
-
icon={getDeviceFilterIcon(topLevelActiveFilter)}
|
|
115
|
-
label={selectedDeviceTypeLabel}
|
|
116
|
-
title={tt('device_filter', 'filterByType') || 'Filtrar por tipo'}
|
|
117
|
-
accentColor={getDeviceFilterAccent(topLevelActiveFilter, {
|
|
118
|
-
brandColors,
|
|
119
|
-
hex: accentPalette,
|
|
120
|
-
})}
|
|
121
|
-
active={topLevelActiveFilter !== ALL_DEVICE_FILTER}
|
|
122
|
-
options={deviceTypeSelectorOptions}
|
|
123
|
-
selectedKey={topLevelActiveFilter}
|
|
124
|
-
onSelect={optionKey => {
|
|
125
|
-
setActiveFilter(optionKey);
|
|
126
|
-
return true;
|
|
127
|
-
}}
|
|
128
|
-
/>
|
|
129
|
-
</View>
|
|
130
|
-
|
|
131
|
-
{topLevelActiveFilter === PDV_DEVICE_TYPE && pdvSubfilterOptions.length > 0 ? (
|
|
132
|
-
<View style={styles.filtersBlock}>
|
|
133
|
-
<Text style={styles.filtersLabel}>{tt('device_filter', 'pdvGateway') || 'Gateway do PDV'}</Text>
|
|
134
|
-
<CompactFilterSelector
|
|
135
|
-
icon={getDeviceFilterIcon(activeFilter)}
|
|
136
|
-
label={selectedPdvGatewayLabel}
|
|
137
|
-
title={tt('device_filter', 'pdvGateway') || 'Gateway do PDV'}
|
|
138
|
-
accentColor={getDeviceFilterAccent(activeFilter, {
|
|
139
|
-
brandColors,
|
|
140
|
-
hex: accentPalette,
|
|
141
|
-
})}
|
|
142
|
-
active={activeFilter !== PDV_DEVICE_TYPE}
|
|
143
|
-
options={pdvGatewaySelectorOptions}
|
|
144
|
-
selectedKey={activeFilter}
|
|
145
|
-
onSelect={optionKey => {
|
|
146
|
-
setActiveFilter(optionKey);
|
|
147
|
-
return true;
|
|
148
|
-
}}
|
|
149
|
-
/>
|
|
150
|
-
</View>
|
|
151
|
-
) : null}
|
|
152
|
-
|
|
153
|
-
{activeFilterAction ? (
|
|
154
|
-
<View style={styles.printerActionBlock}>
|
|
155
|
-
<TouchableOpacity
|
|
156
|
-
style={[
|
|
157
|
-
styles.createPrinterBtn,
|
|
158
|
-
{backgroundColor: brandColors.primary},
|
|
159
|
-
]}
|
|
160
|
-
activeOpacity={0.86}
|
|
161
|
-
onPress={() => navigation.navigate(activeFilterAction.routeName)}>
|
|
162
|
-
<Icon name="plus-circle" size={16} color={brandColors.white} />
|
|
163
|
-
<Text style={styles.createPrinterBtnText}>
|
|
164
|
-
{activeFilterAction.label}
|
|
165
|
-
</Text>
|
|
166
|
-
</TouchableOpacity>
|
|
167
|
-
|
|
168
|
-
<Text style={styles.helperText}>
|
|
169
|
-
{activeFilterAction.helperText}
|
|
170
|
-
</Text>
|
|
171
|
-
</View>
|
|
172
|
-
) : null}
|
|
173
|
-
|
|
174
|
-
{ActiveTabComponent ? (
|
|
175
|
-
<ActiveTabComponent
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
1
|
+
import React, {useEffect, useMemo, useState} from 'react';
|
|
2
|
+
import {Text, TouchableOpacity, View} from 'react-native';
|
|
3
|
+
import {SafeAreaView} from 'react-native-safe-area-context';
|
|
4
|
+
import {useNavigation} from '@react-navigation/native';
|
|
5
|
+
import {useStore} from '@store';
|
|
6
|
+
import {resolveThemePalette} from '@controleonline/../../src/styles/branding';
|
|
7
|
+
import {colors} from '@controleonline/../../src/styles/colors';
|
|
8
|
+
import CompactFilterSelector from '@controleonline/ui-default/src/react/components/filters/CompactFilterSelector';
|
|
9
|
+
import {PDV_DEVICE_TYPE} from '@controleonline/ui-common/src/react/utils/printerDevices';
|
|
10
|
+
import Icon from 'react-native-vector-icons/Feather';
|
|
11
|
+
import {
|
|
12
|
+
ALL_DEVICE_FILTER,
|
|
13
|
+
getDeviceCreationAction,
|
|
14
|
+
getDeviceFilterAccent,
|
|
15
|
+
getDeviceFilterDefinition,
|
|
16
|
+
getDeviceFilterIcon,
|
|
17
|
+
getDeviceFilterOptions,
|
|
18
|
+
getPdvSubfilterOptions,
|
|
19
|
+
hasRegisteredDeviceFilter,
|
|
20
|
+
isPdvSubfilter,
|
|
21
|
+
} from './Devices/deviceTypes';
|
|
22
|
+
import { createStyles } from './Devices.styles';
|
|
23
|
+
|
|
24
|
+
const tt = (type, key) => global.t?.t('configs', type, key);
|
|
25
|
+
|
|
26
|
+
const Devices = ({themePalette = null} = {}) => {
|
|
27
|
+
const navigation = useNavigation();
|
|
28
|
+
const peopleStore = useStore('people');
|
|
29
|
+
const themeStore = useStore('theme');
|
|
30
|
+
|
|
31
|
+
const {currentCompany} = peopleStore.getters;
|
|
32
|
+
const {colors: themeColors} = themeStore.getters;
|
|
33
|
+
|
|
34
|
+
const [activeFilter, setActiveFilter] = useState(ALL_DEVICE_FILTER);
|
|
35
|
+
|
|
36
|
+
const brandColors = useMemo(
|
|
37
|
+
() =>
|
|
38
|
+
themePalette || resolveThemePalette(
|
|
39
|
+
{...themeColors, ...(currentCompany?.theme?.colors || {})},
|
|
40
|
+
colors,
|
|
41
|
+
),
|
|
42
|
+
[themePalette, themeColors, currentCompany?.id],
|
|
43
|
+
);
|
|
44
|
+
const styles = useMemo(() => createStyles(brandColors), [brandColors]);
|
|
45
|
+
const accentPalette = useMemo(
|
|
46
|
+
() => ({
|
|
47
|
+
primary: brandColors.info,
|
|
48
|
+
success: brandColors.success,
|
|
49
|
+
danger: brandColors.error,
|
|
50
|
+
warning: brandColors.warning,
|
|
51
|
+
}),
|
|
52
|
+
[brandColors.error, brandColors.info, brandColors.success, brandColors.warning],
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const filterOptions = useMemo(() => getDeviceFilterOptions(), []);
|
|
56
|
+
const pdvSubfilterOptions = useMemo(() => getPdvSubfilterOptions(), []);
|
|
57
|
+
const activeFilterDefinition = useMemo(
|
|
58
|
+
() => getDeviceFilterDefinition(activeFilter),
|
|
59
|
+
[activeFilter],
|
|
60
|
+
);
|
|
61
|
+
const topLevelActiveFilter = useMemo(
|
|
62
|
+
() => (isPdvSubfilter(activeFilter) ? PDV_DEVICE_TYPE : activeFilter),
|
|
63
|
+
[activeFilter],
|
|
64
|
+
);
|
|
65
|
+
const activeFilterAction = useMemo(
|
|
66
|
+
() => getDeviceCreationAction(topLevelActiveFilter),
|
|
67
|
+
[topLevelActiveFilter],
|
|
68
|
+
);
|
|
69
|
+
const deviceTypeSelectorOptions = useMemo(
|
|
70
|
+
() => filterOptions.map(option => ({
|
|
71
|
+
key: option.key,
|
|
72
|
+
label: option.label,
|
|
73
|
+
})),
|
|
74
|
+
[filterOptions],
|
|
75
|
+
);
|
|
76
|
+
const pdvGatewaySelectorOptions = useMemo(
|
|
77
|
+
() => [
|
|
78
|
+
{key: PDV_DEVICE_TYPE, label: tt('device_filter', 'all') || 'Todos'},
|
|
79
|
+
...pdvSubfilterOptions.map(option => ({
|
|
80
|
+
key: option.key,
|
|
81
|
+
label: option.label,
|
|
82
|
+
})),
|
|
83
|
+
],
|
|
84
|
+
[pdvSubfilterOptions],
|
|
85
|
+
);
|
|
86
|
+
const selectedDeviceTypeLabel = useMemo(
|
|
87
|
+
() =>
|
|
88
|
+
deviceTypeSelectorOptions.find(option => option.key === topLevelActiveFilter)?.label ||
|
|
89
|
+
tt('device_filter', 'allTypes') ||
|
|
90
|
+
'Todos os tipos',
|
|
91
|
+
[deviceTypeSelectorOptions, topLevelActiveFilter],
|
|
92
|
+
);
|
|
93
|
+
const selectedPdvGatewayLabel = useMemo(
|
|
94
|
+
() =>
|
|
95
|
+
pdvGatewaySelectorOptions.find(option => option.key === activeFilter)?.label ||
|
|
96
|
+
tt('device_filter', 'all') ||
|
|
97
|
+
'Todos',
|
|
98
|
+
[activeFilter, pdvGatewaySelectorOptions],
|
|
99
|
+
);
|
|
100
|
+
const ActiveTabComponent = activeFilterDefinition.TabComponent;
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (!hasRegisteredDeviceFilter(activeFilter)) {
|
|
104
|
+
setActiveFilter(ALL_DEVICE_FILTER);
|
|
105
|
+
}
|
|
106
|
+
}, [activeFilter]);
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<SafeAreaView
|
|
110
|
+
style={[styles.container, {backgroundColor: brandColors.background}]}>
|
|
111
|
+
<View style={styles.filtersBlock}>
|
|
112
|
+
<Text style={styles.filtersLabel}>{tt('device_filter', 'filterByType') || 'Filtrar por tipo'}</Text>
|
|
113
|
+
<CompactFilterSelector
|
|
114
|
+
icon={getDeviceFilterIcon(topLevelActiveFilter)}
|
|
115
|
+
label={selectedDeviceTypeLabel}
|
|
116
|
+
title={tt('device_filter', 'filterByType') || 'Filtrar por tipo'}
|
|
117
|
+
accentColor={getDeviceFilterAccent(topLevelActiveFilter, {
|
|
118
|
+
brandColors,
|
|
119
|
+
hex: accentPalette,
|
|
120
|
+
})}
|
|
121
|
+
active={topLevelActiveFilter !== ALL_DEVICE_FILTER}
|
|
122
|
+
options={deviceTypeSelectorOptions}
|
|
123
|
+
selectedKey={topLevelActiveFilter}
|
|
124
|
+
onSelect={optionKey => {
|
|
125
|
+
setActiveFilter(optionKey);
|
|
126
|
+
return true;
|
|
127
|
+
}}
|
|
128
|
+
/>
|
|
129
|
+
</View>
|
|
130
|
+
|
|
131
|
+
{topLevelActiveFilter === PDV_DEVICE_TYPE && pdvSubfilterOptions.length > 0 ? (
|
|
132
|
+
<View style={styles.filtersBlock}>
|
|
133
|
+
<Text style={styles.filtersLabel}>{tt('device_filter', 'pdvGateway') || 'Gateway do PDV'}</Text>
|
|
134
|
+
<CompactFilterSelector
|
|
135
|
+
icon={getDeviceFilterIcon(activeFilter)}
|
|
136
|
+
label={selectedPdvGatewayLabel}
|
|
137
|
+
title={tt('device_filter', 'pdvGateway') || 'Gateway do PDV'}
|
|
138
|
+
accentColor={getDeviceFilterAccent(activeFilter, {
|
|
139
|
+
brandColors,
|
|
140
|
+
hex: accentPalette,
|
|
141
|
+
})}
|
|
142
|
+
active={activeFilter !== PDV_DEVICE_TYPE}
|
|
143
|
+
options={pdvGatewaySelectorOptions}
|
|
144
|
+
selectedKey={activeFilter}
|
|
145
|
+
onSelect={optionKey => {
|
|
146
|
+
setActiveFilter(optionKey);
|
|
147
|
+
return true;
|
|
148
|
+
}}
|
|
149
|
+
/>
|
|
150
|
+
</View>
|
|
151
|
+
) : null}
|
|
152
|
+
|
|
153
|
+
{activeFilterAction ? (
|
|
154
|
+
<View style={styles.printerActionBlock}>
|
|
155
|
+
<TouchableOpacity
|
|
156
|
+
style={[
|
|
157
|
+
styles.createPrinterBtn,
|
|
158
|
+
{backgroundColor: brandColors.primary},
|
|
159
|
+
]}
|
|
160
|
+
activeOpacity={0.86}
|
|
161
|
+
onPress={() => navigation.navigate(activeFilterAction.routeName)}>
|
|
162
|
+
<Icon name="plus-circle" size={16} color={brandColors.white} />
|
|
163
|
+
<Text style={styles.createPrinterBtnText}>
|
|
164
|
+
{activeFilterAction.label}
|
|
165
|
+
</Text>
|
|
166
|
+
</TouchableOpacity>
|
|
167
|
+
|
|
168
|
+
<Text style={styles.helperText}>
|
|
169
|
+
{activeFilterAction.helperText}
|
|
170
|
+
</Text>
|
|
171
|
+
</View>
|
|
172
|
+
) : null}
|
|
173
|
+
|
|
174
|
+
{ActiveTabComponent ? (
|
|
175
|
+
<ActiveTabComponent
|
|
176
|
+
themePalette={themePalette}
|
|
177
|
+
key={`${activeFilter}-${currentCompany?.id || 'company'}`}
|
|
178
|
+
/>
|
|
179
|
+
) : null}
|
|
180
|
+
</SafeAreaView>
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export default Devices;
|
|
@@ -14,6 +14,7 @@ import { fetchPeopleConfigs } from './fetchPeopleConfigs';
|
|
|
14
14
|
import IntegrationConfigFields from './IntegrationConfigFields';
|
|
15
15
|
import {
|
|
16
16
|
ROUTE_PROVIDER_MAP,
|
|
17
|
+
expandBundledConfig,
|
|
17
18
|
extractAuthorizationUrl,
|
|
18
19
|
formatApiError,
|
|
19
20
|
formatUberOAuthError,
|
|
@@ -112,6 +113,8 @@ export default function IntegrationConfigPage({ route, embedded = false, fiscalT
|
|
|
112
113
|
const allFieldKeysSig = useMemo(() => allFieldKeys.join('|'), [allFieldKeys]);
|
|
113
114
|
// Pure config providers (e.g. receita-federal) do not use marketplace/integrations.
|
|
114
115
|
const needsMarketplaceIntegration = Boolean(providerConfig?.oauthConnect);
|
|
116
|
+
const bundleKey = normalizeTextValue(providerConfig?.bundleKey);
|
|
117
|
+
const configVisibility = normalizeTextValue(providerConfig?.visibility) || 'public';
|
|
115
118
|
|
|
116
119
|
useEffect(() => {
|
|
117
120
|
setConfigValues({});
|
|
@@ -135,14 +138,28 @@ export default function IntegrationConfigPage({ route, embedded = false, fiscalT
|
|
|
135
138
|
}
|
|
136
139
|
if (showLoading) setLoading(true);
|
|
137
140
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
141
|
+
if (bundleKey) {
|
|
142
|
+
const configItems = await fetchPeopleConfigs({
|
|
143
|
+
peopleIri: providerIri,
|
|
144
|
+
configKeys: [bundleKey],
|
|
145
|
+
});
|
|
146
|
+
const bundled = configItems.find(
|
|
147
|
+
item => String(item?.configKey || '').trim() === bundleKey,
|
|
148
|
+
);
|
|
144
149
|
const mergeFields = allConfigFields.length ? allConfigFields : visibleFields;
|
|
145
|
-
setConfigValues(current =>
|
|
150
|
+
setConfigValues(current =>
|
|
151
|
+
expandBundledConfig(mergeFields, bundled?.configValue, current),
|
|
152
|
+
);
|
|
153
|
+
} else {
|
|
154
|
+
const keys = allFieldKeys.length
|
|
155
|
+
? allFieldKeys
|
|
156
|
+
: visibleFields.map(field => field.key).filter(Boolean);
|
|
157
|
+
|
|
158
|
+
if (keys.length) {
|
|
159
|
+
const configItems = await fetchPeopleConfigs({ peopleIri: providerIri, configKeys: keys });
|
|
160
|
+
const mergeFields = allConfigFields.length ? allConfigFields : visibleFields;
|
|
161
|
+
setConfigValues(current => mergeTabValues(mergeFields, configItems, current));
|
|
162
|
+
}
|
|
146
163
|
}
|
|
147
164
|
|
|
148
165
|
if (needsMarketplaceIntegration) {
|
|
@@ -170,6 +187,7 @@ export default function IntegrationConfigPage({ route, embedded = false, fiscalT
|
|
|
170
187
|
allConfigFields,
|
|
171
188
|
allFieldKeys,
|
|
172
189
|
allFieldKeysSig,
|
|
190
|
+
bundleKey,
|
|
173
191
|
needsMarketplaceIntegration,
|
|
174
192
|
providerConfig,
|
|
175
193
|
providerIri,
|
|
@@ -246,16 +264,44 @@ export default function IntegrationConfigPage({ route, embedded = false, fiscalT
|
|
|
246
264
|
showError('Nao foi possivel identificar a integracao selecionada.');
|
|
247
265
|
return;
|
|
248
266
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
267
|
+
|
|
268
|
+
let configs;
|
|
269
|
+
if (bundleKey) {
|
|
270
|
+
const payloadObject = {};
|
|
271
|
+
(visibleFields || []).forEach(field => {
|
|
272
|
+
payloadObject[field.key] = normalizeTextValue(configValues[field.key]);
|
|
273
|
+
});
|
|
274
|
+
configs = [
|
|
275
|
+
{
|
|
276
|
+
configKey: bundleKey,
|
|
277
|
+
configValue: toConfigRequestValue(payloadObject),
|
|
278
|
+
},
|
|
279
|
+
];
|
|
280
|
+
} else {
|
|
281
|
+
configs = visibleFields.map(field => ({
|
|
282
|
+
configKey: field.key,
|
|
283
|
+
configValue: toConfigRequestValue(normalizeTextValue(configValues[field.key])),
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
|
|
253
287
|
try {
|
|
254
|
-
await persistConfigs({ configs, people: providerIri, module: 4, visibility:
|
|
288
|
+
await persistConfigs({ configs, people: providerIri, module: 4, visibility: configVisibility });
|
|
255
289
|
showSuccess(`${activeTabDef?.label || providerConfig.label} salvo com sucesso.`);
|
|
256
290
|
await loadPageData({ showLoading: false });
|
|
257
291
|
} catch (error) { showError(error?.message || 'Nao foi possivel salvar a integracao.'); }
|
|
258
|
-
}, [
|
|
292
|
+
}, [
|
|
293
|
+
activeTabDef,
|
|
294
|
+
bundleKey,
|
|
295
|
+
configValues,
|
|
296
|
+
configVisibility,
|
|
297
|
+
loadPageData,
|
|
298
|
+
persistConfigs,
|
|
299
|
+
providerConfig,
|
|
300
|
+
providerIri,
|
|
301
|
+
showError,
|
|
302
|
+
showSuccess,
|
|
303
|
+
visibleFields,
|
|
304
|
+
]);
|
|
259
305
|
|
|
260
306
|
if (!providerConfig) return <CenterState icon="alert-triangle" iconColor="#e67e22" title="Integracao indisponivel" text="A tela solicitada nao possui configuracao cadastrada." />;
|
|
261
307
|
if (!providerId) return <CenterState icon="building" iconColor="#94A3B8" title="Empresa nao identificada" text="Abra a ficha da empresa para gravar as configuracoes fiscais nela." />;
|
|
@@ -5,6 +5,7 @@ export const ROUTE_PROVIDER_MAP = {
|
|
|
5
5
|
AsaasIntegrationPage: 'asaas',
|
|
6
6
|
ClickSignIntegrationPage: 'clicksign',
|
|
7
7
|
ReceitaFederalIntegrationPage: 'receita-federal',
|
|
8
|
+
SemPararIntegrationPage: 'sem-parar',
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
export const routeNameToPath = routeName =>
|
|
@@ -53,6 +54,22 @@ export const mergeTabValues = (fields, source, current = {}) => {
|
|
|
53
54
|
return next;
|
|
54
55
|
};
|
|
55
56
|
|
|
57
|
+
/** Expand a single JSON config (bundleKey) into flat field values. */
|
|
58
|
+
export const expandBundledConfig = (fields, rawValue, current = {}) => {
|
|
59
|
+
let parsed = rawValue;
|
|
60
|
+
if (typeof rawValue === 'string') {
|
|
61
|
+
try {
|
|
62
|
+
parsed = JSON.parse(rawValue);
|
|
63
|
+
} catch {
|
|
64
|
+
parsed = {};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
68
|
+
parsed = {};
|
|
69
|
+
}
|
|
70
|
+
return mergeTabValues(fields, parsed, current);
|
|
71
|
+
};
|
|
72
|
+
|
|
56
73
|
export const buildFieldValues = (providerConfig, source) =>
|
|
57
74
|
mergeTabValues(getConfigFields(providerConfig), source, {});
|
|
58
75
|
|
|
@@ -70,6 +70,16 @@ export const INTEGRATION_LIST = [
|
|
|
70
70
|
accent: '#166534',
|
|
71
71
|
icon: 'file-text',
|
|
72
72
|
},
|
|
73
|
+
{
|
|
74
|
+
key: 'sem-parar',
|
|
75
|
+
label: 'Sem Parar',
|
|
76
|
+
route: 'IntegrationConfigPage',
|
|
77
|
+
routeParams: {
|
|
78
|
+
providerKey: 'sem-parar',
|
|
79
|
+
},
|
|
80
|
+
accent: '#0EA5E9',
|
|
81
|
+
icon: 'truck',
|
|
82
|
+
},
|
|
73
83
|
];
|
|
74
84
|
|
|
75
85
|
export const INTEGRATION_CONFIGS = {
|
|
@@ -257,6 +267,67 @@ export const INTEGRATION_CONFIGS = {
|
|
|
257
267
|
],
|
|
258
268
|
fields: [],
|
|
259
269
|
},
|
|
270
|
+
|
|
271
|
+
'sem-parar': {
|
|
272
|
+
key: 'sem-parar',
|
|
273
|
+
label: 'Sem Parar',
|
|
274
|
+
accent: '#0EA5E9',
|
|
275
|
+
icon: 'truck',
|
|
276
|
+
description:
|
|
277
|
+
'Credenciais da empresa para emissao de vale-pedagio via Sem Parar.',
|
|
278
|
+
saveLabel: 'Salvar Sem Parar',
|
|
279
|
+
// Backend (api-platform-integration SemPararConfig) reads a single private JSON key.
|
|
280
|
+
bundleKey: 'SEM_PARAR',
|
|
281
|
+
visibility: 'private',
|
|
282
|
+
requiredKeys: ['ENABLED', 'CLIENT_ID', 'CLIENT_SECRET', 'USERNAME', 'PASSWORD'],
|
|
283
|
+
fields: [
|
|
284
|
+
{
|
|
285
|
+
key: 'ENABLED',
|
|
286
|
+
label: 'Ativo',
|
|
287
|
+
type: 'select',
|
|
288
|
+
options: [
|
|
289
|
+
{value: '1', label: 'Sim'},
|
|
290
|
+
{value: '0', label: 'Nao'},
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
key: 'ENVIRONMENT',
|
|
295
|
+
label: 'Ambiente',
|
|
296
|
+
type: 'select',
|
|
297
|
+
options: [
|
|
298
|
+
{value: 'sandbox', label: 'Sandbox'},
|
|
299
|
+
{value: 'production', label: 'Producao'},
|
|
300
|
+
],
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
key: 'CLIENT_ID',
|
|
304
|
+
label: 'Client ID',
|
|
305
|
+
placeholder: 'Client ID Sem Parar',
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
key: 'CLIENT_SECRET',
|
|
309
|
+
label: 'Client secret',
|
|
310
|
+
placeholder: 'Client secret Sem Parar',
|
|
311
|
+
secureTextEntry: true,
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
key: 'USERNAME',
|
|
315
|
+
label: 'Usuario',
|
|
316
|
+
placeholder: 'Usuario da API',
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
key: 'PASSWORD',
|
|
320
|
+
label: 'Senha',
|
|
321
|
+
placeholder: 'Senha da API',
|
|
322
|
+
secureTextEntry: true,
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
key: 'CONVENIADO',
|
|
326
|
+
label: 'Conveniado',
|
|
327
|
+
placeholder: 'Codigo do conveniado (se aplicavel)',
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
},
|
|
260
331
|
};
|
|
261
332
|
|
|
262
333
|
export const getIntegrationListItem = key =>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {env as APP_ENV} from '@env';
|
|
2
|
+
import {filterApiHeaders} from '../../utils/trustedApiUrl';
|
|
2
3
|
import {buildAssetUrl} from '@controleonline/../../src/styles/branding';
|
|
3
4
|
import {
|
|
4
5
|
resolveAppDomain,
|
|
@@ -227,7 +228,7 @@ export const extractFileId = file => {
|
|
|
227
228
|
return null;
|
|
228
229
|
};
|
|
229
230
|
|
|
230
|
-
|
|
231
|
+
const resolveFileSource = (
|
|
231
232
|
file,
|
|
232
233
|
{company = null, appDomain = '', headers = {}} = {},
|
|
233
234
|
) => {
|
|
@@ -329,6 +330,11 @@ export const resolveDefaultFileSource = (
|
|
|
329
330
|
return null;
|
|
330
331
|
};
|
|
331
332
|
|
|
333
|
+
export const resolveDefaultFileSource = (file, options = {}) => {
|
|
334
|
+
const source = resolveFileSource(file, options);
|
|
335
|
+
return source ? {...source, headers: filterApiHeaders(source.uri, source.headers, APP_ENV?.API_ENTRYPOINT)} : null;
|
|
336
|
+
};
|
|
337
|
+
|
|
332
338
|
export const resolveDefaultFileUrl = (file, options = {}) =>
|
|
333
339
|
resolveDefaultFileSource(file, options)?.uri || '';
|
|
334
340
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import eventBus from '../components/EventBus';
|
|
2
|
+
|
|
3
|
+
const key = 'product-catalog-cache-revision';
|
|
4
|
+
let revision = '0';
|
|
5
|
+
let sequence = 0;
|
|
6
|
+
export const getProductCatalogCacheRevision = () => {
|
|
7
|
+
try { return localStorage.getItem(key) || revision; } catch { return revision; }
|
|
8
|
+
};
|
|
9
|
+
export const invalidateProductCatalogCache = () => {
|
|
10
|
+
revision = `${Date.now()}:${++sequence}`;
|
|
11
|
+
try { localStorage.setItem(key, revision); } catch { /* In-memory invalidation still applies. */ }
|
|
12
|
+
eventBus.emit('product-catalog-cache:cleared');
|
|
13
|
+
};
|