@controleonline/ui-common 1.2.70 → 1.2.75
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/.github/actions/workers/manager/action.yml +147 -0
- package/.github/actions/workers/qa/action.yml +44 -0
- package/.github/actions/workers/security/action.yml +44 -0
- package/.github/actions/workers/technical-documenter/action.yml +51 -0
- package/.github/workflows/manager-worker.yml +168 -0
- package/.github/workflows/publish-npm.yml +45 -0
- package/.gitmodules +3 -0
- package/AGENTS.md +6 -0
- package/README.md +14 -0
- package/package.json +32 -28
- package/src/api/fetch.js +1 -1
- package/src/api/index.js +328 -58
- package/src/react/components/AddImportModal.js +95 -111
- package/src/react/components/AnimatedModal.js +171 -0
- package/src/react/components/AnimatedModal.styles.js +21 -0
- package/src/react/components/AppTypeSwitcher.js +164 -0
- package/src/react/components/AppTypeSwitcher.styles.js +109 -0
- package/src/react/components/BackgroundRuntimeBridge.js +5 -4
- package/src/react/components/BottomNavigationBar.config.js +211 -231
- package/src/react/components/BottomNavigationBar.js +86 -31
- package/src/react/components/BottomNavigationBar.styles.js +118 -110
- package/src/react/components/CategoryForm.js +52 -35
- package/src/react/components/DefaultProvider.native.js +194 -156
- package/src/react/components/DefaultProvider.web.js +188 -112
- package/src/react/components/DeliveryPushBridge.native.js +2 -2
- package/src/react/components/DeviceAlertSoundService.js +3 -3
- package/src/react/components/KioskModeBridge.js +2 -2
- package/src/react/components/LauncherModeBridge.js +2 -2
- package/src/react/components/ManagerPushBridge.native.js +2 -2
- package/src/react/components/NetworkPrinterPreviewModal.js +249 -0
- package/src/react/components/NetworkPrinterPreviewModal.styles.js +159 -0
- package/src/react/components/RemoteCheckoutService.js +28 -20
- package/src/react/components/RuntimeBottomNavigationBar.js +146 -119
- package/src/react/components/RuntimeInfoFooter.js +137 -45
- package/src/react/components/RuntimeInfoFooter.styles.js +3 -0
- package/src/react/components/StateStore.js +258 -0
- package/src/react/components/UnifiedPaymentBar.js +7 -4
- package/src/react/components/UserAvatar.js +160 -0
- package/src/react/components/WebsocketListener.native.js +11 -11
- package/src/react/components/integrations/IntegrationHero/index.js +48 -0
- package/src/react/components/integrations/IntegrationHero/styles.js +57 -0
- package/src/react/components/integrations/IntegrationTabs/index.js +89 -0
- package/src/react/components/integrations/IntegrationTabs/styles.js +44 -0
- package/src/react/config/deviceConfigBootstrap.js +71 -30
- package/src/react/css/orders.js +72 -0
- package/src/react/pages/Connections.js +229 -0
- package/src/react/pages/Connections.styles.js +222 -0
- package/src/react/pages/DeviceDetailPage.js +2814 -0
- package/src/react/pages/DeviceDetailPage.styles.js +308 -0
- package/src/react/pages/Devices/currentDevice.js +194 -0
- package/src/react/pages/Devices/deviceTypes/DeviceGroupCard.js +257 -0
- package/src/react/pages/Devices/deviceTypes/all.js +23 -0
- package/src/react/pages/Devices/deviceTypes/device.js +25 -0
- package/src/react/pages/Devices/deviceTypes/deviceListHelpers.js +178 -0
- package/src/react/pages/Devices/deviceTypes/display.js +26 -0
- package/src/react/pages/Devices/deviceTypes/index.js +145 -0
- package/src/react/pages/Devices/deviceTypes/ipCamera.js +35 -0
- package/src/react/pages/Devices/deviceTypes/pdv.js +88 -0
- package/src/react/pages/Devices/deviceTypes/printer.js +37 -0
- package/src/react/pages/Devices/deviceTypes/shared.js +453 -0
- package/src/react/pages/Devices/deviceTypes/useDeviceNetworkConnectivity.js +145 -0
- package/src/react/pages/Devices.js +183 -0
- package/src/react/pages/Devices.styles.js +353 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99CatalogTab.js +219 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99OverviewTab.js +134 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99PreviewModal.js +173 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99ProductCard.js +1 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99QuickWalletModal.js +181 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99SettingsTab.js +306 -0
- package/src/react/pages/Food99IntegrationPage/components/Food99StoreTab.js +417 -0
- package/src/react/pages/Food99IntegrationPage/index.js +1530 -0
- package/src/react/pages/Food99IntegrationPage/styles.js +792 -0
- package/src/react/pages/Food99IntegrationPage/utils.js +155 -0
- package/src/react/pages/Food99IntegrationPage.js +1 -0
- package/src/react/pages/Food99IntegrationPage.styles.js +1 -0
- package/src/react/pages/GenericLogPage.js +42 -167
- package/src/react/pages/IFoodIntegrationPage/components/IFoodCatalogTab.js +8 -0
- package/src/react/pages/IFoodIntegrationPage/components/IFoodOperationsTab.js +420 -0
- package/src/react/pages/IFoodIntegrationPage/components/IFoodOverviewTab.js +118 -0
- package/src/react/pages/IFoodIntegrationPage/components/IFoodPreviewModal.js +103 -0
- package/src/react/pages/IFoodIntegrationPage/components/IFoodProductCard.js +1 -0
- package/src/react/pages/IFoodIntegrationPage/components/IFoodStoreTab.js +150 -0
- package/src/react/pages/IFoodIntegrationPage/index.js +1298 -0
- package/src/react/pages/IFoodIntegrationPage/styles.js +764 -0
- package/src/react/pages/IFoodIntegrationPage/utils.js +91 -0
- package/src/react/pages/IFoodIntegrationPage.js +1 -0
- package/src/react/pages/IFoodIntegrationPage.styles.js +1 -0
- package/src/react/pages/Imports.js +214 -166
- package/src/react/pages/IntegrationConfigPage.js +744 -0
- package/src/react/pages/IntegrationConfigPage.styles.js +269 -0
- package/src/react/pages/Integrations.js +299 -0
- package/src/react/pages/Integrations.styles.js +130 -0
- package/src/react/pages/ManagerOrderNotificationsPage.js +642 -0
- package/src/react/pages/MarketplaceIntegrationPage/components/MarketplaceProductCard.js +205 -0
- package/src/react/pages/MarketplaceIntegrationPage/components/RemoteMarketplaceMenuTab.js +335 -0
- package/src/react/pages/MarketplaceIntegrationPage/index.js +60 -0
- package/src/react/pages/MarketplaceIntegrationPage/utils.js +137 -0
- package/src/react/pages/MercadoLivreIntegrationPage.js +538 -0
- package/src/react/pages/PrinterDeviceDetailPage.js +1916 -0
- package/src/react/pages/PrinterDeviceDetailPage.styles.js +223 -0
- package/src/react/pages/PrinterDeviceFormPage.js +521 -0
- package/src/react/pages/PrinterDeviceFormPage.styles.js +159 -0
- package/src/react/pages/SettingsPage/PaymentTypesByWalletTab.js +484 -454
- package/src/react/pages/SettingsPage/index.js +1266 -1167
- package/src/react/pages/WhatsAppConnectionPage.js +459 -0
- package/src/react/pages/WhatsAppConnectionPage.styles.js +284 -0
- package/src/react/pages/integrationsCatalog.js +399 -0
- package/src/react/print/providers/local.js +1 -1
- package/src/react/router/publicRoutes.js +25 -0
- package/src/react/services/Cielo/Checkout.js +39 -0
- package/src/react/services/Cielo/Cielo.js +193 -0
- package/src/react/services/Cielo/Print.js +7 -0
- package/src/react/services/InfinitePay/Checkout.js +33 -0
- package/src/react/services/InfinitePay/InfinitePay.js +24 -0
- package/src/react/{utils → services}/paymentGatewayExecution.js +91 -91
- package/src/react/styles/global.js +115 -0
- package/src/react/utils/categoryContexts.js +46 -0
- package/src/react/utils/entityDisplay.js +222 -217
- package/src/react/utils/fileUrl.js +201 -16
- package/src/react/utils/integrationPage.js +16 -0
- package/src/react/utils/menuNavigation.js +31 -0
- package/src/react/utils/orderIdentity.js +277 -0
- package/src/react/utils/remotePayment.js +115 -61
- package/src/react/utils/runtimeFooter.js +40 -22
- package/src/react/utils/runtimeLanguage.js +50 -9
- package/src/react/utils/runtimeMenu.js +190 -98
- package/src/react/utils/runtimeZoom.js +43 -0
- package/src/react/utils/shopConfig.js +72 -34
- package/src/react/utils/shopFranchises.js +182 -23
- package/src/react/utils/socketRuntimePipeline.js +14 -14
- package/src/react/utils/storeColumns.js +23 -4
- package/src/react/utils/userAvatar.js +104 -0
- package/src/store/categories/index.js +48 -20
- package/src/store/configs/index.js +2 -2
- package/src/store/device_config/index.js +62 -21
- package/src/store/entity_log/index.js +22 -1
- package/src/store/imports/customActions.js +51 -0
- package/src/store/imports/index.js +89 -88
- package/src/store/status/index.js +11 -11
- package/src/store/themes/index.js +22 -0
- package/src/store/timezones/index.js +3 -1
- package/src/tests/browser/manager/connections.spec.js +143 -0
- package/src/tests/browser/manager/devices-current.spec.js +343 -0
- package/src/tests/browser/manager/mercado-livre-integration.spec.js +268 -0
- package/src/tests/react/api/deviceHeaders.test.js +114 -0
- package/src/tests/react/api/loadSmokeIndex.test.js +75 -0
- package/src/tests/react/components/AddImportModal.test.js +166 -0
- package/src/tests/react/components/BottomNavigationBar.config.test.js +20 -0
- package/src/tests/react/components/UserAvatar.test.js +52 -0
- package/src/tests/react/config/deviceConfigBootstrap.test.js +47 -0
- package/src/tests/react/pages/DevicePickerStyles.test.js +47 -0
- package/src/tests/react/pages/Devices/deviceListHelpers.test.js +39 -0
- package/src/tests/react/pages/DevicesCurrentDevice.test.js +198 -0
- package/src/tests/react/pages/connectionsHelpers.test.js +34 -0
- package/src/tests/react/pages/mercadoLivreIntegrationPageContract.test.mjs +16 -0
- package/src/tests/react/print/localPrintProvider.test.js +1 -1
- package/src/tests/react/services/Cielo.test.js +190 -0
- package/src/tests/react/store/categoriesOrdering.test.js +26 -0
- package/src/tests/react/utils/appDomain.test.js +62 -0
- package/src/tests/react/utils/entityDisplay.test.js +2 -2
- package/src/tests/react/utils/fileUrl.test.js +62 -0
- package/src/tests/react/utils/formatter.test.mjs +33 -0
- package/src/tests/react/utils/importStatus.test.js +2 -2
- package/src/tests/react/utils/orderIdentity.test.js +139 -0
- package/src/tests/react/utils/remotePayment.test.js +62 -0
- package/src/tests/react/utils/runtimeFooter.test.js +32 -9
- package/src/tests/react/utils/runtimeLanguage.test.js +80 -10
- package/src/tests/react/utils/runtimeMenu.test.js +195 -111
- package/src/tests/react/utils/runtimeZoom.test.js +38 -0
- package/src/tests/react/utils/shopConfig.test.js +75 -0
- package/src/tests/react/utils/shopFranchises.test.js +122 -12
- package/src/tests/react/utils/translate.discovery.test.mjs +170 -0
- package/src/tests/react/utils/translate.test.helpers.mjs +152 -0
- package/src/tests/react/utils/translate.test.mjs +252 -396
- package/src/tests/react/utils/userAvatar.test.js +63 -0
- package/src/utils/appDomain.js +15 -4
- package/src/utils/formatter.js +122 -16
- package/src/utils/integrationConfigs.js +1 -0
- package/src/utils/translate.js +277 -398
- package/src/utils/translateDiscovery.js +96 -0
- package/src/utils/translateNormalize.js +33 -0
- package/src/utils/translatePending.js +92 -0
- package/src/utils/translateStorage.js +142 -0
- package/.github/agents/developer.agent.md +0 -30
- package/.github/agents/devops.agent.md +0 -30
- package/.github/agents/qa.agent.md +0 -30
- package/.github/agents/security.agent.md +0 -30
- package/.github/workflows/npmjs.yml +0 -35
package/src/api/index.js
CHANGED
|
@@ -1,11 +1,217 @@
|
|
|
1
1
|
import myFetch from '@controleonline/ui-common/src/api/fetch';
|
|
2
2
|
import axios from 'axios';
|
|
3
|
-
import { APP_ENV
|
|
3
|
+
import {env as APP_ENV} from '@env';
|
|
4
4
|
import { resolveAppDomain } from '@controleonline/ui-common/src/utils/appDomain';
|
|
5
5
|
import { resolveApiEntryPoint } from '@controleonline/ui-common/src/utils/apiEntryPoint';
|
|
6
|
-
|
|
7
|
-
const MIME_TYPE = 'application/ld+json';
|
|
8
|
-
|
|
6
|
+
|
|
7
|
+
const MIME_TYPE = 'application/ld+json';
|
|
8
|
+
const DOMAIN_QUERY_PARAM_KEYS = new Set(['app-domain', 'appdomain']);
|
|
9
|
+
|
|
10
|
+
function stripDomainQueryParams(uri) {
|
|
11
|
+
const value = String(uri || '');
|
|
12
|
+
const hashIndex = value.indexOf('#');
|
|
13
|
+
const withoutHash = hashIndex >= 0 ? value.slice(0, hashIndex) : value;
|
|
14
|
+
const hash = hashIndex >= 0 ? value.slice(hashIndex) : '';
|
|
15
|
+
const queryIndex = withoutHash.indexOf('?');
|
|
16
|
+
|
|
17
|
+
if (queryIndex < 0) {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const path = withoutHash.slice(0, queryIndex);
|
|
22
|
+
const params = new URLSearchParams(withoutHash.slice(queryIndex + 1));
|
|
23
|
+
|
|
24
|
+
for (const key of [...params.keys()]) {
|
|
25
|
+
if (DOMAIN_QUERY_PARAM_KEYS.has(String(key || '').trim().toLowerCase())) {
|
|
26
|
+
params.delete(key);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const query = params.toString();
|
|
31
|
+
return `${path}${query ? `?${query}` : ''}${hash}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function encodeBasicAuth(user, password) {
|
|
35
|
+
const credentials = `${String(user || '').trim()}:${String(password || '').trim()}`;
|
|
36
|
+
|
|
37
|
+
if (credentials === ':') {
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof btoa === 'function') {
|
|
42
|
+
return btoa(credentials);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (typeof Buffer !== 'undefined') {
|
|
46
|
+
return Buffer.from(credentials, 'utf8').toString('base64');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function resolveApiRuntimeConfig(config = {}) {
|
|
53
|
+
return {
|
|
54
|
+
apiBaseUrl: String(config.apiBaseUrl || APP_ENV.API_ENTRYPOINT || '').trim(),
|
|
55
|
+
domain: String(config.domain || APP_ENV.DOMAIN || '').trim(),
|
|
56
|
+
htaccessUser: String(config.htaccessUser || APP_ENV.HTACCESS_USER || '').trim(),
|
|
57
|
+
htaccessPassword: String(config.htaccessPassword || APP_ENV.HTACCESS_PASSWORD || '').trim(),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function buildApiUrl(apiBaseUrl, path) {
|
|
62
|
+
const apiEntryPoint = resolveApiEntryPoint(apiBaseUrl);
|
|
63
|
+
const entryPoint = apiEntryPoint + (apiEntryPoint.endsWith('/') ? '' : '/');
|
|
64
|
+
const rawPath = String(path || '').startsWith('/')
|
|
65
|
+
? String(path || '').substring(1)
|
|
66
|
+
: String(path || '');
|
|
67
|
+
const normalizedPath = stripDomainQueryParams(rawPath);
|
|
68
|
+
|
|
69
|
+
return new URL(normalizedPath, entryPoint).href;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveRequestDevice(masterDevice, device, bodyDeviceId) {
|
|
73
|
+
if (masterDevice?.id) {
|
|
74
|
+
return masterDevice;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (device?.id) {
|
|
78
|
+
return device;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return bodyDeviceId ? {id: bodyDeviceId} : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function readSmokeResponseBody(response, responseType) {
|
|
85
|
+
if (responseType === 'blob') {
|
|
86
|
+
return await response.blob();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (responseType === 'text') {
|
|
90
|
+
return await response.text();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const text = await response.text();
|
|
94
|
+
|
|
95
|
+
if (!text) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(text);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return text;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function buildSmokeHttpError(response, body) {
|
|
107
|
+
const message =
|
|
108
|
+
String(body?.message || body?.error || body || response.statusText || 'Request failed')
|
|
109
|
+
.trim() || 'Request failed';
|
|
110
|
+
|
|
111
|
+
const error = new Error(message);
|
|
112
|
+
error.code = response.status;
|
|
113
|
+
error.status = response.status;
|
|
114
|
+
error.body = body;
|
|
115
|
+
return error;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function requestSmoke(path, options = {}, config = {}) {
|
|
119
|
+
const runtimeConfig = resolveApiRuntimeConfig(config);
|
|
120
|
+
const url = buildApiUrl(runtimeConfig.apiBaseUrl, path);
|
|
121
|
+
const headers = new Headers(options.headers || undefined);
|
|
122
|
+
const accept =
|
|
123
|
+
options.responseType === 'blob' || options.responseType === 'text'
|
|
124
|
+
? '*/*'
|
|
125
|
+
: MIME_TYPE;
|
|
126
|
+
|
|
127
|
+
if (accept !== '' && !headers.has('Accept')) {
|
|
128
|
+
headers.set('Accept', accept);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (options.responseType !== 'blob' && !headers.has('Content-Type')) {
|
|
132
|
+
headers.set('Content-Type', MIME_TYPE);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const sessionToken = await api.getToken();
|
|
136
|
+
|
|
137
|
+
if (sessionToken && !headers.has('API-TOKEN')) {
|
|
138
|
+
headers.set('API-TOKEN', sessionToken);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const basicAuth = encodeBasicAuth(runtimeConfig.htaccessUser, runtimeConfig.htaccessPassword);
|
|
142
|
+
if (basicAuth !== '' && !headers.has('Authorization')) {
|
|
143
|
+
headers.set('Authorization', `Basic ${basicAuth}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const appDomain = resolveAppDomain(runtimeConfig.domain);
|
|
147
|
+
if (appDomain !== '' && !headers.has('App-Domain')) {
|
|
148
|
+
headers.set('App-Domain', appDomain);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let body = options.body;
|
|
152
|
+
const hasBody =
|
|
153
|
+
body !== undefined &&
|
|
154
|
+
body !== null &&
|
|
155
|
+
options.method !== 'GET' &&
|
|
156
|
+
options.method !== 'HEAD';
|
|
157
|
+
|
|
158
|
+
if (hasBody && typeof body !== 'string' && !(body instanceof FormData) && !(body instanceof Blob)) {
|
|
159
|
+
body = JSON.stringify(body);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (options.params) {
|
|
163
|
+
const params = api.serialize(options.params);
|
|
164
|
+
if (params.length > 0) {
|
|
165
|
+
const urlObject = new URL(url);
|
|
166
|
+
urlObject.search = params.join('&');
|
|
167
|
+
return requestSmokeUrl(urlObject.toString(), {
|
|
168
|
+
...options,
|
|
169
|
+
body,
|
|
170
|
+
headers,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return requestSmokeUrl(url, {
|
|
176
|
+
...options,
|
|
177
|
+
body,
|
|
178
|
+
headers,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function requestSmokeUrl(url, options) {
|
|
183
|
+
const response = await fetch(url, options);
|
|
184
|
+
|
|
185
|
+
if (options.responseType === 'blob') {
|
|
186
|
+
if (!response.ok) {
|
|
187
|
+
throw buildSmokeHttpError(response, await response.text().catch(() => ''));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return await response.blob();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const responseBody = await readSmokeResponseBody(response, options.responseType);
|
|
194
|
+
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
throw buildSmokeHttpError(response, responseBody);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (
|
|
200
|
+
responseBody &&
|
|
201
|
+
typeof responseBody === 'object' &&
|
|
202
|
+
(responseBody['@type'] === 'Error' ||
|
|
203
|
+
responseBody['@type'] === 'ConstraintViolationList')
|
|
204
|
+
) {
|
|
205
|
+
throw buildSmokeHttpError(response, responseBody);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (options.responseType === 'text') {
|
|
209
|
+
return typeof responseBody === 'string' ? responseBody : '';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return responseBody;
|
|
213
|
+
}
|
|
214
|
+
|
|
9
215
|
export const api = {
|
|
10
216
|
device: JSON.parse(localStorage.getItem('device') || '{}'),
|
|
11
217
|
masterDevice: JSON.parse(localStorage.getItem('master-device') || '{}'),
|
|
@@ -17,19 +223,25 @@ export const api = {
|
|
|
17
223
|
const apiEntryPoint = resolveApiEntryPoint(APP_ENV.API_ENTRYPOINT);
|
|
18
224
|
const entryPoint = apiEntryPoint + (apiEntryPoint.endsWith("/") ? "" : "/");
|
|
19
225
|
const url = new URL(uri.startsWith('/') ? uri.substring(1) : uri, entryPoint).href;
|
|
226
|
+
const basicAuth = encodeBasicAuth(APP_ENV.HTACCESS_USER, APP_ENV.HTACCESS_PASSWORD);
|
|
227
|
+
const headers = {
|
|
228
|
+
'API-TOKEN': token,
|
|
229
|
+
'App-Domain': appDomain,
|
|
230
|
+
'Accept': '*/*',
|
|
231
|
+
// IMPORTANTE: Deixe vazio para o Axios/Browser definir o boundary do multipart
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
if (basicAuth !== '') {
|
|
235
|
+
headers.Authorization = `Basic ${basicAuth}`;
|
|
236
|
+
}
|
|
20
237
|
|
|
21
238
|
return axios.post(url, formData, {
|
|
22
|
-
headers
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
},
|
|
30
|
-
fetch: async function (uri, options = {}) {
|
|
31
|
-
if (typeof options.headers === 'undefined')
|
|
32
|
-
Object.assign(options, { headers: new Headers() });
|
|
239
|
+
headers,
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
fetch: async function (uri, options = {}) {
|
|
243
|
+
if (typeof options.headers === 'undefined')
|
|
244
|
+
Object.assign(options, { headers: new Headers() });
|
|
33
245
|
|
|
34
246
|
// Always refresh device from localStorage in case it was set after module load
|
|
35
247
|
try {
|
|
@@ -45,14 +257,19 @@ export const api = {
|
|
|
45
257
|
} catch (e) {
|
|
46
258
|
this.masterDevice = {};
|
|
47
259
|
}
|
|
48
|
-
|
|
49
|
-
let token = await this.getToken();
|
|
50
|
-
if (token) options.headers.set('API-TOKEN', token);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (options.
|
|
54
|
-
|
|
55
|
-
|
|
260
|
+
|
|
261
|
+
let token = await this.getToken();
|
|
262
|
+
if (token) options.headers.set('API-TOKEN', token);
|
|
263
|
+
|
|
264
|
+
const basicAuth = encodeBasicAuth(APP_ENV.HTACCESS_USER, APP_ENV.HTACCESS_PASSWORD);
|
|
265
|
+
if (basicAuth !== '' && !options.headers.has('Authorization')) {
|
|
266
|
+
options.headers.set('Authorization', `Basic ${basicAuth}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let bodyDeviceId = null;
|
|
270
|
+
if (options.body) {
|
|
271
|
+
if (typeof options.body === 'object') {
|
|
272
|
+
bodyDeviceId = options.body.device || null;
|
|
56
273
|
} else if (typeof options.body === 'string') {
|
|
57
274
|
try {
|
|
58
275
|
const parsedBody = JSON.parse(options.body);
|
|
@@ -63,21 +280,36 @@ export const api = {
|
|
|
63
280
|
}
|
|
64
281
|
}
|
|
65
282
|
|
|
66
|
-
const
|
|
67
|
-
this.masterDevice
|
|
68
|
-
|
|
283
|
+
const requestDevice = resolveRequestDevice(
|
|
284
|
+
this.masterDevice,
|
|
285
|
+
this.device,
|
|
286
|
+
bodyDeviceId,
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
if (requestDevice?.id) {
|
|
290
|
+
options.headers.set('DEVICE', requestDevice.id);
|
|
291
|
+
|
|
292
|
+
const requestDeviceType = String(requestDevice.type || '')
|
|
293
|
+
.trim()
|
|
294
|
+
.toUpperCase();
|
|
295
|
+
if (requestDeviceType !== '') {
|
|
296
|
+
options.headers.set('DEVICE-TYPE', requestDeviceType);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
69
299
|
if (options.responseType != 'text') {
|
|
70
300
|
options.headers.set('Content-Type', MIME_TYPE);
|
|
71
301
|
options.headers.set('Accept', MIME_TYPE);
|
|
72
302
|
}
|
|
73
303
|
options.headers.set('App-Domain', resolveAppDomain(APP_ENV.DOMAIN));
|
|
74
|
-
|
|
75
|
-
if (options.body && typeof options.body != 'string') {
|
|
76
|
-
options.body = JSON.stringify(options.body);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
304
|
+
|
|
305
|
+
if (options.body && typeof options.body != 'string') {
|
|
306
|
+
options.body = JSON.stringify(options.body);
|
|
307
|
+
}
|
|
308
|
+
uri = stripDomainQueryParams(uri);
|
|
309
|
+
|
|
310
|
+
if (options.params) {
|
|
311
|
+
uri = this.buildQueryString(uri, options);
|
|
312
|
+
}
|
|
81
313
|
return myFetch(uri, options).catch(e => {
|
|
82
314
|
throw e;
|
|
83
315
|
});
|
|
@@ -100,13 +332,18 @@ export const api = {
|
|
|
100
332
|
|
|
101
333
|
return session?.token || session?.api_key;
|
|
102
334
|
},
|
|
103
|
-
serialize(obj, prefix = '') {
|
|
104
|
-
const pairs = [];
|
|
105
|
-
|
|
106
|
-
for (const key in obj) {
|
|
107
|
-
if (obj.hasOwnProperty(key)) {
|
|
108
|
-
const
|
|
109
|
-
|
|
335
|
+
serialize(obj, prefix = '') {
|
|
336
|
+
const pairs = [];
|
|
337
|
+
|
|
338
|
+
for (const key in obj) {
|
|
339
|
+
if (obj.hasOwnProperty(key)) {
|
|
340
|
+
const normalizedKey = String(key || '').trim().toLowerCase();
|
|
341
|
+
if (DOMAIN_QUERY_PARAM_KEYS.has(normalizedKey)) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const value = obj[key];
|
|
346
|
+
let fullKey = prefix ? `${prefix}.${key}` : key; // Usa ponto para objetos
|
|
110
347
|
|
|
111
348
|
if (value === null || value === undefined) {
|
|
112
349
|
pairs.push(`${fullKey}=`);
|
|
@@ -135,21 +372,54 @@ export const api = {
|
|
|
135
372
|
return pairs;
|
|
136
373
|
},
|
|
137
374
|
|
|
138
|
-
buildQueryString(uri, options) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
375
|
+
buildQueryString(uri, options) {
|
|
376
|
+
const safeUri = stripDomainQueryParams(uri);
|
|
377
|
+
|
|
378
|
+
if (options.params) {
|
|
379
|
+
const params = this.serialize(options.params);
|
|
380
|
+
if (params.length === 0) {
|
|
381
|
+
return safeUri;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const hashIndex = safeUri.indexOf('#');
|
|
385
|
+
const withoutHash = hashIndex >= 0 ? safeUri.slice(0, hashIndex) : safeUri;
|
|
386
|
+
const hash = hashIndex >= 0 ? safeUri.slice(hashIndex) : '';
|
|
387
|
+
const separator = withoutHash.includes('?') ? '&' : '?';
|
|
388
|
+
|
|
389
|
+
return `${withoutHash}${separator}${params.join('&')}${hash}`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return safeUri;
|
|
393
|
+
},
|
|
394
|
+
post: async function (uri, body = {}) {
|
|
395
|
+
const options = {
|
|
396
|
+
method: 'POST',
|
|
397
|
+
body: body,
|
|
398
|
+
};
|
|
399
|
+
return await this.fetch(uri, options);
|
|
400
|
+
},
|
|
401
|
+
loadSmokeIndex: async function (config = {}) {
|
|
402
|
+
return await requestSmoke('/tests', {}, config);
|
|
403
|
+
},
|
|
404
|
+
loadArtifactBlob: async function (config = {}, artifact) {
|
|
405
|
+
if (!artifact || typeof artifact !== 'object') {
|
|
406
|
+
throw new Error('Artefato inválido.');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (!artifact.url) {
|
|
410
|
+
throw new Error('Artefato inválido.');
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return await requestSmoke(artifact.url, {
|
|
414
|
+
responseType: 'blob',
|
|
415
|
+
}, config);
|
|
416
|
+
},
|
|
417
|
+
triggerSmokeRun: async function (config = {}) {
|
|
418
|
+
return await requestSmoke('/tests/run', {
|
|
419
|
+
method: 'POST',
|
|
420
|
+
}, config);
|
|
421
|
+
},
|
|
422
|
+
execute: function (params) {
|
|
423
|
+
return axios(params);
|
|
424
|
+
},
|
|
425
|
+
};
|
|
@@ -1,108 +1,68 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {
|
|
3
|
-
View,
|
|
4
|
-
Text,
|
|
5
|
-
TouchableOpacity,
|
|
6
|
-
ScrollView,
|
|
7
|
-
ActivityIndicator,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import Icon from 'react-native-vector-icons/MaterialIcons';
|
|
12
|
-
import * as DocumentPicker from 'expo-document-picker';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
TouchableOpacity,
|
|
6
|
+
ScrollView,
|
|
7
|
+
ActivityIndicator,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
|
|
10
|
+
import Icon from 'react-native-vector-icons/MaterialIcons';
|
|
13
11
|
import { useStore } from '@store';
|
|
14
|
-
import AnimatedModal from '
|
|
15
|
-
import { api } from '@controleonline/ui-common/src/api';
|
|
12
|
+
import AnimatedModal from './AnimatedModal';
|
|
16
13
|
import { useMessage } from '@controleonline/ui-common/src/react/components/MessageService';
|
|
14
|
+
import DefaultUpload from '@controleonline/ui-default/src/react/components/upload/DefaultUpload';
|
|
17
15
|
import styles from './AddImportModal.styles';
|
|
18
|
-
|
|
19
|
-
const AddImportModal = ({ visible, onClose, onSuccess, context = {} }) => {
|
|
20
|
-
const { showError, showSuccess } = useMessage();
|
|
21
|
-
const peopleStore = useStore('people');
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
const
|
|
16
|
+
|
|
17
|
+
const AddImportModal = ({ visible, onClose, onSuccess, context = {} }) => {
|
|
18
|
+
const { showError, showSuccess } = useMessage();
|
|
19
|
+
const peopleStore = useStore('people');
|
|
20
|
+
const importsStore = useStore('imports');
|
|
21
|
+
const themeStore = useStore('theme');
|
|
22
|
+
const getters = peopleStore.getters;
|
|
23
|
+
const { currentCompany } = getters;
|
|
24
|
+
const importActions = importsStore.actions || {};
|
|
25
|
+
const themeColors = themeStore?.getters?.colors || {};
|
|
26
|
+
const buttonPalette = {
|
|
27
|
+
buttonBackground: themeColors.buttonBackground,
|
|
28
|
+
buttonBorder: themeColors.buttonBorder,
|
|
29
|
+
buttonText: themeColors.buttonText,
|
|
30
|
+
buttonIcon: themeColors.buttonIcon || themeColors.buttonText,
|
|
31
|
+
};
|
|
32
|
+
|
|
27
33
|
const modalTitle = global.t?.t('imports', 'title', 'new_import');
|
|
28
34
|
const csvLabel = global.t?.t('imports', 'label', 'csv_file');
|
|
29
35
|
const selectFileLabel = global.t?.t('imports', 'button', 'select_file');
|
|
30
36
|
const cancelLabel = global.t?.t('imports', 'button', 'cancel');
|
|
31
|
-
const importLabel = global.t?.t('imports', 'button', 'import');
|
|
32
37
|
const csvOnlyLabel = global.t?.t('imports', 'message', 'only_csv_files_are_allowed');
|
|
33
|
-
const pickFileErrorLabel = global.t?.t('imports', 'error', 'error_selecting_file');
|
|
34
38
|
const importSuccessLabel = global.t?.t('imports', 'success', 'import_sent_successfully');
|
|
35
39
|
const importErrorLabel = global.t?.t('imports', 'error', 'error_sending_import');
|
|
36
|
-
const missingFileLabel = global.t?.t('imports', 'error', 'select_a_csv_file');
|
|
37
|
-
|
|
38
|
-
const handlePickFile = async () => {
|
|
39
|
-
try {
|
|
40
|
-
const result = await DocumentPicker.getDocumentAsync({
|
|
41
|
-
type: 'text/csv',
|
|
42
|
-
copyToCacheDirectory: true,
|
|
43
|
-
multiple: false,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
if (result.canceled) return;
|
|
47
|
-
|
|
48
|
-
const pickedFile = result.assets[0];
|
|
49
|
-
|
|
50
|
-
if (!pickedFile.name?.toLowerCase().endsWith('.csv')) {
|
|
51
|
-
showError(csvOnlyLabel);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
setFile(pickedFile);
|
|
56
|
-
} catch {
|
|
57
|
-
showError(pickFileErrorLabel);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
40
|
|
|
61
|
-
const
|
|
62
|
-
if (!file) {
|
|
63
|
-
|
|
64
|
-
return;
|
|
41
|
+
const handleUploadImportFile = async ({ file }) => {
|
|
42
|
+
if (!file?.name?.toLowerCase().endsWith('.csv')) {
|
|
43
|
+
throw new Error(csvOnlyLabel);
|
|
65
44
|
}
|
|
66
|
-
|
|
67
|
-
setLoading(true);
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
const formData = new FormData();
|
|
71
|
-
formData.append('importType', context.context);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
formData.append('people', String(currentCompany.id));
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (Platform.OS === 'web') {
|
|
78
|
-
const response = await fetch(file.uri);
|
|
79
|
-
const blob = await response.blob();
|
|
80
|
-
formData.append('file', blob, file.name || 'import.csv');
|
|
81
|
-
} else {
|
|
82
|
-
formData.append('file', {
|
|
83
|
-
uri: file.uri,
|
|
84
|
-
name: file.name || 'import.csv',
|
|
85
|
-
type: file.mimeType || 'text/csv',
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
45
|
|
|
89
|
-
|
|
46
|
+
try {
|
|
47
|
+
await importActions.uploadImportFile({
|
|
48
|
+
file,
|
|
49
|
+
importType: context.context,
|
|
50
|
+
peopleId: currentCompany.id,
|
|
51
|
+
});
|
|
90
52
|
showSuccess(importSuccessLabel);
|
|
91
53
|
if (onSuccess) onSuccess();
|
|
92
54
|
handleClose();
|
|
55
|
+
return file;
|
|
93
56
|
} catch {
|
|
94
|
-
|
|
95
|
-
} finally {
|
|
96
|
-
setLoading(false);
|
|
57
|
+
throw new Error(importErrorLabel);
|
|
97
58
|
}
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const handleClose = () => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return (
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const handleClose = () => {
|
|
62
|
+
onClose();
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return (
|
|
106
66
|
<AnimatedModal
|
|
107
67
|
visible={visible}
|
|
108
68
|
onRequestClose={handleClose}
|
|
@@ -118,15 +78,50 @@ const AddImportModal = ({ visible, onClose, onSuccess, context = {} }) => {
|
|
|
118
78
|
|
|
119
79
|
<ScrollView style={styles.content}>
|
|
120
80
|
<Text style={styles.label}>{csvLabel}</Text>
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
81
|
+
<DefaultUpload
|
|
82
|
+
relationStoreName="imports"
|
|
83
|
+
relationField="import"
|
|
84
|
+
relationResource="imports"
|
|
85
|
+
entityId={context.context || 'import'}
|
|
86
|
+
companyId={currentCompany.id}
|
|
87
|
+
context={`imports-${context.context || 'default'}`}
|
|
88
|
+
libraryContexts={[`imports-${context.context || 'default'}`]}
|
|
89
|
+
acceptedTypes="text/csv,.csv"
|
|
90
|
+
fileType=""
|
|
91
|
+
title={csvLabel}
|
|
92
|
+
triggerLabel={selectFileLabel}
|
|
93
|
+
managerTitle={modalTitle}
|
|
94
|
+
searchPlaceholder={selectFileLabel}
|
|
95
|
+
uploadButtonLabel={selectFileLabel}
|
|
96
|
+
emptyAttachmentLabel=""
|
|
97
|
+
emptyLibraryLabel={selectFileLabel}
|
|
98
|
+
showInlineContent={false}
|
|
99
|
+
uploadResultAlreadyAttached
|
|
100
|
+
requireEntity={false}
|
|
101
|
+
onUploadFile={handleUploadImportFile}
|
|
102
|
+
renderTrigger={({openManager, uploading}) => (
|
|
103
|
+
<TouchableOpacity
|
|
104
|
+
onPress={openManager}
|
|
105
|
+
disabled={uploading}
|
|
106
|
+
style={[
|
|
107
|
+
styles.filePicker,
|
|
108
|
+
{
|
|
109
|
+
backgroundColor: buttonPalette.buttonBackground,
|
|
110
|
+
borderColor: buttonPalette.buttonBorder,
|
|
111
|
+
},
|
|
112
|
+
]}
|
|
113
|
+
>
|
|
114
|
+
<Text numberOfLines={1} style={[styles.fileName, { color: buttonPalette.buttonText }]}>
|
|
115
|
+
{selectFileLabel}
|
|
116
|
+
</Text>
|
|
117
|
+
{uploading ? (
|
|
118
|
+
<ActivityIndicator color={buttonPalette.buttonIcon} />
|
|
119
|
+
) : (
|
|
120
|
+
<Icon name="upload-file" size={22} color={buttonPalette.buttonIcon} />
|
|
121
|
+
)}
|
|
122
|
+
</TouchableOpacity>
|
|
123
|
+
)}
|
|
124
|
+
/>
|
|
130
125
|
<Text style={styles.helperText}>
|
|
131
126
|
{csvOnlyLabel}
|
|
132
127
|
</Text>
|
|
@@ -136,22 +131,11 @@ const AddImportModal = ({ visible, onClose, onSuccess, context = {} }) => {
|
|
|
136
131
|
<TouchableOpacity onPress={handleClose} style={styles.secondaryButton}>
|
|
137
132
|
<Text>{cancelLabel}</Text>
|
|
138
133
|
</TouchableOpacity>
|
|
139
|
-
|
|
140
|
-
<TouchableOpacity
|
|
141
|
-
onPress={handleSave}
|
|
142
|
-
disabled={loading}
|
|
143
|
-
style={[
|
|
144
|
-
styles.primaryButton,
|
|
145
|
-
loading && styles.primaryButtonDisabled,
|
|
146
|
-
]}
|
|
147
|
-
>
|
|
148
|
-
{loading ? <ActivityIndicator color="#fff" /> : <Text style={styles.primaryButtonText}>{importLabel}</Text>}
|
|
149
|
-
</TouchableOpacity>
|
|
150
134
|
</View>
|
|
151
135
|
</View>
|
|
152
|
-
</AnimatedModal>
|
|
153
|
-
);
|
|
154
|
-
};
|
|
155
|
-
|
|
136
|
+
</AnimatedModal>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
156
140
|
export default AddImportModal;
|
|
157
141
|
// TODO(store-first): quando este arquivo for mexido, mover a leitura para stores e evitar chamadas HTTP diretas quando o store ja resolver isso.
|