@controleonline/ui-common 1.2.79 → 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/passwordPolicy.js +73 -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 +6 -1
- 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 +27 -10
- 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 +10 -8
- package/src/tests/react/utils/shopFranchises.test.js +121 -115
- package/src/tests/react/utils/systemErrorMessage.test.js +32 -59
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Linking, Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export const ROUTE_PROVIDER_MAP = {
|
|
4
|
+
UberIntegrationPage: 'uber',
|
|
5
|
+
AsaasIntegrationPage: 'asaas',
|
|
6
|
+
ClickSignIntegrationPage: 'clicksign',
|
|
7
|
+
ReceitaFederalIntegrationPage: 'receita-federal',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const routeNameToPath = routeName =>
|
|
11
|
+
String(routeName || '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
12
|
+
|
|
13
|
+
export const normalizeTextValue = value => {
|
|
14
|
+
let text = String(value ?? '').trim();
|
|
15
|
+
if (
|
|
16
|
+
(text.startsWith('"') && text.endsWith('"') && text.length >= 2) ||
|
|
17
|
+
(text.startsWith("'") && text.endsWith("'") && text.length >= 2)
|
|
18
|
+
) {
|
|
19
|
+
text = text.slice(1, -1).trim();
|
|
20
|
+
}
|
|
21
|
+
return text;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const formatApiError = error => {
|
|
25
|
+
if (!error) return 'Nao foi possivel carregar a configuracao da integracao.';
|
|
26
|
+
if (typeof error === 'string') return error;
|
|
27
|
+
return error?.message || error?.description || error?.errmsg || 'Nao foi possivel carregar a configuracao da integracao.';
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const getConfigFields = providerConfig => {
|
|
31
|
+
if (!providerConfig) return [];
|
|
32
|
+
if (Array.isArray(providerConfig.tabs) && providerConfig.tabs.length > 0) {
|
|
33
|
+
return providerConfig.tabs.flatMap(tab => tab.fields || []);
|
|
34
|
+
}
|
|
35
|
+
return providerConfig.fields || [];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const buildFieldValues = (providerConfig, source) => {
|
|
39
|
+
const sourceMap = Array.isArray(source)
|
|
40
|
+
? source.reduce((acc, item) => {
|
|
41
|
+
const key = String(item?.configKey || '').trim();
|
|
42
|
+
if (key) acc[key] = item?.configValue;
|
|
43
|
+
return acc;
|
|
44
|
+
}, {})
|
|
45
|
+
: source && typeof source === 'object' ? source : {};
|
|
46
|
+
|
|
47
|
+
return getConfigFields(providerConfig).reduce((acc, field) => {
|
|
48
|
+
acc[field.key] = normalizeTextValue(sourceMap[field.key]);
|
|
49
|
+
return acc;
|
|
50
|
+
}, {});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const resolveProviderId = ({ route, currentCompany }) => {
|
|
54
|
+
const routeCompanyId = String(route?.params?.companyId || '').replace(/\D/g, '');
|
|
55
|
+
if (routeCompanyId) return Number(routeCompanyId);
|
|
56
|
+
|
|
57
|
+
const currentCompanyId = String(currentCompany?.id || '').replace(/\D/g, '');
|
|
58
|
+
return currentCompanyId ? Number(currentCompanyId) : null;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const resolveFallbackConfigs = ({ providerId, currentCompany }) =>
|
|
62
|
+
Number(providerId) === Number(currentCompany?.id) ? currentCompany?.configs || {} : {};
|
|
63
|
+
|
|
64
|
+
export const isConnectedValue = value =>
|
|
65
|
+
value === true || value === 1 || value === '1' || String(value).trim().toLowerCase() === 'true';
|
|
66
|
+
|
|
67
|
+
export const toConfigRequestValue = value => {
|
|
68
|
+
if (value === undefined || value === null) return '';
|
|
69
|
+
if (typeof value === 'string') return normalizeTextValue(value);
|
|
70
|
+
if (typeof value === 'object') return JSON.stringify(value);
|
|
71
|
+
return String(value);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const extractAuthorizationUrl = response => normalizeTextValue(
|
|
75
|
+
response?.member?.[0]?.authorization_url || response?.member?.[0]?.auth_url ||
|
|
76
|
+
response?.member?.[0]?.url || response?.authorization_url || response?.auth_url ||
|
|
77
|
+
response?.url || response?.data?.authorization_url || response?.data?.auth_url || response?.data?.url,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
export const formatUberOAuthError = error => {
|
|
81
|
+
const normalized = normalizeTextValue(error).toLowerCase();
|
|
82
|
+
if (normalized === 'invalid_scope') {
|
|
83
|
+
return 'O Uber nao liberou o scope pos_provisioning para este app. Esse app precisa estar aprovado/whitelisted no dashboard do Uber.';
|
|
84
|
+
}
|
|
85
|
+
if (normalized === 'access_denied') return 'O login do Uber foi cancelado.';
|
|
86
|
+
return normalizeTextValue(error) || 'Nao foi possivel concluir a conexao com o Uber.';
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const openAuthorizationUrl = async authUrl => {
|
|
90
|
+
if (Platform.OS === 'web' && typeof window !== 'undefined' && typeof window.location?.assign === 'function') {
|
|
91
|
+
window.location.assign(authUrl);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
await Linking.openURL(authUrl);
|
|
95
|
+
};
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
resolvePrinterSelectionValue,
|
|
29
29
|
resolveSelectedPrinter,
|
|
30
30
|
} from '@controleonline/ui-common/src/react/print/selection';
|
|
31
|
+
import {resolvePrintRoutingError} from '@controleonline/ui-common/src/react/utils/printRouting';
|
|
31
32
|
|
|
32
33
|
const resolveCurrentItemId = storeGetters =>
|
|
33
34
|
storeGetters?.item?.['@id']
|
|
@@ -396,7 +397,14 @@ export const usePrintButtonController = ({
|
|
|
396
397
|
return;
|
|
397
398
|
}
|
|
398
399
|
|
|
399
|
-
setError(
|
|
400
|
+
setError(
|
|
401
|
+
resolvePrintRoutingError({
|
|
402
|
+
targetDeviceId: selectedPrinter?.device,
|
|
403
|
+
selectedPrinter,
|
|
404
|
+
}) ||
|
|
405
|
+
global.t?.t('orders', 'title', 'selectPrinter') ||
|
|
406
|
+
'Selecione a impressora.',
|
|
407
|
+
);
|
|
400
408
|
return;
|
|
401
409
|
}
|
|
402
410
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import GetnetService from './Getnet';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Map wallet paymentCode / paymentType label to Getnet deeplink paymentType.
|
|
5
|
+
*/
|
|
6
|
+
export const resolveGetnetPaymentType = payment => {
|
|
7
|
+
const raw = String(
|
|
8
|
+
payment?.paymentCode ||
|
|
9
|
+
payment?.paymentType?.paymentType ||
|
|
10
|
+
payment?.paymentType?.name ||
|
|
11
|
+
payment?.type ||
|
|
12
|
+
'',
|
|
13
|
+
)
|
|
14
|
+
.trim()
|
|
15
|
+
.toLowerCase();
|
|
16
|
+
|
|
17
|
+
if (!raw) return 'credit';
|
|
18
|
+
|
|
19
|
+
if (raw.includes('pix')) return 'pix';
|
|
20
|
+
if (raw.includes('debit') || raw.includes('debito') || raw.includes('débito')) {
|
|
21
|
+
return 'debit';
|
|
22
|
+
}
|
|
23
|
+
if (raw.includes('voucher') || raw.includes('vr') || raw.includes('va')) {
|
|
24
|
+
return 'voucher';
|
|
25
|
+
}
|
|
26
|
+
if (raw.includes('credit') || raw.includes('credito') || raw.includes('crédito')) {
|
|
27
|
+
return 'credit';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return 'credit';
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const runGetnetCheckoutPayment = async ({
|
|
34
|
+
installments = null,
|
|
35
|
+
order = null,
|
|
36
|
+
payment = null,
|
|
37
|
+
total = 0,
|
|
38
|
+
}) => {
|
|
39
|
+
const resolvedTotal = Number(total || 0);
|
|
40
|
+
|
|
41
|
+
if (resolvedTotal <= 0) {
|
|
42
|
+
throw new Error('Informe um valor de pagamento valido.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const orderId =
|
|
46
|
+
order?.id ||
|
|
47
|
+
String(order?.['@id'] || '')
|
|
48
|
+
.split('/')
|
|
49
|
+
.pop() ||
|
|
50
|
+
'';
|
|
51
|
+
|
|
52
|
+
const callerId = `getnet-${orderId || 'order'}-${Date.now()}`;
|
|
53
|
+
const paymentType = resolveGetnetPaymentType(payment);
|
|
54
|
+
const resolvedInstallments =
|
|
55
|
+
Number(installments || payment?.installments || 1) || 1;
|
|
56
|
+
|
|
57
|
+
const response = await new GetnetService().payment({
|
|
58
|
+
amount: resolvedTotal,
|
|
59
|
+
paymentType,
|
|
60
|
+
callerId,
|
|
61
|
+
installments: resolvedInstallments,
|
|
62
|
+
orderId: orderId ? String(orderId) : undefined,
|
|
63
|
+
allowPrintCurrentTransaction: true,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!response?.success) {
|
|
67
|
+
const detail =
|
|
68
|
+
response?.result?.resultDetails ||
|
|
69
|
+
response?.result?.result ||
|
|
70
|
+
response?.code ||
|
|
71
|
+
'Pagamento Getnet nao aprovado.';
|
|
72
|
+
throw new Error(
|
|
73
|
+
typeof detail === 'string' ? detail : JSON.stringify(detail),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
paidAmount: resolvedTotal,
|
|
79
|
+
response,
|
|
80
|
+
};
|
|
81
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import Getnet from '@controleonline/react-native-getnet-payment';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Thin adapter over the native Getnet POS deeplink module.
|
|
5
|
+
* paymentType: credit | debit | voucher | pix
|
|
6
|
+
*/
|
|
7
|
+
class GetnetService {
|
|
8
|
+
async payment({
|
|
9
|
+
amount,
|
|
10
|
+
paymentType = 'credit',
|
|
11
|
+
callerId,
|
|
12
|
+
installments = 1,
|
|
13
|
+
creditType,
|
|
14
|
+
allowPrintCurrentTransaction = true,
|
|
15
|
+
orderId,
|
|
16
|
+
}) {
|
|
17
|
+
if (!callerId) {
|
|
18
|
+
throw new Error('callerId is required for Getnet payment.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const resolvedAmount = Number(amount || 0);
|
|
22
|
+
if (resolvedAmount <= 0) {
|
|
23
|
+
throw new Error('Informe um valor de pagamento valido.');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const params = {
|
|
27
|
+
amount: resolvedAmount,
|
|
28
|
+
paymentType: String(paymentType || 'credit').toLowerCase(),
|
|
29
|
+
callerId: String(callerId),
|
|
30
|
+
installments: Number(installments) > 0 ? Number(installments) : 1,
|
|
31
|
+
allowPrintCurrentTransaction: Boolean(allowPrintCurrentTransaction),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
if (params.installments > 1 && creditType) {
|
|
35
|
+
params.creditType = creditType;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (orderId) {
|
|
39
|
+
params.orderId = String(orderId);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const response = await Getnet.payment(params);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
success: Boolean(response?.success),
|
|
46
|
+
code: response?.result ?? response?.code ?? '',
|
|
47
|
+
result: response,
|
|
48
|
+
authorizationCode: response?.authorizationCode,
|
|
49
|
+
nsu: response?.nsu,
|
|
50
|
+
cvNumber: response?.cvNumber,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async refund({amount, cvNumber, transactionDate, originTerminal}) {
|
|
55
|
+
const response = await Getnet.refund({
|
|
56
|
+
amount: Number(amount || 0),
|
|
57
|
+
cvNumber,
|
|
58
|
+
transactionDate,
|
|
59
|
+
originTerminal,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
success: Boolean(response?.success),
|
|
64
|
+
result: response,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async reprint() {
|
|
69
|
+
return Getnet.reprint();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async checkStatus(callerId) {
|
|
73
|
+
return Getnet.checkStatus(callerId);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default GetnetService;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {runCieloCheckoutPayment} from './Cielo/Checkout';
|
|
2
2
|
import {runInfinitePayCheckoutPayment} from './InfinitePay/Checkout';
|
|
3
|
+
import {runGetnetCheckoutPayment} from './Getnet/Checkout';
|
|
3
4
|
|
|
4
5
|
import {
|
|
5
6
|
PAYMENT_GATEWAY_CIELO,
|
|
6
7
|
PAYMENT_GATEWAY_INFINITE_PAY,
|
|
8
|
+
PAYMENT_GATEWAY_GETNET,
|
|
7
9
|
} from '../utils/paymentDevices';
|
|
8
10
|
|
|
9
11
|
export const normalizeGatewayPaymentError = (
|
|
@@ -87,5 +89,23 @@ export const runConfiguredGatewayPayment = async ({
|
|
|
87
89
|
};
|
|
88
90
|
}
|
|
89
91
|
|
|
92
|
+
if (gateway === PAYMENT_GATEWAY_GETNET) {
|
|
93
|
+
const {response, paidAmount} = await runGetnetCheckoutPayment({
|
|
94
|
+
installments,
|
|
95
|
+
order,
|
|
96
|
+
payment,
|
|
97
|
+
total: resolvedTotal,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (!response?.success) {
|
|
101
|
+
throw new Error(normalizeGatewayPaymentError(response?.result));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
paidAmount,
|
|
106
|
+
response,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
90
110
|
throw new Error('Gateway de pagamento indisponível neste equipamento.');
|
|
91
111
|
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toolbar bottom-nav helpers: role-aware menus + safe fallback.
|
|
3
|
+
* Preset is never a permission bypass — only Home/Profile on empty/failed API.
|
|
4
|
+
* CommonJS so unit tests can run with node --test like runtimeLanguage.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Routes allowed in the controlled fallback (never business routes). */
|
|
8
|
+
const SAFE_TOOLBAR_FALLBACK_ROUTES = Object.freeze([
|
|
9
|
+
'HomePage',
|
|
10
|
+
'ProfilePage',
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const SAFE_ROUTE_SET = new Set(SAFE_TOOLBAR_FALLBACK_ROUTES);
|
|
14
|
+
|
|
15
|
+
const normalizeMenuType = (value, fallback = 'home') => {
|
|
16
|
+
const normalized = String(value == null ? '' : value)
|
|
17
|
+
.trim()
|
|
18
|
+
.toLowerCase();
|
|
19
|
+
return normalized || fallback;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const filterMenusByType = (menus, menuType) => {
|
|
23
|
+
const normalizedMenuType =
|
|
24
|
+
menuType === null || menuType === undefined || String(menuType).trim() === ''
|
|
25
|
+
? null
|
|
26
|
+
: normalizeMenuType(menuType, '');
|
|
27
|
+
|
|
28
|
+
const modules = Array.isArray(menus)
|
|
29
|
+
? menus.map(module => ({
|
|
30
|
+
...module,
|
|
31
|
+
menus: Array.isArray(module?.menus)
|
|
32
|
+
? module.menus.map(menu => ({...menu}))
|
|
33
|
+
: [],
|
|
34
|
+
}))
|
|
35
|
+
: [];
|
|
36
|
+
|
|
37
|
+
if (!normalizedMenuType) {
|
|
38
|
+
return modules;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return modules
|
|
42
|
+
.map(module => ({
|
|
43
|
+
...module,
|
|
44
|
+
menus: module.menus.filter(
|
|
45
|
+
menu => normalizeMenuType(menu?.menuType, '') === normalizedMenuType,
|
|
46
|
+
),
|
|
47
|
+
}))
|
|
48
|
+
.filter(module => module.menus.length > 0);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const resolvePresetLabel = (translate, descriptor) => {
|
|
52
|
+
if (!descriptor || typeof descriptor === 'string') {
|
|
53
|
+
return descriptor || '';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const translated = translate?.(
|
|
57
|
+
descriptor.store,
|
|
58
|
+
descriptor.type,
|
|
59
|
+
descriptor.key,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return translated || descriptor.fallback || descriptor.key || '';
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Build a minimal safe toolbar module list from a navigation preset.
|
|
67
|
+
* Only HomePage + ProfilePage are kept so fallback cannot expose unauthorized
|
|
68
|
+
* business routes (Clientes, Oportunidades, etc.).
|
|
69
|
+
*/
|
|
70
|
+
const buildSafeToolbarFallbackMenus = (
|
|
71
|
+
preset,
|
|
72
|
+
menuType = 'toolbar',
|
|
73
|
+
translate,
|
|
74
|
+
presetKey = 'safe-toolbar-fallback',
|
|
75
|
+
) => {
|
|
76
|
+
const items = Array.isArray(preset?.items) ? preset.items : [];
|
|
77
|
+
const safeItems = items.filter(item => SAFE_ROUTE_SET.has(item?.route));
|
|
78
|
+
|
|
79
|
+
if (safeItems.length === 0) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const menus = safeItems.map((item, index) => ({
|
|
84
|
+
...item,
|
|
85
|
+
menuType,
|
|
86
|
+
sortOrder: Number(item?.sortOrder || (index + 1) * 10),
|
|
87
|
+
label: resolvePresetLabel(translate, item?.label),
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
return [
|
|
91
|
+
{
|
|
92
|
+
id: presetKey || 'safe-toolbar-fallback',
|
|
93
|
+
label: preset?.label || '',
|
|
94
|
+
icon: preset?.icon || '',
|
|
95
|
+
menus,
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Prefer API toolbar menus when present; otherwise safe preset fallback.
|
|
102
|
+
* Does not use the full business preset as a permission bypass.
|
|
103
|
+
*/
|
|
104
|
+
const resolveToolbarRuntimeMenus = ({
|
|
105
|
+
apiMenus,
|
|
106
|
+
preset,
|
|
107
|
+
presetKey,
|
|
108
|
+
menuType = 'toolbar',
|
|
109
|
+
translate,
|
|
110
|
+
} = {}) => {
|
|
111
|
+
const filtered = filterMenusByType(apiMenus || [], menuType);
|
|
112
|
+
const hasAuthorizedItems = filtered.some(
|
|
113
|
+
module => Array.isArray(module?.menus) && module.menus.length > 0,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (hasAuthorizedItems) {
|
|
117
|
+
return filtered;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (preset) {
|
|
121
|
+
return buildSafeToolbarFallbackMenus(
|
|
122
|
+
preset,
|
|
123
|
+
menuType,
|
|
124
|
+
translate,
|
|
125
|
+
presetKey || 'safe-toolbar-fallback',
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return [];
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Flatten toolbar menus into bottom-nav item shape (route/icon/label/...).
|
|
134
|
+
*/
|
|
135
|
+
const mapRuntimeMenusToNavItems = (runtimeMenus, menuType) =>
|
|
136
|
+
filterMenusByType(runtimeMenus || [], menuType)
|
|
137
|
+
.flatMap(module => (Array.isArray(module?.menus) ? module.menus : []))
|
|
138
|
+
.map(item => ({
|
|
139
|
+
route: item.route,
|
|
140
|
+
icon: item.icon || 'circle',
|
|
141
|
+
label: item.label,
|
|
142
|
+
menuKey: item.menuKey,
|
|
143
|
+
routeParams: item.routeParams,
|
|
144
|
+
menuType: item.menuType || menuType,
|
|
145
|
+
sortOrder: Number(item?.sortOrder || 0),
|
|
146
|
+
}))
|
|
147
|
+
.filter(item => Boolean(item.route))
|
|
148
|
+
.sort((left, right) => {
|
|
149
|
+
const orderDiff = left.sortOrder - right.sortOrder;
|
|
150
|
+
if (orderDiff !== 0) return orderDiff;
|
|
151
|
+
return String(left.label || left.menuKey || '').localeCompare(
|
|
152
|
+
String(right.label || right.menuKey || ''),
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
module.exports = {
|
|
157
|
+
SAFE_TOOLBAR_FALLBACK_ROUTES,
|
|
158
|
+
buildSafeToolbarFallbackMenus,
|
|
159
|
+
mapRuntimeMenusToNavItems,
|
|
160
|
+
resolveToolbarRuntimeMenus,
|
|
161
|
+
};
|
|
@@ -121,21 +121,33 @@ export const fetchOrderProducts = async orderRef => {
|
|
|
121
121
|
return extractCollectionItems(response);
|
|
122
122
|
};
|
|
123
123
|
|
|
124
|
-
export const searchCompanyProducts = async ({
|
|
124
|
+
export const searchCompanyProducts = async ({
|
|
125
|
+
companyId,
|
|
126
|
+
query = '',
|
|
127
|
+
categoryId = null,
|
|
128
|
+
}) => {
|
|
125
129
|
if (!companyId) {
|
|
126
130
|
return [];
|
|
127
131
|
}
|
|
128
132
|
|
|
129
|
-
const params = {
|
|
130
|
-
company: toEntityIri(companyId, 'people'),
|
|
131
|
-
active: 1,
|
|
132
|
-
'order[product]': 'ASC',
|
|
133
|
-
};
|
|
133
|
+
const params = {
|
|
134
|
+
company: toEntityIri(companyId, 'people'),
|
|
135
|
+
active: 1,
|
|
136
|
+
'order[product]': 'ASC',
|
|
137
|
+
};
|
|
134
138
|
|
|
135
139
|
if (String(query || '').trim()) {
|
|
136
140
|
params.product = String(query).trim();
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
// ProductCategoryTreeFilter — includes the category and its descendants.
|
|
144
|
+
// Prefer server-side filter: product payloads do not always embed productCategory
|
|
145
|
+
// under the product:read serialization group.
|
|
146
|
+
const normalizedCategoryId = normalizeEntityId(categoryId);
|
|
147
|
+
if (normalizedCategoryId) {
|
|
148
|
+
params['productCategory.category'] = normalizedCategoryId;
|
|
149
|
+
}
|
|
150
|
+
|
|
139
151
|
const response = await api.fetch('products', { params });
|
|
140
152
|
return extractCollectionItems(response);
|
|
141
153
|
};
|