@controleonline/ui-common 1.2.80 → 1.2.81
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 +8 -0
- package/package.json +9 -3
- package/src/api/fetch.js +7 -7
- package/src/react/components/AddImportModal.js +6 -2
- package/src/react/components/BackgroundRuntimeBridge.js +201 -215
- package/src/react/components/ConfirmModal.js +44 -9
- package/src/react/components/ConfirmModal.styles.js +55 -41
- package/src/react/components/CopyDeviceConfigModal.js +193 -0
- package/src/react/components/CopyDeviceConfigModal.styles.js +123 -0
- package/src/react/components/DefaultProvider.native.js +22 -0
- package/src/react/components/DefaultProvider.web.js +25 -1
- package/src/react/components/EntityLogBranch.js +482 -0
- package/src/react/components/EntityLogCards.js +369 -0
- package/src/react/components/EntityLogContent.js +113 -794
- package/src/react/components/LinkedOrderProductsTab.js +3 -3
- package/src/react/components/PrintService.js +130 -409
- package/src/react/components/RemoteCheckoutService.js +2 -2
- package/src/react/components/RuntimeBottomNavigationBar.js +141 -146
- package/src/react/components/RuntimeFooterMarqueeText.js +137 -0
- package/src/react/components/RuntimeInfoFooter.js +12 -15
- package/src/react/components/SystemErrorToast.js +49 -29
- package/src/react/components/SystemErrorToast.styles.js +43 -38
- package/src/react/components/UserAvatar.js +120 -28
- package/src/react/config/deviceConfigBootstrap.js +330 -304
- package/src/react/hooks/usePrintSpoolEffects.js +161 -0
- package/src/react/pages/DeviceDetailPage.js +6 -2882
- package/src/react/pages/Devices/detail/DeviceDetailAlertsCommandsSection.js +233 -0
- package/src/react/pages/Devices/detail/DeviceDetailHeader.js +138 -0
- package/src/react/pages/Devices/detail/DeviceDetailMovementSections.js +167 -0
- package/src/react/pages/Devices/detail/DeviceDetailOrdersPrintSection.js +216 -0
- package/src/react/pages/Devices/detail/DeviceDetailPaymentSection.js +40 -0
- package/src/react/pages/Devices/detail/DeviceDetailPdvConfigSection.js +390 -0
- package/src/react/pages/Devices/detail/DeviceDetailRenderers.js +135 -0
- package/src/react/pages/Devices/detail/DeviceDetailScreen.js +493 -0
- package/src/react/pages/Devices/detail/OptionButtonChip.js +65 -0
- package/src/react/pages/Devices/detail/deviceDetailConstants.js +36 -0
- package/src/react/pages/Devices/detail/deviceDetailHelpers.js +70 -0
- package/src/react/pages/Devices/detail/useDeviceDetailActions.js +483 -0
- package/src/react/pages/Devices/detail/useDeviceDetailCopyConfig.js +87 -0
- package/src/react/pages/Devices/detail/useDeviceDetailLoaders.js +327 -0
- package/src/react/pages/Devices/detail/useDeviceDetailSaves.js +429 -0
- package/src/react/pages/Devices/detail/useDeviceDetailStateA.js +484 -0
- package/src/react/pages/Devices/detail/useDeviceDetailStateB.js +71 -0
- package/src/react/pages/Devices/deviceTypes/index.js +2 -0
- package/src/react/pages/Devices/deviceTypes/pdv.js +11 -0
- package/src/react/pages/Imports.js +3 -2
- package/src/react/pages/IntegrationConfigFields.js +126 -0
- package/src/react/pages/IntegrationConfigPage.js +122 -591
- package/src/react/pages/IntegrationConfigPage.utils.js +95 -0
- package/src/react/print/usePrintButtonController.js +9 -1
- package/src/react/services/Getnet/Checkout.js +81 -0
- package/src/react/services/Getnet/Getnet.js +77 -0
- package/src/react/services/paymentGatewayExecution.js +20 -0
- package/src/react/utils/bottomNavigationToolbar.js +161 -0
- package/src/react/utils/commercialDocumentOrders.js +18 -6
- package/src/react/utils/copyDeviceConfigs.js +198 -0
- package/src/react/utils/fileUrl.js +43 -0
- package/src/react/utils/importStatus.js +19 -10
- package/src/react/utils/logSettings.js +7 -7
- package/src/react/utils/maintenanceSettings.js +16 -0
- package/src/react/utils/paymentDevices.js +6 -0
- package/src/react/utils/printManagedPrinter.js +45 -0
- package/src/react/utils/printRouting.js +63 -0
- package/src/react/utils/printSpoolUtils.js +119 -0
- package/src/react/utils/printerDevices.js +368 -368
- package/src/react/utils/runtimeFooter.js +10 -19
- package/src/react/utils/shopFranchises.js +186 -65
- package/src/react/utils/systemErrorMessage.js +71 -67
- package/src/store/device_config/customActions.js +119 -30
- package/src/tests/browser/manager/bottom-navigation-role-toolbar.spec.js +353 -0
- package/src/tests/browser/manager/device-detail-alias-save.spec.js +345 -0
- package/src/tests/browser/manager/device-detail-copy-config.spec.js +365 -0
- package/src/tests/browser/manager/runtime-footer-marquee.spec.js +262 -0
- package/src/tests/browser/manager/theme-confirm-toast.spec.js +236 -0
- package/src/tests/react/components/RuntimeFooterMarqueeText.test.js +171 -0
- package/src/tests/react/components/UserAvatar.test.js +6 -2
- package/src/tests/react/components/themeAwareModals.test.mjs +60 -0
- package/src/tests/react/config/deviceConfigBootstrap.test.js +104 -66
- package/src/tests/react/pages/deviceDetailDelete.test.js +78 -0
- package/src/tests/react/pages/receitaFederalIntegrationContract.test.mjs +48 -0
- package/src/tests/react/utils/bottomNavigationToolbar.test.js +222 -0
- package/src/tests/react/utils/copyDeviceConfigs.test.mjs +127 -0
- package/src/tests/react/utils/fileUrl.test.js +38 -0
- package/src/tests/react/utils/importStatus.test.js +26 -9
- package/src/tests/react/utils/passwordPolicy.test.js +27 -0
- package/src/tests/react/utils/printRouting.test.mjs +86 -0
- package/src/tests/react/utils/printSpoolUtils.test.mjs +52 -0
- package/src/tests/react/utils/runtimeFooter.test.js +7 -0
- package/src/tests/react/utils/shopFranchises.test.js +121 -115
- package/src/tests/react/utils/systemErrorMessage.test.js +32 -59
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import { ActivityIndicator,
|
|
2
|
+
import { ActivityIndicator, Platform, RefreshControl, ScrollView, Text, TouchableOpacity, View } from 'react-native';
|
|
3
3
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
4
4
|
import { useFocusEffect } from '@react-navigation/native';
|
|
5
5
|
import Icon from 'react-native-vector-icons/Feather';
|
|
@@ -8,160 +8,43 @@ import { api } from '@controleonline/ui-common/src/api';
|
|
|
8
8
|
import useToastMessage from '@controleonline/ui-crm/src/react/hooks/useToastMessage';
|
|
9
9
|
import { useStore } from '@store';
|
|
10
10
|
import { colors } from '@controleonline/../../src/styles/colors';
|
|
11
|
+
import { resolveThemePalette, withOpacity } from '@controleonline/../../src/styles/branding';
|
|
12
|
+
import { getIntegrationConfig, getIntegrationByKey, parseIntegrationCollection } from './integrationsCatalog';
|
|
13
|
+
import IntegrationConfigFields from './IntegrationConfigFields';
|
|
11
14
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
ROUTE_PROVIDER_MAP,
|
|
16
|
+
buildFieldValues,
|
|
17
|
+
extractAuthorizationUrl,
|
|
18
|
+
formatApiError,
|
|
19
|
+
formatUberOAuthError,
|
|
20
|
+
getConfigFields,
|
|
21
|
+
isConnectedValue,
|
|
22
|
+
normalizeTextValue,
|
|
23
|
+
openAuthorizationUrl,
|
|
24
|
+
resolveFallbackConfigs,
|
|
25
|
+
resolveProviderId,
|
|
26
|
+
routeNameToPath,
|
|
27
|
+
toConfigRequestValue,
|
|
28
|
+
} from './IntegrationConfigPage.utils';
|
|
23
29
|
import styles from './IntegrationConfigPage.styles';
|
|
24
30
|
|
|
25
|
-
const ROUTE_PROVIDER_MAP = {
|
|
26
|
-
UberIntegrationPage: 'uber',
|
|
27
|
-
AsaasIntegrationPage: 'asaas',
|
|
28
|
-
ClickSignIntegrationPage: 'clicksign',
|
|
29
|
-
};
|
|
30
|
-
|
|
31
31
|
const shadowStyle = Platform.select({
|
|
32
|
-
ios: {
|
|
33
|
-
shadowColor: '#0F172A',
|
|
34
|
-
shadowOffset: { width: 0, height: 8 },
|
|
35
|
-
shadowOpacity: 0.08,
|
|
36
|
-
shadowRadius: 16,
|
|
37
|
-
},
|
|
32
|
+
ios: { shadowColor: '#0F172A', shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.08, shadowRadius: 16 },
|
|
38
33
|
android: { elevation: 3 },
|
|
39
34
|
web: { boxShadow: '0 10px 24px rgba(15,23,42,0.08)' },
|
|
40
35
|
});
|
|
41
36
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const normalizeSourceConfigs = source => {
|
|
54
|
-
if (Array.isArray(source)) {
|
|
55
|
-
return source.reduce((accumulator, item) => {
|
|
56
|
-
const key = String(item?.configKey || '').trim();
|
|
57
|
-
if (key) {
|
|
58
|
-
accumulator[key] = item?.configValue;
|
|
59
|
-
}
|
|
60
|
-
return accumulator;
|
|
61
|
-
}, {});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (source && typeof source === 'object') {
|
|
65
|
-
return source;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return {};
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const normalizeTextValue = value => {
|
|
72
|
-
let text = String(value ?? '').trim();
|
|
73
|
-
// Strip accidental surrounding quotes from double-encoded config values
|
|
74
|
-
if (
|
|
75
|
-
(text.startsWith('"') && text.endsWith('"') && text.length >= 2) ||
|
|
76
|
-
(text.startsWith("'") && text.endsWith("'") && text.length >= 2)
|
|
77
|
-
) {
|
|
78
|
-
text = text.slice(1, -1).trim();
|
|
79
|
-
}
|
|
80
|
-
return text;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const isConnectedValue = value =>
|
|
84
|
-
value === true ||
|
|
85
|
-
value === 1 ||
|
|
86
|
-
value === '1' ||
|
|
87
|
-
String(value).trim().toLowerCase() === 'true';
|
|
88
|
-
|
|
89
|
-
const toConfigRequestValue = value => {
|
|
90
|
-
// Plain strings must NOT be JSON.stringify'd here — the HTTP JSON body
|
|
91
|
-
// already serializes them. Stringify caused values to be stored with
|
|
92
|
-
// literal surrounding quotes (e.g. ""whsec_...""), breaking webhook auth.
|
|
93
|
-
if (value === undefined || value === null) {
|
|
94
|
-
return '';
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (typeof value === 'string') {
|
|
98
|
-
return normalizeTextValue(value);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (typeof value === 'object') {
|
|
102
|
-
return JSON.stringify(value);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return String(value);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const getConfigFields = providerConfig => {
|
|
110
|
-
if (!providerConfig) return [];
|
|
111
|
-
if (Array.isArray(providerConfig.tabs) && providerConfig.tabs.length > 0) {
|
|
112
|
-
return providerConfig.tabs.flatMap(tab => tab.fields || []);
|
|
113
|
-
}
|
|
114
|
-
return providerConfig.fields || [];
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const buildFieldValues = (providerConfig, source) => {
|
|
118
|
-
const sourceMap = normalizeSourceConfigs(source);
|
|
119
|
-
|
|
120
|
-
return getConfigFields(providerConfig).reduce((accumulator, field) => {
|
|
121
|
-
accumulator[field.key] = normalizeTextValue(sourceMap[field.key]);
|
|
122
|
-
return accumulator;
|
|
123
|
-
}, {});
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const extractAuthorizationUrl = response => {
|
|
127
|
-
const candidate =
|
|
128
|
-
response?.member?.[0]?.authorization_url ||
|
|
129
|
-
response?.member?.[0]?.auth_url ||
|
|
130
|
-
response?.member?.[0]?.url ||
|
|
131
|
-
response?.authorization_url ||
|
|
132
|
-
response?.auth_url ||
|
|
133
|
-
response?.url ||
|
|
134
|
-
response?.data?.authorization_url ||
|
|
135
|
-
response?.data?.auth_url ||
|
|
136
|
-
response?.data?.url;
|
|
137
|
-
|
|
138
|
-
return normalizeTextValue(candidate);
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const formatUberOAuthError = error => {
|
|
142
|
-
const normalized = normalizeTextValue(error).toLowerCase();
|
|
143
|
-
|
|
144
|
-
if (normalized === 'invalid_scope') {
|
|
145
|
-
return 'O Uber nao liberou o scope pos_provisioning para este app. Esse app precisa estar aprovado/whitelisted no dashboard do Uber.';
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (normalized === 'access_denied') {
|
|
149
|
-
return 'O login do Uber foi cancelado.';
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return normalizeTextValue(error) || 'Nao foi possivel concluir a conexao com o Uber.';
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const openAuthorizationUrl = async authUrl => {
|
|
156
|
-
if (Platform.OS === 'web' && typeof window !== 'undefined' && typeof window.location?.assign === 'function') {
|
|
157
|
-
window.location.assign(authUrl);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
await Linking.openURL(authUrl);
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
export default function IntegrationConfigPage({ route, navigation, embedded = false }) {
|
|
37
|
+
const CenterState = ({ icon, iconColor, title, text, backgroundColor, spinnerColor }) => (
|
|
38
|
+
<SafeAreaView style={[styles.container, backgroundColor ? { backgroundColor } : null]} edges={['bottom']}>
|
|
39
|
+
<View style={styles.centerState}>
|
|
40
|
+
{spinnerColor ? <ActivityIndicator size="large" color={spinnerColor} /> : <Icon name={icon} size={32} color={iconColor} />}
|
|
41
|
+
<Text style={styles.centerStateTitle}>{title}</Text>
|
|
42
|
+
<Text style={styles.centerStateText}>{text}</Text>
|
|
43
|
+
</View>
|
|
44
|
+
</SafeAreaView>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
export default function IntegrationConfigPage({ route, embedded = false }) {
|
|
165
48
|
const peopleStore = useStore('people');
|
|
166
49
|
const themeStore = useStore('theme');
|
|
167
50
|
const configsStore = useStore('configs');
|
|
@@ -173,21 +56,13 @@ export default function IntegrationConfigPage({ route, navigation, embedded = fa
|
|
|
173
56
|
const oauthNoticeRef = useRef('');
|
|
174
57
|
|
|
175
58
|
const providerKey = useMemo(
|
|
176
|
-
() =>
|
|
177
|
-
route?.params?.providerKey ||
|
|
178
|
-
ROUTE_PROVIDER_MAP[route?.name] ||
|
|
179
|
-
'',
|
|
59
|
+
() => route?.params?.providerKey || ROUTE_PROVIDER_MAP[route?.name] || '',
|
|
180
60
|
[route?.name, route?.params?.providerKey],
|
|
181
61
|
);
|
|
182
|
-
const providerConfig = useMemo(
|
|
183
|
-
() => getIntegrationConfig(providerKey),
|
|
184
|
-
[providerKey],
|
|
185
|
-
);
|
|
62
|
+
const providerConfig = useMemo(() => getIntegrationConfig(providerKey), [providerKey]);
|
|
186
63
|
const configFields = useMemo(() => getConfigFields(providerConfig), [providerConfig]);
|
|
187
64
|
const fiscalTabs = providerConfig?.tabs || [];
|
|
188
|
-
const [activeFiscalTab, setActiveFiscalTab] = useState(
|
|
189
|
-
() => fiscalTabs[0]?.key || 'general',
|
|
190
|
-
);
|
|
65
|
+
const [activeFiscalTab, setActiveFiscalTab] = useState(() => fiscalTabs[0]?.key || 'general');
|
|
191
66
|
const activeTabDef = useMemo(
|
|
192
67
|
() => fiscalTabs.find(tab => tab.key === activeFiscalTab) || fiscalTabs[0] || null,
|
|
193
68
|
[activeFiscalTab, fiscalTabs],
|
|
@@ -198,82 +73,51 @@ export default function IntegrationConfigPage({ route, navigation, embedded = fa
|
|
|
198
73
|
);
|
|
199
74
|
|
|
200
75
|
useEffect(() => {
|
|
201
|
-
if (fiscalTabs.length && !fiscalTabs.some(tab => tab.key === activeFiscalTab))
|
|
202
|
-
setActiveFiscalTab(fiscalTabs[0].key);
|
|
203
|
-
}
|
|
76
|
+
if (fiscalTabs.length && !fiscalTabs.some(tab => tab.key === activeFiscalTab)) setActiveFiscalTab(fiscalTabs[0].key);
|
|
204
77
|
}, [activeFiscalTab, fiscalTabs]);
|
|
205
|
-
const returnPath = useMemo(() => {
|
|
206
|
-
const routePath = normalizeTextValue(
|
|
207
|
-
route?.params?.return_path || route?.params?.returnPath || '',
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
if (routePath) {
|
|
211
|
-
return routePath.startsWith('/') ? routePath : `/${routePath}`;
|
|
212
|
-
}
|
|
213
78
|
|
|
214
|
-
|
|
215
|
-
|
|
79
|
+
const returnPath = useMemo(() => {
|
|
80
|
+
const routePath = normalizeTextValue(route?.params?.return_path || route?.params?.returnPath || '');
|
|
81
|
+
if (routePath) return routePath.startsWith('/') ? routePath : `/${routePath}`;
|
|
82
|
+
const normalized = routeNameToPath(route?.name);
|
|
83
|
+
return normalized ? `/${normalized}` : '/uber-integration-page';
|
|
216
84
|
}, [route?.name, route?.params?.returnPath, route?.params?.return_path]);
|
|
217
85
|
|
|
218
86
|
const brandColors = useMemo(
|
|
219
|
-
() =>
|
|
220
|
-
resolveThemePalette(
|
|
221
|
-
{
|
|
222
|
-
...themeColors,
|
|
223
|
-
...(currentCompany?.theme?.colors || {}),
|
|
224
|
-
},
|
|
225
|
-
colors,
|
|
226
|
-
),
|
|
87
|
+
() => resolveThemePalette({ ...themeColors, ...(currentCompany?.theme?.colors || {}) }, colors),
|
|
227
88
|
[themeColors, currentCompany?.id],
|
|
228
89
|
);
|
|
229
|
-
|
|
230
90
|
const [loading, setLoading] = useState(true);
|
|
231
91
|
const [refreshing, setRefreshing] = useState(false);
|
|
232
92
|
const [authLoading, setAuthLoading] = useState(false);
|
|
233
93
|
const [configValues, setConfigValues] = useState({});
|
|
234
94
|
const [integrationSummary, setIntegrationSummary] = useState(null);
|
|
235
95
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
96
|
+
// Company details can edit a company that is not the globally selected one.
|
|
97
|
+
// The embedded route id is authoritative for all fiscal reads and writes.
|
|
98
|
+
const providerId = useMemo(
|
|
99
|
+
() => resolveProviderId({ route, currentCompany }),
|
|
100
|
+
[route?.params?.companyId, currentCompany?.id],
|
|
240
101
|
);
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
setConfigValues({});
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
setConfigValues(buildFieldValues(providerConfig, source));
|
|
250
|
-
},
|
|
251
|
-
[providerConfig],
|
|
102
|
+
const providerIri = useMemo(() => (providerId ? `/people/${providerId}` : ''), [providerId]);
|
|
103
|
+
const fallbackConfigs = useMemo(
|
|
104
|
+
() => resolveFallbackConfigs({ providerId, currentCompany }),
|
|
105
|
+
[providerId, currentCompany?.id, currentCompany?.configs],
|
|
252
106
|
);
|
|
253
107
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}, [
|
|
108
|
+
const syncConfigValues = useCallback(source => {
|
|
109
|
+
setConfigValues(providerConfig ? buildFieldValues(providerConfig, source) : {});
|
|
110
|
+
}, [providerConfig]);
|
|
257
111
|
|
|
112
|
+
useEffect(() => syncConfigValues(fallbackConfigs), [fallbackConfigs, syncConfigValues]);
|
|
258
113
|
useEffect(() => {
|
|
259
114
|
const oauthStatus = normalizeTextValue(route?.params?.oauth_status).toLowerCase();
|
|
260
115
|
const oauthError = normalizeTextValue(route?.params?.oauth_error);
|
|
261
116
|
const oauthKey = `${oauthStatus}|${oauthError}`;
|
|
262
|
-
|
|
263
|
-
if (!oauthStatus || oauthNoticeRef.current === oauthKey) {
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
117
|
+
if (!oauthStatus || oauthNoticeRef.current === oauthKey) return;
|
|
267
118
|
oauthNoticeRef.current = oauthKey;
|
|
268
|
-
|
|
269
|
-
if (oauthStatus === '
|
|
270
|
-
showSuccess('Uber conectado com sucesso.');
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (oauthStatus === 'error') {
|
|
275
|
-
showError(formatUberOAuthError(oauthError));
|
|
276
|
-
}
|
|
119
|
+
if (oauthStatus === 'success') showSuccess('Uber conectado com sucesso.');
|
|
120
|
+
if (oauthStatus === 'error') showError(formatUberOAuthError(oauthError));
|
|
277
121
|
}, [route?.params?.oauth_error, route?.params?.oauth_status, showError, showSuccess]);
|
|
278
122
|
|
|
279
123
|
const loadPageData = useCallback(async ({ showLoading = true } = {}) => {
|
|
@@ -282,119 +126,61 @@ export default function IntegrationConfigPage({ route, navigation, embedded = fa
|
|
|
282
126
|
setLoading(false);
|
|
283
127
|
return;
|
|
284
128
|
}
|
|
285
|
-
|
|
286
|
-
if (showLoading) {
|
|
287
|
-
setLoading(true);
|
|
288
|
-
}
|
|
289
|
-
|
|
129
|
+
if (showLoading) setLoading(true);
|
|
290
130
|
try {
|
|
291
|
-
const integrationPromise = api.fetch('/marketplace/integrations', {
|
|
292
|
-
|
|
293
|
-
provider_id: providerId,
|
|
294
|
-
},
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
if (configFields.length > 0) {
|
|
131
|
+
const integrationPromise = api.fetch('/marketplace/integrations', { params: { provider_id: providerId } });
|
|
132
|
+
if (configFields.length) {
|
|
298
133
|
const [configResponse, integrationResponse] = await Promise.all([
|
|
299
|
-
api.fetch('/configs', {
|
|
300
|
-
params: {
|
|
301
|
-
people: providerIri,
|
|
302
|
-
},
|
|
303
|
-
}),
|
|
134
|
+
api.fetch('/configs', { params: { people: providerIri } }),
|
|
304
135
|
integrationPromise,
|
|
305
136
|
]);
|
|
306
|
-
|
|
307
137
|
syncConfigValues(parseIntegrationCollection(configResponse));
|
|
308
|
-
setIntegrationSummary(
|
|
309
|
-
|
|
310
|
-
);
|
|
311
|
-
return;
|
|
138
|
+
setIntegrationSummary(getIntegrationByKey(integrationResponse, providerConfig.key));
|
|
139
|
+
} else {
|
|
140
|
+
setIntegrationSummary(getIntegrationByKey(await integrationPromise, providerConfig.key));
|
|
312
141
|
}
|
|
313
|
-
|
|
314
|
-
const integrationResponse = await integrationPromise;
|
|
315
|
-
setIntegrationSummary(
|
|
316
|
-
getIntegrationByKey(integrationResponse, providerConfig.key),
|
|
317
|
-
);
|
|
318
142
|
} catch (error) {
|
|
319
143
|
showError(formatApiError(error));
|
|
320
|
-
syncConfigValues(
|
|
144
|
+
syncConfigValues(fallbackConfigs);
|
|
321
145
|
setIntegrationSummary(null);
|
|
322
146
|
} finally {
|
|
323
|
-
if (showLoading)
|
|
324
|
-
setLoading(false);
|
|
325
|
-
}
|
|
147
|
+
if (showLoading) setLoading(false);
|
|
326
148
|
}
|
|
327
|
-
}, [
|
|
328
|
-
configFields.length,
|
|
329
|
-
currentCompany?.configs,
|
|
330
|
-
providerConfig,
|
|
331
|
-
providerIri,
|
|
332
|
-
providerId,
|
|
333
|
-
showError,
|
|
334
|
-
syncConfigValues,
|
|
335
|
-
]);
|
|
336
|
-
|
|
337
|
-
useFocusEffect(
|
|
338
|
-
useCallback(() => {
|
|
339
|
-
loadPageData();
|
|
340
|
-
}, [loadPageData]),
|
|
341
|
-
);
|
|
149
|
+
}, [configFields.length, fallbackConfigs, providerConfig, providerIri, providerId, showError, syncConfigValues]);
|
|
342
150
|
|
|
151
|
+
useFocusEffect(useCallback(() => { loadPageData(); }, [loadPageData]));
|
|
343
152
|
const onRefresh = useCallback(async () => {
|
|
344
153
|
setRefreshing(true);
|
|
345
|
-
try {
|
|
346
|
-
await loadPageData({ showLoading: false });
|
|
347
|
-
} finally {
|
|
348
|
-
setRefreshing(false);
|
|
349
|
-
}
|
|
154
|
+
try { await loadPageData({ showLoading: false }); } finally { setRefreshing(false); }
|
|
350
155
|
}, [loadPageData]);
|
|
351
|
-
|
|
352
156
|
const updateField = useCallback((fieldKey, value) => {
|
|
353
|
-
setConfigValues(
|
|
354
|
-
...currentValues,
|
|
355
|
-
[fieldKey]: value,
|
|
356
|
-
}));
|
|
157
|
+
setConfigValues(current => ({ ...current, [fieldKey]: value }));
|
|
357
158
|
}, []);
|
|
358
159
|
|
|
359
160
|
const handleOAuthConnect = useCallback(async () => {
|
|
360
|
-
if (!providerIri || !providerConfig
|
|
161
|
+
if (!providerIri || !providerConfig?.oauthConnect) {
|
|
361
162
|
showError('Nao foi possivel identificar a integracao selecionada.');
|
|
362
163
|
return;
|
|
363
164
|
}
|
|
364
|
-
|
|
365
165
|
setAuthLoading(true);
|
|
366
166
|
try {
|
|
367
167
|
const response = await api.fetch(providerConfig.authorizationEndpoint, {
|
|
368
|
-
method: 'POST',
|
|
369
|
-
body: {
|
|
370
|
-
provider_id: providerId,
|
|
371
|
-
return_path: returnPath,
|
|
372
|
-
},
|
|
168
|
+
method: 'POST', body: { provider_id: providerId, return_path: returnPath },
|
|
373
169
|
});
|
|
374
|
-
|
|
375
170
|
const authUrl = extractAuthorizationUrl(response);
|
|
376
|
-
if (!authUrl)
|
|
377
|
-
showError('Nao foi possivel iniciar o login do Uber.');
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
|
|
171
|
+
if (!authUrl) return showError('Nao foi possivel iniciar o login do Uber.');
|
|
381
172
|
await openAuthorizationUrl(authUrl);
|
|
382
173
|
showSuccess('Abrindo login do Uber.');
|
|
383
174
|
} catch (error) {
|
|
384
175
|
showError(error?.message || 'Nao foi possivel iniciar o login do Uber.');
|
|
385
|
-
} finally {
|
|
386
|
-
setAuthLoading(false);
|
|
387
|
-
}
|
|
176
|
+
} finally { setAuthLoading(false); }
|
|
388
177
|
}, [providerConfig, providerId, providerIri, returnPath, showError, showSuccess]);
|
|
389
178
|
|
|
390
179
|
const requiredKeys = providerConfig?.requiredKeys || [];
|
|
391
180
|
const connected = integrationSummary && typeof integrationSummary.connected !== 'undefined'
|
|
392
181
|
? isConnectedValue(integrationSummary.connected)
|
|
393
|
-
: requiredKeys.length > 0
|
|
394
|
-
? requiredKeys.every(fieldKey => normalizeTextValue(configValues[fieldKey]) !== '')
|
|
395
|
-
: false;
|
|
182
|
+
: requiredKeys.length > 0 && requiredKeys.every(key => normalizeTextValue(configValues[key]) !== '');
|
|
396
183
|
const statusTone = connected ? '#16A34A' : '#e67e22';
|
|
397
|
-
const statusText = connected ? 'Conectado' : 'Pendente';
|
|
398
184
|
const editable = Boolean(providerIri && providerConfig && !isSaving && !loading && !providerConfig.oauthConnect);
|
|
399
185
|
const actionLoading = providerConfig?.oauthConnect ? authLoading : isSaving;
|
|
400
186
|
const actionDisabled = providerConfig?.oauthConnect ? authLoading || loading : !editable;
|
|
@@ -404,337 +190,82 @@ export default function IntegrationConfigPage({ route, navigation, embedded = fa
|
|
|
404
190
|
showError('Nao foi possivel identificar a integracao selecionada.');
|
|
405
191
|
return;
|
|
406
192
|
}
|
|
407
|
-
|
|
408
193
|
const configs = configFields.map(field => ({
|
|
409
194
|
configKey: field.key,
|
|
410
195
|
configValue: toConfigRequestValue(normalizeTextValue(configValues[field.key])),
|
|
411
196
|
}));
|
|
412
|
-
|
|
413
197
|
try {
|
|
414
|
-
await configActions.addManyConfigs({
|
|
415
|
-
configs,
|
|
416
|
-
people: providerIri,
|
|
417
|
-
module: 4,
|
|
418
|
-
visibility: 'public',
|
|
419
|
-
});
|
|
420
|
-
|
|
198
|
+
await configActions.addManyConfigs({ configs, people: providerIri, module: 4, visibility: 'public' });
|
|
421
199
|
showSuccess(`${providerConfig.label} salvo com sucesso.`);
|
|
422
200
|
await loadPageData({ showLoading: false });
|
|
423
|
-
} catch (error) {
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
}, [
|
|
427
|
-
configActions,
|
|
428
|
-
configFields,
|
|
429
|
-
configValues,
|
|
430
|
-
loadPageData,
|
|
431
|
-
providerConfig,
|
|
432
|
-
providerIri,
|
|
433
|
-
showError,
|
|
434
|
-
showSuccess,
|
|
435
|
-
]);
|
|
201
|
+
} catch (error) { showError(error?.message || 'Nao foi possivel salvar a integracao.'); }
|
|
202
|
+
}, [configActions, configFields, configValues, loadPageData, providerConfig, providerIri, showError, showSuccess]);
|
|
436
203
|
|
|
437
|
-
if (!providerConfig)
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
<View style={styles.centerState}>
|
|
441
|
-
<Icon name="alert-triangle" size={32} color="#e67e22" />
|
|
442
|
-
<Text style={styles.centerStateTitle}>Integracao indisponivel</Text>
|
|
443
|
-
<Text style={styles.centerStateText}>
|
|
444
|
-
A tela solicitada nao possui configuracao cadastrada.
|
|
445
|
-
</Text>
|
|
446
|
-
</View>
|
|
447
|
-
</SafeAreaView>
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if (!providerId) {
|
|
452
|
-
return (
|
|
453
|
-
<SafeAreaView style={styles.container} edges={['bottom']}>
|
|
454
|
-
<View style={styles.centerState}>
|
|
455
|
-
<Icon name="building" size={32} color="#94A3B8" />
|
|
456
|
-
<Text style={styles.centerStateTitle}>Selecione uma empresa</Text>
|
|
457
|
-
<Text style={styles.centerStateText}>
|
|
458
|
-
A configuracao da integracao depende da empresa ativa.
|
|
459
|
-
</Text>
|
|
460
|
-
</View>
|
|
461
|
-
</SafeAreaView>
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
if (loading) {
|
|
466
|
-
return (
|
|
467
|
-
<SafeAreaView style={[styles.container, { backgroundColor: brandColors.background }]} edges={['bottom']}>
|
|
468
|
-
<View style={styles.centerState}>
|
|
469
|
-
<ActivityIndicator size="large" color={providerConfig.accent} />
|
|
470
|
-
<Text style={styles.centerStateTitle}>Carregando integracao</Text>
|
|
471
|
-
<Text style={styles.centerStateText}>
|
|
472
|
-
Buscando as credenciais salvas para a empresa ativa.
|
|
473
|
-
</Text>
|
|
474
|
-
</View>
|
|
475
|
-
</SafeAreaView>
|
|
476
|
-
);
|
|
477
|
-
}
|
|
204
|
+
if (!providerConfig) return <CenterState icon="alert-triangle" iconColor="#e67e22" title="Integracao indisponivel" text="A tela solicitada nao possui configuracao cadastrada." />;
|
|
205
|
+
if (!providerId) return <CenterState icon="building" iconColor="#94A3B8" title="Selecione uma empresa" text="A configuracao da integracao depende da empresa ativa." />;
|
|
206
|
+
if (loading) return <CenterState backgroundColor={brandColors.background} spinnerColor={providerConfig.accent} title="Carregando integracao" text="Buscando as credenciais salvas para a empresa ativa." />;
|
|
478
207
|
|
|
479
208
|
return (
|
|
480
209
|
<SafeAreaView style={[styles.container, { backgroundColor: brandColors.background }]} edges={['bottom']}>
|
|
481
|
-
<ScrollView
|
|
482
|
-
|
|
483
|
-
showsVerticalScrollIndicator={false}
|
|
484
|
-
refreshControl={
|
|
485
|
-
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={providerConfig.accent} />
|
|
486
|
-
}>
|
|
210
|
+
<ScrollView contentContainerStyle={styles.scroll} showsVerticalScrollIndicator={false}
|
|
211
|
+
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={providerConfig.accent} />}>
|
|
487
212
|
{!embedded ? (
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
</View>
|
|
496
|
-
<View style={styles.heroBadge}>
|
|
497
|
-
<Icon name={providerConfig.icon} size={22} color={providerConfig.accent} />
|
|
498
|
-
</View>
|
|
499
|
-
</View>
|
|
500
|
-
) : (
|
|
501
|
-
<View style={[styles.embeddedHeader, shadowStyle]}>
|
|
502
|
-
<Text style={styles.embeddedTitle}>Configuracoes fiscais</Text>
|
|
213
|
+
<View style={[styles.heroCard, shadowStyle, { backgroundColor: providerConfig.accent }]}>
|
|
214
|
+
<View style={styles.heroCopy}>
|
|
215
|
+
<Text style={styles.heroEyebrow}>INTEGRACAO</Text>
|
|
216
|
+
<Text style={styles.heroTitle}>{providerConfig.label}</Text>
|
|
217
|
+
<Text style={styles.heroText}>{providerConfig.description}</Text>
|
|
218
|
+
</View>
|
|
219
|
+
<View style={styles.heroBadge}><Icon name={providerConfig.icon} size={22} color={providerConfig.accent} /></View>
|
|
503
220
|
</View>
|
|
504
|
-
)}
|
|
221
|
+
) : <View style={[styles.embeddedHeader, shadowStyle]}><Text style={styles.embeddedTitle}>Configuracoes fiscais</Text></View>}
|
|
505
222
|
|
|
506
223
|
{!embedded ? (
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
{providerConfig.oauthConnect
|
|
224
|
+
<View style={[styles.statusCard, shadowStyle]}>
|
|
225
|
+
<View style={styles.statusHeader}>
|
|
226
|
+
<View style={styles.statusCopy}>
|
|
227
|
+
<Text style={styles.sectionTitle}>Status</Text>
|
|
228
|
+
<Text style={styles.sectionSubtitle}>{providerConfig.oauthConnect
|
|
513
229
|
? 'A integracao fica conectada quando o login do Uber termina e o store e salvo automaticamente.'
|
|
514
|
-
: 'A integracao so aparece como conectada quando todos os campos obrigatorios foram salvos na empresa ativa.'}
|
|
515
|
-
</
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
style={[
|
|
520
|
-
styles.statusBadge,
|
|
521
|
-
{ backgroundColor: withOpacity(statusTone, 0.12) },
|
|
522
|
-
]}>
|
|
523
|
-
<Text style={[styles.statusBadgeText, { color: statusTone }]}>
|
|
524
|
-
{statusText}
|
|
525
|
-
</Text>
|
|
230
|
+
: 'A integracao so aparece como conectada quando todos os campos obrigatorios foram salvos na empresa ativa.'}</Text>
|
|
231
|
+
</View>
|
|
232
|
+
<View style={[styles.statusBadge, { backgroundColor: withOpacity(statusTone, 0.12) }]}>
|
|
233
|
+
<Text style={[styles.statusBadgeText, { color: statusTone }]}>{connected ? 'Conectado' : 'Pendente'}</Text>
|
|
234
|
+
</View>
|
|
526
235
|
</View>
|
|
527
236
|
</View>
|
|
528
|
-
</View>
|
|
529
237
|
) : null}
|
|
530
238
|
|
|
531
239
|
<View style={[styles.formCard, shadowStyle]}>
|
|
532
|
-
<Text style={styles.cardTitle}>
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
? (activeTabDef?.label || 'Configuracoes')
|
|
537
|
-
: 'Credenciais'}
|
|
538
|
-
</Text>
|
|
539
|
-
{!embedded ? (
|
|
540
|
-
<Text style={styles.cardSubtitle}>
|
|
541
|
-
{providerConfig.oauthConnect
|
|
542
|
-
? 'Use o login oficial do Uber. A store sera localizada e gravada automaticamente na empresa ativa.'
|
|
543
|
-
: 'Salve as credenciais na empresa ativa. O hub de integracoes volta a mostrar o status correto quando voce retornar para a lista.'}
|
|
544
|
-
</Text>
|
|
545
|
-
) : null}
|
|
240
|
+
<Text style={styles.cardTitle}>{providerConfig.oauthConnect ? 'Conexao' : fiscalTabs.length ? activeTabDef?.label || 'Configuracoes' : 'Credenciais'}</Text>
|
|
241
|
+
{!embedded ? <Text style={styles.cardSubtitle}>{providerConfig.oauthConnect
|
|
242
|
+
? 'Use o login oficial do Uber. A store sera localizada e gravada automaticamente na empresa ativa.'
|
|
243
|
+
: 'Salve as credenciais na empresa ativa. O hub de integracoes volta a mostrar o status correto quando voce retornar para a lista.'}</Text> : null}
|
|
546
244
|
|
|
547
245
|
{providerConfig.oauthConnect ? (
|
|
548
|
-
<View style={styles.fieldList}>
|
|
549
|
-
<
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
Nao ha campos manuais. O login autoriza o app e salva o store automaticamente.
|
|
553
|
-
</Text>
|
|
554
|
-
</View>
|
|
555
|
-
</View>
|
|
246
|
+
<View style={styles.fieldList}><View style={styles.fieldGroup}>
|
|
247
|
+
<Text style={styles.fieldLabel}>Uber OAuth</Text>
|
|
248
|
+
<Text style={styles.fieldKey}>Nao ha campos manuais. O login autoriza o app e salva o store automaticamente.</Text>
|
|
249
|
+
</View></View>
|
|
556
250
|
) : (
|
|
557
251
|
<View style={styles.fieldList}>
|
|
558
|
-
{fiscalTabs.length >
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
<Text
|
|
569
|
-
style={[
|
|
570
|
-
styles.subTabLabel,
|
|
571
|
-
selected && styles.subTabLabelActive,
|
|
572
|
-
]}>
|
|
573
|
-
{tab.label}
|
|
574
|
-
</Text>
|
|
575
|
-
</TouchableOpacity>
|
|
576
|
-
);
|
|
577
|
-
})}
|
|
578
|
-
</View>
|
|
579
|
-
) : null}
|
|
580
|
-
{activeTabDef?.description ? (
|
|
581
|
-
<Text style={styles.tabDescription}>{activeTabDef.description}</Text>
|
|
582
|
-
) : null}
|
|
583
|
-
{visibleFields.map(field => (
|
|
584
|
-
<View key={field.key} style={styles.fieldGroup}>
|
|
585
|
-
<Text style={styles.fieldLabel}>{field.label}</Text>
|
|
586
|
-
{!embedded ? <Text style={styles.fieldKey}>{field.key}</Text> : null}
|
|
587
|
-
{field.type === 'select' ? (
|
|
588
|
-
<View style={styles.selectList}>
|
|
589
|
-
{(field.options || []).map(option => {
|
|
590
|
-
const selected = String(configValues[field.key] || '') === String(option.value);
|
|
591
|
-
return (
|
|
592
|
-
<TouchableOpacity
|
|
593
|
-
key={String(option.value)}
|
|
594
|
-
style={[
|
|
595
|
-
styles.selectOption,
|
|
596
|
-
selected && styles.selectOptionActive,
|
|
597
|
-
!editable && styles.inputDisabled,
|
|
598
|
-
]}
|
|
599
|
-
disabled={!editable}
|
|
600
|
-
activeOpacity={0.85}
|
|
601
|
-
onPress={() => updateField(field.key, String(option.value))}>
|
|
602
|
-
<Text
|
|
603
|
-
style={[
|
|
604
|
-
styles.selectOptionText,
|
|
605
|
-
selected && styles.selectOptionTextActive,
|
|
606
|
-
]}>
|
|
607
|
-
{option.label}
|
|
608
|
-
</Text>
|
|
609
|
-
</TouchableOpacity>
|
|
610
|
-
);
|
|
611
|
-
})}
|
|
612
|
-
</View>
|
|
613
|
-
) : field.type === 'file' ? (
|
|
614
|
-
<View style={styles.fileFieldWrap}>
|
|
615
|
-
{configValues[field.key] ? (
|
|
616
|
-
<Text style={styles.fieldHint}>
|
|
617
|
-
Arquivo vinculado (id: {String(configValues[field.key]).replace(/\D/g, '') || configValues[field.key]})
|
|
618
|
-
</Text>
|
|
619
|
-
) : (
|
|
620
|
-
<Text style={styles.fieldHint}>Nenhum certificado vinculado.</Text>
|
|
621
|
-
)}
|
|
622
|
-
<DefaultUpload
|
|
623
|
-
relationStoreName="people"
|
|
624
|
-
relationField="people"
|
|
625
|
-
relationResource="people"
|
|
626
|
-
entityId={providerId}
|
|
627
|
-
companyId={providerId}
|
|
628
|
-
context={field.fileContext || 'company_certificate'}
|
|
629
|
-
libraryContexts={[field.fileContext || 'company_certificate']}
|
|
630
|
-
acceptedTypes={field.accept || '.pfx,.p12,application/x-pkcs12'}
|
|
631
|
-
fileType=""
|
|
632
|
-
fileTypeLabel="certificado"
|
|
633
|
-
title={field.label}
|
|
634
|
-
triggerLabel="Gerenciar certificado"
|
|
635
|
-
managerTitle="Gerenciador de arquivos"
|
|
636
|
-
searchPlaceholder="Buscar certificado"
|
|
637
|
-
uploadButtonLabel="Enviar certificado"
|
|
638
|
-
emptyAttachmentLabel="Nenhum certificado anexado."
|
|
639
|
-
emptyLibraryLabel="Nenhum arquivo encontrado."
|
|
640
|
-
uploadSuccessMessage="Certificado enviado."
|
|
641
|
-
attachSuccessMessage="Certificado vinculado."
|
|
642
|
-
removeSuccessMessage="Certificado removido."
|
|
643
|
-
showInlineContent={false}
|
|
644
|
-
uploadResultAlreadyAttached
|
|
645
|
-
requireEntity={false}
|
|
646
|
-
onUploadFile={async ({ file, companyId, context, entityId }) => {
|
|
647
|
-
const uploaded = await uploadFileToApi({
|
|
648
|
-
file,
|
|
649
|
-
context: context || field.fileContext || 'company_certificate',
|
|
650
|
-
peopleId: companyId || providerId,
|
|
651
|
-
entityId: entityId || providerId,
|
|
652
|
-
});
|
|
653
|
-
const id = extractFileId(uploaded);
|
|
654
|
-
const iri = toFileIri(uploaded);
|
|
655
|
-
const value = id ? String(id) : iri || '';
|
|
656
|
-
if (!value) {
|
|
657
|
-
throw new Error('Upload sem identificador de arquivo.');
|
|
658
|
-
}
|
|
659
|
-
updateField(field.key, value);
|
|
660
|
-
return uploaded;
|
|
661
|
-
}}
|
|
662
|
-
onAttachFile={async fileObj => {
|
|
663
|
-
const id = extractFileId(fileObj);
|
|
664
|
-
const iri = toFileIri(fileObj);
|
|
665
|
-
const value = id ? String(id) : iri || '';
|
|
666
|
-
if (!value) {
|
|
667
|
-
throw new Error('Arquivo sem identificador.');
|
|
668
|
-
}
|
|
669
|
-
updateField(field.key, value);
|
|
670
|
-
return fileObj;
|
|
671
|
-
}}
|
|
672
|
-
onRemoveAttachment={async () => {
|
|
673
|
-
updateField(field.key, '');
|
|
674
|
-
return true;
|
|
675
|
-
}}
|
|
676
|
-
renderTrigger={({ openManager, uploading }) => (
|
|
677
|
-
<TouchableOpacity
|
|
678
|
-
style={[
|
|
679
|
-
styles.filePickerButton,
|
|
680
|
-
!editable && styles.inputDisabled,
|
|
681
|
-
]}
|
|
682
|
-
disabled={!editable || uploading}
|
|
683
|
-
activeOpacity={0.85}
|
|
684
|
-
onPress={openManager}>
|
|
685
|
-
{uploading ? (
|
|
686
|
-
<ActivityIndicator color="#166534" />
|
|
687
|
-
) : (
|
|
688
|
-
<Icon name="folder" size={16} color="#166534" />
|
|
689
|
-
)}
|
|
690
|
-
<Text style={styles.filePickerButtonText}>
|
|
691
|
-
{configValues[field.key]
|
|
692
|
-
? 'Trocar certificado (gerenciador)'
|
|
693
|
-
: 'Selecionar / enviar certificado'}
|
|
694
|
-
</Text>
|
|
695
|
-
</TouchableOpacity>
|
|
696
|
-
)}
|
|
697
|
-
/>
|
|
698
|
-
</View>
|
|
699
|
-
) : (
|
|
700
|
-
<TextInput
|
|
701
|
-
style={[
|
|
702
|
-
styles.input,
|
|
703
|
-
!editable && styles.inputDisabled,
|
|
704
|
-
]}
|
|
705
|
-
value={configValues[field.key] || ''}
|
|
706
|
-
onChangeText={value => updateField(field.key, value)}
|
|
707
|
-
editable={editable}
|
|
708
|
-
autoCapitalize="none"
|
|
709
|
-
autoCorrect={false}
|
|
710
|
-
secureTextEntry={Boolean(field.secureTextEntry)}
|
|
711
|
-
placeholder={field.placeholder}
|
|
712
|
-
/>
|
|
713
|
-
)}
|
|
714
|
-
</View>
|
|
715
|
-
))}
|
|
252
|
+
{fiscalTabs.length ? <View style={styles.subTabRow}>{fiscalTabs.map(tab => {
|
|
253
|
+
const selected = tab.key === (activeTabDef?.key || activeFiscalTab);
|
|
254
|
+
return <TouchableOpacity key={tab.key} style={[styles.subTabButton, selected && styles.subTabButtonActive]}
|
|
255
|
+
activeOpacity={0.85} onPress={() => setActiveFiscalTab(tab.key)}>
|
|
256
|
+
<Text style={[styles.subTabLabel, selected && styles.subTabLabelActive]}>{tab.label}</Text>
|
|
257
|
+
</TouchableOpacity>;
|
|
258
|
+
})}</View> : null}
|
|
259
|
+
{activeTabDef?.description ? <Text style={styles.tabDescription}>{activeTabDef.description}</Text> : null}
|
|
260
|
+
<IntegrationConfigFields fields={visibleFields} configValues={configValues} editable={editable}
|
|
261
|
+
embedded={embedded} providerId={providerId} updateField={updateField} />
|
|
716
262
|
</View>
|
|
717
263
|
)}
|
|
718
264
|
|
|
719
|
-
<TouchableOpacity
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
actionDisabled && styles.saveButtonDisabled,
|
|
724
|
-
]}
|
|
725
|
-
disabled={actionDisabled}
|
|
726
|
-
activeOpacity={0.9}
|
|
727
|
-
onPress={providerConfig.oauthConnect ? handleOAuthConnect : saveIntegration}>
|
|
728
|
-
{actionLoading ? (
|
|
729
|
-
<ActivityIndicator color="#FFFFFF" />
|
|
730
|
-
) : (
|
|
731
|
-
<Icon name={providerConfig.oauthConnect ? 'log-in' : 'save'} size={16} color="#FFFFFF" />
|
|
732
|
-
)}
|
|
733
|
-
<Text style={styles.saveButtonText}>
|
|
734
|
-
{providerConfig.oauthConnect
|
|
735
|
-
? providerConfig.connectLabel || 'Conectar'
|
|
736
|
-
: providerConfig.saveLabel}
|
|
737
|
-
</Text>
|
|
265
|
+
<TouchableOpacity style={[styles.saveButton, { backgroundColor: providerConfig.accent }, actionDisabled && styles.saveButtonDisabled]}
|
|
266
|
+
disabled={actionDisabled} activeOpacity={0.9} onPress={providerConfig.oauthConnect ? handleOAuthConnect : saveIntegration}>
|
|
267
|
+
{actionLoading ? <ActivityIndicator color="#FFFFFF" /> : <Icon name={providerConfig.oauthConnect ? 'log-in' : 'save'} size={16} color="#FFFFFF" />}
|
|
268
|
+
<Text style={styles.saveButtonText}>{providerConfig.oauthConnect ? providerConfig.connectLabel || 'Conectar' : providerConfig.saveLabel}</Text>
|
|
738
269
|
</TouchableOpacity>
|
|
739
270
|
</View>
|
|
740
271
|
</ScrollView>
|